diff --git a/UPDATING.md b/UPDATING.md index 7cfd118b90b1..253f2d531500 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -24,6 +24,8 @@ assists people when migrating to a new version. ## Next +- Partition filter mapping no longer suggests bare `unix_timestamp(:value)` on Hive/Impala-family engines. Existing temporal-to-numeric mappings using that expression, or any transform that answers raw epoch instants in seconds through nanoseconds (for example `CAST(unix_timestamp(:value) AS BIGINT)` or `utc_to_unix_micros(:value)`, from a timestamp column or a text one in any common layout), stop pruning to prevent incorrect counts on hour/day epoch keys; this includes keys that really store raw epoch instants. Replace it with the expression that populates the partition key, including rounding and timezone. The dataset editor can explicitly compare a candidate against sampled rows from selected numeric partitions; a sample match is not a guarantee for the entire dataset. + - With a Redis Sentinel coordination backend (`DISTRIBUTED_COORDINATION_CONFIG` using `RedisSentinelCache`), coordination commands now honor `CACHE_REDIS_DB` and `CACHE_REDIS_USER`. Previously they always ran on DB 0 and without the ACL user. diff --git a/docs/admin_docs/configuration/partition-filter-mapping.mdx b/docs/admin_docs/configuration/partition-filter-mapping.mdx index 1190a7e4d43e..52e35bb693ab 100644 --- a/docs/admin_docs/configuration/partition-filter-mapping.mdx +++ b/docs/admin_docs/configuration/partition-filter-mapping.mdx @@ -66,13 +66,78 @@ Expand the mapped column's row in *Column Settings* and set the **value transfor expression containing a `:value` placeholder, which stands for the filter value being mirrored. The **Transform preserves ordering** checkbox sits directly beneath it. -| Mapped column | Partition column | Transform | -|---|---|---| -| `event_time` (`TIMESTAMP`) | `dt_epoch` (`BIGINT`) | `unix_timestamp(:value)` | -| `country` (`VARCHAR`) | `region_key` (`VARCHAR`) | `lower(:value)` | +No engine default can infer how your pipeline buckets a numeric partition key. +Enter the expression that actually populates it, including units and timezone. -A filter of `event_time >= '2026-01-01'` then adds `dt_epoch >= 1767225600` to the query. -The added predicate is an ordinary `WHERE` clause and shows up in **View query**. +| Mapped column | Stored partition key | Transform | +|---|---|---| +| Epoch seconds (`BIGINT`, datetime format `epoch_s`) | Hour-start epoch seconds | `CAST(floor(:value / 3600) * 3600 AS BIGINT)` | +| Epoch seconds (`BIGINT`, datetime format `epoch_s`) | UTC day-start epoch seconds | `CAST(floor(:value / 86400) * 86400 AS BIGINT)` | +| UTC `TIMESTAMP`, with UTC epoch conversion in the database session | Hour-start epoch seconds | `CAST(floor(unix_timestamp(:value) / 3600) * 3600 AS BIGINT)` | +| UTC `TIMESTAMP`, with UTC epoch conversion in the database session | UTC day-start epoch seconds | `CAST(floor(unix_timestamp(:value) / 86400) * 86400 AS BIGINT)` | +| `country` (`VARCHAR`) | Lowercase `region_key` | `lower(:value)` | + +The timestamp examples use Hive/Impala syntax. Confirm conversion settings in both +query and transform-evaluation sessions; `unix_timestamp` can depend on session timezone. +The numeric epoch examples avoid that conversion. Use mathematical floor, including for +negative epochs. Millisecond inputs require conversion to seconds before these expressions; +local-calendar-day keys are not interchangeable with fixed UTC 86,400-second buckets. +A session in a non-UTC zone shifts these boundaries: under IST (UTC+05:30) the hourly +timestamp recipe lands on the wrong hour key, five or six hours earlier, so mirrored filters +miss the matching rows. The daily recipe puts early-morning rows on the previous day. +Set the evaluating session to UTC, or use the +numeric epoch recipes. + +Bare `unix_timestamp(:value)` is not suggested or mirrored for temporal-to-numeric +mappings on Hive/Impala and their engine-spec subclasses. It produces an instant, +not an hour/day boundary, and can silently exclude matching rows. Existing expressions +remain editable; replace them with the pipeline's actual partition expression. +On the same engines, a transform from a timestamp or text column onto a numeric key is +also evaluated at two instants 2,843 seconds apart within one hour. A text column is asked +in each common layout (`2001-01-01 10:00:00`, `2001-01-01T10:00:00`, +`2001-01-01T10:00:00Z`, and its own datetime format, if set), whether or not it is flagged +as a datetime. A datetime format whose output contains a quote or backslash the engine +would read differently is skipped rather than sent. If any answers are exactly that far apart in seconds, milliseconds, +microseconds or nanoseconds, the transform returns raw instants, however it is spelled: +`CAST(unix_timestamp(:value) AS BIGINT)`, `unix_timestamp(:value) + 0`, +`floor(unix_timestamp(:value))` and `utc_to_unix_micros(:value)` are all declined. A key +that really stores raw epoch instants is declined too; queries then scan every partition +but return correct rows. This check cannot catch a wrong bucket size or timezone, so still +check every transform with **Validate against data**. + +Dataset time-range bounds already pass through Superset's timezone/hour-offset adjustment +before mirroring. Do not convert those bounds from the dataset timezone a second time. +The added partition predicate remains visible in **View query**. + +### Validate against stored data + +The automatic preview reports **Expression evaluated — data not checked**. This means +Superset could evaluate the expression and build a predicate, not that it matches your data. + +For a physical dataset with a numeric epoch key, enter one to three **existing partition +keys** (whole numbers within the 64-bit range), then choose **Validate against data**. The +section is not shown for a text partition key, and a disabled button says what is missing. Superset reads at most 50 rows from each +selected partition and compares each transformed business value with that same row's +stored key. It applies your data-access permissions and row-level security. It does not +run a total-count query or infer sample partitions from the candidate expression. + +- **Matched sampled rows** reports the checked rows and covered partitions. This is + evidence about that sample, not proof for all rows, backfills, or future ingestion. +- **Partition mismatch found** shows the business value, stored key, and transform output. + An output found in another partition still fails the same-row comparison. +- **Validation inconclusive** names each selected key that had no checkable rows or whose + evaluation/query failed. NULL business values cannot establish a match. +- **Filters will not be mirrored** means the mapping itself refuses the transform, for + example one that answers raw epoch instants. No rows are sampled: rows on the hour + boundary would match it exactly and report a transform the mirror declines as matching. + +Choose partitions containing off-boundary business timestamps, such as 10:47:23, and repeat +across relevant dates and timezone transitions. The read is restricted to the selected keys; +a row limit does not guarantee a cheap scan within a large partition. Existing warehouse +query limits still apply. Changing the candidate clears the displayed validation. +Validation does not save a global verdict, change chart behavior, or override the bare-epoch +safety refusal. Before enabling a production mapping, compare chart row IDs and counts with +and without its partition predicates, including equality, IN, and partial-bucket ranges. ### Transform preserves ordering @@ -80,7 +145,7 @@ Range filters — including the Explore time range, the most important case — mirrored when you check **Transform preserves ordering**. Monotonicity is a property of the *transform*, not of the column's data type. -`unix_timestamp(:value)` preserves ordering. `hour(:value)`, +`CAST(floor(unix_timestamp(:value) / 3600) * 3600 AS BIGINT)` preserves ordering. `hour(:value)`, `date_format(:value, 'dd')` and `dayofweek(:value)` are all perfectly reasonable partition transforms on a `TIMESTAMP` column and none of them do: `hour('2026-01-01 23:00')` is greater than `hour('2026-01-02 01:00')` even though the first instant is earlier. Mirroring @@ -89,7 +154,7 @@ declare it rather than guessing. A *bucketing* transform preserves ordering and is the common case rather than an exception: `to_char(:value, 'YYYYMMDD')`, `date(:value)` and -`cast(unix_timestamp(:value) / 86400 as bigint)` each map a whole day onto one +`CAST(floor(unix_timestamp(:value) / 86400) * 86400 AS BIGINT)` each map a whole day onto one partition key, which is exactly what a daily-partitioned table wants. Check the box for these. @@ -105,7 +170,7 @@ says why. Any other engine declines too, until it is confirmed and declared. If your partition column is temporal and your transform returns text, the robust answer is to make the transform's return type match the column: `date(:value)` or `cast(... as date)` rather than `to_char(...)`. A *numeric* partition column — the -`unix_timestamp(:value)` / epoch shape — is unaffected by any of this. +epoch shape, such as `CAST(floor(unix_timestamp(:value) / 3600) * 3600 AS BIGINT)` — is unaffected by any of this. When the box is unchecked, `=` and `IN` filters still mirror; ranges do not. @@ -239,7 +304,7 @@ column. That substitution is only valid if, for every row in the table: partition_column = (mapped_column) ``` -**Superset cannot verify this.** It is a property of whatever ETL populates the partition +**Sample validation cannot prove this for the entire dataset.** It is a property of whatever ETL populates the partition column. If that job lags, backfills with different logic, or writes the partition key in a different timezone than the transform resolves, mirrored predicates silently drop real rows and charts show quietly wrong numbers. Confirm the invariant with whoever owns the pipeline @@ -280,12 +345,12 @@ accepts a comparison across those types: Postgres, Trino and BigQuery refuse it SQLite compares it as never equal rather than erroring, which would drop every row the filter keeps. So Superset declines the mirror instead. The preview panel names the column and the value it got, and the fix is to cast the result to the key's own type — -`cast(unix_timestamp(:value) as string)` for a text partition key. +`date_format(:value, 'yyyyMMddHH')` for a text partition key holding hours. Because the evaluation happens in a **different session** from the chart query, transforms that call non-deterministic functions are rejected when you save. That includes `now()`, `current_date`, `current_timestamp`, `rand()` and the zero-argument `unix_timestamp()`, which -means "now" on Hive and Impala. The one-argument `unix_timestamp(:value)` is fine. +means "now" on Hive and Impala. The one-argument function is deterministic only under consistent session settings; the bare expression is declined for temporal-to-numeric Hive/Impala-family mappings because it does not specify a bucket. Session-dependent behaviour that Superset cannot detect is still your responsibility: `unix_timestamp(:value)` is timezone-dependent on Hive and Impala, so if the evaluating @@ -314,6 +379,10 @@ refused when you save: PostgreSQL large-object writer. The transform is evaluated against the engine to check that it mirrors, and that evaluation is cached and repeated, so it has to be a read. +An ordinary comment that does not swallow `:value`, such as +`floor(unix_timestamp(:value) / 3600) * 3600 -- hourly`, is allowed. Superset removes it +before evaluating the transform. + Only the mapped column may carry a transform. Setting one on any other column through the API, or in an import bundle, has no effect: Superset drops it on write, because a value parked on a column the mapping does not mirror is invisible in the editor and would go live the moment the mapped diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 07af26d4a7c7..0fb99404ba1c 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -8537,6 +8537,18 @@ "minItems": 1, "type": "array" }, + "validation_partition_values": { + "default": null, + "description": "One to three existing integer partition keys for explicit data validation", + "items": { + "pattern": "^-?[0-9]{1,19}$", + "type": "string" + }, + "maxItems": 3, + "minItems": 1, + "nullable": true, + "type": "array" + }, "value_transform": { "description": "SQL expression containing a :value placeholder", "maxLength": 1024, @@ -25816,6 +25828,64 @@ "properties": { "result": { "properties": { + "data_validation": { + "properties": { + "checked_rows": { + "type": "integer" + }, + "examples": { + "items": { + "properties": { + "source_value": { + "type": "string" + }, + "stored_key": { + "type": "string" + }, + "transformed_value": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "inconclusive_keys": { + "description": "Partition keys whose rows could not be read, present when the status is inconclusive.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "mismatch_count": { + "type": "integer" + }, + "partitions_checked": { + "type": "integer" + }, + "partitions_requested": { + "type": "integer" + }, + "skipped_rows": { + "type": "integer" + }, + "status": { + "enum": [ + "not_checked", + "matched", + "mismatch", + "inconclusive", + "refused" + ], + "type": "string" + } + }, + "type": "object" + }, "emitted_predicate": { "type": "string" }, @@ -25823,8 +25893,9 @@ "type": "string" }, "reason": { - "description": "Why the mapping is not valid. Absent when it is. The editor branches on this to decide how to present the failure.", + "description": "Why predicate evaluation was declined. Absent on evaluation success. The editor branches on this to decide how to present the failure.", "enum": [ + "unbucketed_epoch", "unconfigured", "validation", "parse", @@ -25842,6 +25913,7 @@ "type": "string" }, "valid": { + "description": "Expression/predicate evaluation succeeded; not a guarantee of partition correspondence.", "type": "boolean" } }, diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionColumnFields.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionColumnFields.tsx index 0ca01471050d..3215bcca2c4e 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionColumnFields.tsx +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionColumnFields.tsx @@ -133,7 +133,13 @@ export default function PartitionColumnFields({ // mapped column's row already asks it. Deferring to that answer where there // is one is what keeps this banner and that panel from contradicting each // other. + const refusal = datasource.partition_filter_mapping?.safety_refusal; + const refusalApplies = + refusal?.mapped_column === mappedColumn && + refusal?.partition_column === datasource.partition_column && + refusal?.value_transform === liveTransform; const isActive = + !refusalApplies && mappingIsActive(datasource, allColumns) && previewMirrors !== false && !( @@ -254,9 +260,13 @@ export default function PartitionColumnFields({ )} - {t( - 'Filters on this column are mirrored onto the partition column.', - )} + {isActive + ? t( + 'Filters on this column are mirrored onto the partition column.', + ) + : t( + 'Filters on this column are mapped onto the partition column, but are not mirrored.', + )} {differentColumnTarget && ( <> {' '} @@ -306,15 +316,17 @@ export default function PartitionColumnFields({ type="warning" showIcon message={ - liveTransform - ? t( - 'The value transform on %(mapped)s is not mirroring filters, so queries will scan every partition.', - { mapped: mappedColumn }, - ) - : t( - 'No value transform is set on %(mapped)s, so nothing is mirrored yet and queries will scan every partition.', - { mapped: mappedColumn }, - ) + refusalApplies + ? refusal?.message + : liveTransform + ? t( + 'The value transform on %(mapped)s is not mirroring filters, so queries will scan every partition.', + { mapped: mappedColumn }, + ) + : t( + 'No value transform is set on %(mapped)s, so nothing is mirrored yet and queries will scan every partition.', + { mapped: mappedColumn }, + ) } /> )} diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionDataValidation.test.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionDataValidation.test.tsx new file mode 100644 index 000000000000..fa2e0aa9552e --- /dev/null +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionDataValidation.test.tsx @@ -0,0 +1,214 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import fetchMock from 'fetch-mock'; +import { + fireEvent, + render, + screen, + userEvent, + waitFor, +} from 'spec/helpers/testing-library'; +import PartitionDataValidation from './PartitionDataValidation'; + +const endpoint = 'glob:*/api/v1/dataset/1/partition_mapping/preview/'; +const props = { + datasetId: 1, + mappedColumn: 'event_time', + partitionColumn: 'dt_epoch', + valueTransform: 'unix_timestamp(:value)', + isMonotonic: true, + sampleValues: ['2026-01-15 10:47:23'], + operator: '>=', +}; +afterEach(() => fetchMock.clearHistory().removeRoutes()); + +test('validation is explicit and preserves integer keys as strings', async () => { + fetchMock.post(endpoint, { + result: { + valid: false, + data_validation: { + status: 'mismatch', + checked_rows: 1, + partitions_checked: 1, + examples: [ + { + source_value: '2026-01-15 10:47:23', + stored_key: '1768471200', + transformed_value: '1768474043', + }, + ], + }, + }, + }); + render(); + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '9007199254740993' }, + }); + expect(fetchMock.callHistory.called(endpoint)).toBe(false); + await userEvent.click( + screen.getByRole('button', { name: 'Validate against data' }), + ); + expect( + await screen.findByText('Partition mismatch found'), + ).toBeInTheDocument(); + expect( + screen.getByText(/stored key: 1768471200; transform: 1768474043/), + ).toBeInTheDocument(); + const call = fetchMock.callHistory.lastCall(endpoint); + expect( + JSON.parse(call?.options.body as string).validation_partition_values, + ).toEqual(['9007199254740993']); +}); + +test('changing a candidate clears sampled success', async () => { + fetchMock.post(endpoint, { + result: { + valid: true, + data_validation: { + status: 'matched', + checked_rows: 50, + partitions_checked: 1, + }, + }, + }); + const { rerender } = render(); + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '1768471200' }, + }); + await userEvent.click( + screen.getByRole('button', { name: 'Validate against data' }), + ); + expect( + await screen.findByText('Matched 50 sampled rows across 1 partition'), + ).toBeInTheDocument(); + rerender( + , + ); + expect(screen.queryByText(/Matched 50/)).not.toBeInTheDocument(); +}); + +test('late validation cannot describe a changed candidate', async () => { + let resolveResponse: (value: object) => void = () => {}; + fetchMock.post( + endpoint, + () => + new Promise(resolve => { + resolveResponse = resolve; + }), + ); + const { rerender } = render(); + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '1768471200' }, + }); + await userEvent.click( + screen.getByRole('button', { name: 'Validate against data' }), + ); + rerender(); + resolveResponse({ + result: { + valid: true, + data_validation: { + status: 'matched', + checked_rows: 1, + partitions_checked: 1, + }, + }, + }); + await waitFor(() => + expect( + screen.getByRole('button', { name: 'Validate against data' }), + ).toBeEnabled(), + ); + expect(screen.queryByText(/Matched 1/)).not.toBeInTheDocument(); +}); + +test('empty, invalid, and excessive partition selections cannot run', () => { + render(); + for (const value of [ + '', + 'abc', + '1,2,3,4', + '12345678901234567890', + '9223372036854775808', + ]) { + fireEvent.change(screen.getByRole('textbox'), { target: { value } }); + expect( + screen.getByRole('button', { name: 'Validate against data' }), + ).toBeDisabled(); + expect( + screen.getByTestId('partition-validation-disabled'), + ).toBeInTheDocument(); + } + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '-9223372036854775808, 9223372036854775807' }, + }); + expect( + screen.getByRole('button', { name: 'Validate against data' }), + ).toBeEnabled(); + expect( + screen.queryByTestId('partition-validation-disabled'), + ).not.toBeInTheDocument(); +}); + +test('a disabled button says why', () => { + render( + , + ); + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '1768471200' }, + }); + expect( + screen.getByRole('button', { name: 'Validate against data' }), + ).toBeDisabled(); + expect( + screen.getByText( + 'Correct the value transform before validating against data.', + ), + ).toBeInTheDocument(); +}); + +test('a refused transform is never reported as matched', async () => { + fetchMock.post(endpoint, { + result: { + valid: false, + data_validation: { + status: 'refused', + message: 'Filters will not be mirrored.', + }, + }, + }); + render(); + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '1768471200' }, + }); + await userEvent.click( + screen.getByRole('button', { name: 'Validate against data' }), + ); + expect( + await screen.findByText('Filters will not be mirrored'), + ).toBeInTheDocument(); + expect(screen.queryByText(/Matched/)).not.toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionDataValidation.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionDataValidation.tsx new file mode 100644 index 000000000000..8c7009a964fa --- /dev/null +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionDataValidation.tsx @@ -0,0 +1,201 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useEffect, useRef, useState } from 'react'; +import { SupersetClient, getClientErrorObject } from '@superset-ui/core'; +import { t, tn } from '@apache-superset/core/translation'; +import { Alert } from '@apache-superset/core/components'; +import { Button, Flex, Input, Typography } from '@superset-ui/core/components'; +import type { + PartitionDataValidationResult, + PartitionMappingPreview, +} from './types'; + +interface Props { + datasetId?: number; + mappedColumn: string; + partitionColumn: string; + valueTransform: string; + isMonotonic: boolean; + sampleValues: string[]; + operator: string; + /** Why the mapping cannot be validated yet, if it cannot. */ + disabledReason?: string; +} + +// Spelled out: the build lowers `**` to `Math.pow`, which throws on a BigInt +// as the module loads. +const INT64_MIN = BigInt('-9223372036854775808'); +const INT64_MAX = BigInt('9223372036854775807'); + +/** A 64-bit integer written as plain digits, as the API accepts it. */ +function isPartitionKey(key: string): boolean { + return ( + /^-?[0-9]{1,19}$/.test(key) && + BigInt(key) >= INT64_MIN && + BigInt(key) <= INT64_MAX + ); +} + +/** Explicitly check actual row pairs; never scan a table while typing a transform. */ +export default function PartitionDataValidation(props: Props) { + const [keys, setKeys] = useState(''); + const [result, setResult] = useState(); + const [loading, setLoading] = useState(false); + const requestId = useRef(0); + const identity = JSON.stringify([props, keys]); + const identityRef = useRef(identity); + identityRef.current = identity; + const [resultIdentity, setResultIdentity] = useState(''); + useEffect(() => { + requestId.current += 1; + setLoading(false); + setResult(undefined); + return () => { + requestId.current += 1; + }; + }, [identity]); + const values = keys.split(',').map(key => key.trim()); + const keysValid = values.length <= 3 && values.every(isPartitionKey); + const disabledReason = + props.disabledReason ?? + (!props.datasetId + ? t('Save the dataset before validating against data.') + : !keys.trim() + ? t('Enter one to three existing partition keys.') + : !keysValid + ? t( + 'Partition keys must be whole numbers within the 64-bit range, up to three.', + ) + : undefined); + + const validate = async () => { + requestId.current += 1; + const currentRequest = requestId.current; + const submittedIdentity = identity; + setLoading(true); + setResult(undefined); + const current = () => + currentRequest === requestId.current && + submittedIdentity === identityRef.current; + try { + const { json } = await SupersetClient.post({ + endpoint: `/api/v1/dataset/${props.datasetId}/partition_mapping/preview/`, + jsonPayload: { + mapped_column: props.mappedColumn, + partition_column: props.partitionColumn, + value_transform: props.valueTransform, + is_monotonic: props.isMonotonic, + sample_values: props.sampleValues, + operator: props.operator, + validation_partition_values: values, + }, + }); + if (current()) { + setResult((json.result as PartitionMappingPreview).data_validation); + setResultIdentity(submittedIdentity); + } + } catch (error) { + const clientError = await getClientErrorObject(error); + if (current()) { + setResult({ + status: 'inconclusive', + message: clientError.error || t('Data validation failed.'), + }); + setResultIdentity(submittedIdentity); + } + } finally { + if (current()) setLoading(false); + } + }; + const visible = resultIdentity === identity ? result : undefined; + return ( + + + {t('Existing partition keys (up to three, comma separated)')} + + setKeys(event.target.value)} + placeholder={t('Enter stored epoch partition keys, not timestamps')} + /> + + {t( + 'Checks up to 50 rows per selected partition. Sampling cannot verify every row or future ingestion.', + )} + + + {disabledReason && ( + + {disabledReason} + + )} + {visible && ( + + {visible.message} + {visible.examples?.map((example, index) => ( + + {t( + 'Source: %(source)s; stored key: %(stored)s; transform: %(actual)s', + { + source: example.source_value, + stored: example.stored_key, + actual: example.transformed_value ?? 'NULL', + }, + )} + + ))} + + } + /> + )} + + ); +} diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionFilterMapping.test.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionFilterMapping.test.tsx index 18735c4308f3..5543f144c918 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionFilterMapping.test.tsx +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionFilterMapping.test.tsx @@ -449,6 +449,102 @@ test('a failed preview shows the error instead of a predicate', async () => { ).not.toBeInTheDocument(); }); +// Not only a safety refusal: an operator or engine the mirror cannot use +// withdraws it too, and the copy must not call either one a refusal. +test.each(['unbucketed_epoch', 'operator'])( + 'a transform the preview rejects for %s is not described as mirrored', + async reason => { + fetchMock.post(PREVIEW_URL, { + result: { + valid: false, + reason, + error: 'The preview rejected this transform.', + }, + }); + + render( + , + ); + + expect( + await screen.findByText('Filters will not be mirrored'), + ).toBeInTheDocument(); + expect( + screen.queryByText( + 'Filters on this column are mirrored onto the partition field.', + ), + ).not.toBeInTheDocument(); + expect( + screen.getByText(/with this transform they are not mirrored/), + ).toBeInTheDocument(); + expect(screen.queryByText(/refused/)).not.toBeInTheDocument(); + }, +); + +test('the column banner does not call a refused mapping mirrored', () => { + render( + , + ); + + expect( + screen.queryByText( + 'Filters on this column are mirrored onto the partition column.', + ), + ).not.toBeInTheDocument(); +}); + +test('data validation is offered only for a numeric partition key', () => { + const renderWith = (type: string) => + render( + , + ); + + const { unmount } = renderWith('VARCHAR'); + expect( + screen.queryByRole('button', { name: 'Validate against data' }), + ).not.toBeInTheDocument(); + unmount(); + renderWith('BIGINT'); + expect( + screen.getByRole('button', { name: 'Validate against data' }), + ).toBeInTheDocument(); +}); + test('only the mapped row publishes a preview verdict', async () => { // Every expanded column renders this component and they all report to the // same `onPreviewVerdict`, but only the mapped one previews anything @@ -1455,3 +1551,62 @@ test('the unsaved notice is not reported as a failed verdict', async () => { ).toBeInTheDocument(); expect(onPreviewVerdict).not.toHaveBeenCalledWith(false); }); + +test('saved safety refusal is shown before expanding or previewing the mapped row', () => { + const datasource = { + main_dttm_col: 'event_time', + partition_column: 'dt_epoch', + partition_filter_mapping: { + safety_refusal: { + reason: 'unbucketed_epoch' as const, + mapped_column: 'event_time', + partition_column: 'dt_epoch', + value_transform: 'unix_timestamp(:value)', + message: 'Raw epoch conversion does not specify the partition bucket.', + }, + }, + }; + const common = { + columns: MAPPED_COLUMNS, + onPartitionColumnChange: jest.fn(), + onNavigateToColumn: jest.fn(), + }; + const { rerender } = render( + , + ); + expect( + screen.getByText( + 'Raw epoch conversion does not specify the partition bucket.', + ), + ).toBeInTheDocument(); + expect( + screen.queryByText(/will automatically apply/), + ).not.toBeInTheDocument(); + expect(fetchMock.callHistory.called()).toBe(false); + const corrected = MAPPED_COLUMNS.map(column => + column.column_name === 'event_time' + ? { + ...column, + partition_value_transform: 'floor(unix_timestamp(:value)/3600)*3600', + } + : column, + ); + rerender( + , + ); + expect( + screen.queryByText( + 'Raw epoch conversion does not specify the partition bucket.', + ), + ).not.toBeInTheDocument(); + expect(screen.getByText(/will automatically apply/)).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionMappingSection.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionMappingSection.tsx index c44b8c2887b2..06d20acb05c7 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionMappingSection.tsx +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/PartitionMappingSection.tsx @@ -30,10 +30,12 @@ import { Loading, Typography, } from '@superset-ui/core/components'; +import PartitionDataValidation from './PartitionDataValidation'; import { useDebouncedCommit } from './useDebouncedCommit'; import { usePartitionMappingPreview } from './usePartitionMappingPreview'; import { defaultTransformFor, + isNumericType, partitionRowState, previewOperatorFor, sampleValuesFor, @@ -181,6 +183,18 @@ export default function PartitionMappingSection({ // message appears as it is typed. The same check gates Save from // `DatasourceEditor.validate`, reading the committed record instead. const transformErrors = valueTransformIssues(item, transform); + // Any verdict the preview reported against mirroring -- a safety refusal or + // a transform the engine, operator or type cannot mirror -- so the copy above + // it stops promising the mirror the alert below withdraws. + const notMirrored = Boolean( + !loading && preview && !preview.valid && preview.reason !== 'unsaved', + ); + // Data validation compares integer keys. On a text key it can never be + // enabled, so it is not offered; an unknown type keeps it, as before. + const partitionType = datasource.columns?.find( + col => col.column_name === datasource.partition_column, + )?.type; + const validatesData = !partitionType || isNumericType(partitionType); return ( - {isTemporal - ? t('Filters on this column are mirrored onto the partition field.') - : t( - 'This column holds the mapping instead of the default datetime column.', - )} + {notMirrored + ? t( + 'Filters on this column are mapped onto the partition field, but with this transform they are not mirrored.', + ) + : isTemporal + ? t('Filters on this column are mirrored onto the partition field.') + : t( + 'This column holds the mapping instead of the default datetime column.', + )} @@ -332,7 +350,9 @@ export default function PartitionMappingSection({ iconColor={theme.colorSuccess} iconSize="s" /> - {t('Valid')} + + {t('Expression evaluated — data not checked')} + @@ -350,6 +370,28 @@ export default function PartitionMappingSection({ )} + {validatesData && ( + 0 + ? t( + 'Correct the value transform before validating against data.', + ) + : preview?.reason === 'unsaved' + ? t('Save the dataset before validating against data.') + : undefined + } + /> + )} {/* Only one of preview and error is ever visible. */} {!loading && preview && !preview.valid && ( /partition_mapping/preview/`. */ export interface PartitionMappingPreview { + /** Expression/predicate evaluation succeeded; not proof of partition correctness. */ valid: boolean; + data_validation?: PartitionDataValidationResult; sample_input?: string; emitted_predicate?: string; error?: string; /** Why it is invalid, so the panel can headline it correctly. */ reason?: + | 'unbucketed_epoch' | 'parse' | 'validation' | 'operator' @@ -113,3 +123,20 @@ export interface PartitionMappingIssue { */ blocking?: boolean; } + +export interface PartitionDataValidationResult { + status: 'not_checked' | 'matched' | 'mismatch' | 'inconclusive' | 'refused'; + checked_rows?: number; + skipped_rows?: number; + mismatch_count?: number; + partitions_checked?: number; + partitions_requested?: number; + message?: string; + /** The keys with no checkable rows, or whose query or transform failed. */ + inconclusive_keys?: string[]; + examples?: { + source_value: string; + stored_key: string; + transformed_value: string | null; + }[]; +} diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/utils.ts b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/utils.ts index 9dbafc84aa7c..4256ab2b6c73 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/utils.ts +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/PartitionFilterMapping/utils.ts @@ -340,6 +340,11 @@ export function defaultTransformFor( const NUMERIC_TYPE_RE = /INT|NUMERIC|DECIMAL|DOUBLE|FLOAT|REAL|NUMBER|BIGNUMERIC/i; +/** Whether a declared column type holds numbers rather than strings. */ +export function isNumericType(type: string | null | undefined): boolean { + return Boolean(type && NUMERIC_TYPE_RE.test(type)); +} + /** * The sample instant, 2026-01-15 10:47:23 UTC, in each epoch representation. * diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 51dd4dd75ac5..4f95f4d21129 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -74,12 +74,14 @@ from superset.common.db_query_status import QueryStatus from superset.connectors.sqla.partition_mapping import ( declares_array_type, + epoch_mapping_refusal, equality_mirrors_safely, FEATURE_FLAG as PARTITION_FILTER_MAPPING_FLAG, has_active_advanced_data_type, is_transform_active, known_mirror_verdict, mirrorable_operators, + raw_instant_refusal, resolve_partition_mapping, value_round_trips, ) @@ -2312,8 +2314,14 @@ def partition_filter_mapping_summary(self) -> dict[str, Any] | None: mapped_column_name = self.partition_mapped_column or self.main_dttm_col mapped_column = columns_by_name.get(mapped_column_name or "") transform = mapped_column.partition_value_transform if mapped_column else None + safety_refusal = epoch_mapping_refusal( + self, mapped_column_name, self.partition_column, transform + ) or raw_instant_refusal( + self, mapped_column_name, self.partition_column, transform, probe=False + ) active = bool( - self.partition_column in columns_by_name + not safety_refusal + and self.partition_column in columns_by_name and not declares_array_type(columns_by_name[self.partition_column].type) and mapped_column is not None and mapped_column_name != self.partition_column @@ -2327,6 +2335,7 @@ def partition_filter_mapping_summary(self) -> dict[str, Any] | None: "partition_column": self.partition_column, "mapped_column": mapped_column_name, "active": active, + "safety_refusal": safety_refusal, # Whether the last probe of this transform produced a mirror. # `active` is the save path's own verdict and deliberately stays # that -- a parse, which a misspelled function clears happily -- so diff --git a/superset/connectors/sqla/partition_mapping.py b/superset/connectors/sqla/partition_mapping.py index 5f51415b694e..245942c937f0 100644 --- a/superset/connectors/sqla/partition_mapping.py +++ b/superset/connectors/sqla/partition_mapping.py @@ -185,6 +185,7 @@ from collections.abc import Mapping, Sequence from dataclasses import dataclass from datetime import date, datetime, time, timedelta +from decimal import Decimal from functools import lru_cache from typing import Any, cast, TYPE_CHECKING @@ -204,7 +205,7 @@ SupersetSecurityException, ) from superset.extensions import cache_manager, feature_flag_manager -from superset.sql.parse import SQLScript, SQLStatement +from superset.sql.parse import SQLScript, SQLStatement, strip_comments from superset.superset_typing import FilterValues from superset.utils import core as utils, json from superset.utils.core import FilterOperator, get_username @@ -1121,6 +1122,303 @@ def _find_niladic_calls(statement: SQLStatement) -> set[str]: return NON_DETERMINISTIC_WHEN_NILADIC & statement.get_niladic_functions() +def epoch_mapping_refusal( + datasource: SqlaTable, + mapped_column: str | None, + partition_column: str | None, + transform: str | None, +) -> dict[str, str] | None: + """Decline the former epoch default, whose bucket size is unspecified.""" + from superset.db_engine_specs.hive import HiveEngineSpec + from superset.db_engine_specs.impala import ImpalaEngineSpec + + columns = {col.column_name: col for col in datasource.columns} + source = columns.get(mapped_column) + target = columns.get(partition_column) + if ( + not transform + or source is None + or target is None + or not issubclass(datasource.db_engine_spec, (HiveEngineSpec, ImpalaEngineSpec)) + or _native_generic_type(source) != utils.GenericDataType.TEMPORAL + or _native_generic_type(target) != utils.GenericDataType.NUMERIC + ): + return None + engine = datasource.database.backend + candidate = _parse_skeleton(transform, engine) + former_default = _parse_skeleton("unix_timestamp(:value)", engine) + if ( + candidate is None + or former_default is None + or not candidate.has_same_projection(former_default) + ): + return None + return { + "reason": "unbucketed_epoch", + "mapped_column": str(mapped_column), + "partition_column": str(partition_column), + "value_transform": transform, + "message": _unbucketed_epoch_message(), + } + + +def _unbucketed_epoch_message() -> str: + return str( + _( + "Raw epoch conversion does not specify the partition bucket. " + "Use the expression that populates the partition key, including " + "its hour or day rounding and timezone. Filters will not be mirrored." + ) + ) + + +#: Two instants 2843 seconds apart within one hour, probed for every +#: temporal-to-numeric mirror. A transform that keeps that distance is answering +#: raw epoch instants, not an hour or day key, whatever its spelling -- +#: `CAST(unix_timestamp(:value) AS BIGINT)`, `+ 0`, `floor(unix_timestamp(:value))`, +#: Impala's `utc_to_unix_micros(:value)`. Mirroring raw instants onto bucketed +#: keys drops rows; a Hive/Impala partition per second is not a layout a +#: metastore survives, so declining costs pruning, never correctness. +_EPOCH_CANARIES = (datetime(2001, 1, 1, 10, 0, 0), datetime(2001, 1, 1, 10, 47, 23)) +#: That distance in seconds, milliseconds, microseconds and nanoseconds. +_RAW_INSTANT_STEPS = {2843 * 1000**power for power in range(4)} + + +def _epoch_canaries( + datasource: SqlaTable, + mapped_column: str, + partition_column: TableColumn, +) -> list[Any]: + """ + The canary inputs for this mapping, empty when the check does not apply. + + A flat list of pairs, one pair per spelling of the two instants. + """ + from superset.db_engine_specs.hive import HiveEngineSpec + from superset.db_engine_specs.impala import ImpalaEngineSpec + + # Elsewhere a raw epoch column is an ordinary index to prune on. + if not issubclass(datasource.db_engine_spec, (HiveEngineSpec, ImpalaEngineSpec)): + return [] + source = next( + (col for col in datasource.columns if col.column_name == mapped_column), + None, + ) + if ( + source is None + or _native_generic_type(partition_column) != utils.GenericDataType.NUMERIC + ): + return [] + source_type = _native_generic_type(source) + if source_type == utils.GenericDataType.TEMPORAL: + return list(_EPOCH_CANARIES) + if source_type == utils.GenericDataType.STRING: + return _text_epoch_canaries(source, datasource.database) + return [] + + +def _text_epoch_canaries(source: TableColumn, database: Database) -> list[str]: + """ + The canaries as the text a timestamp column may hold, one pair per form. + + Whether or not the column is flagged temporal: ``is_dttm`` defaults to + false, and a filter binds the text the author typed either way. A transform + parsing one layout answers ``NULL`` for the others, so each common layout + gets a pair, plus the column's own `python_date_format`. That format is the + dataset owner's text, so a pair is asked only when it round-trips as a + literal, the same gate every filter value passes before it reaches a probe. + """ + formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%SZ"] + date_format = source.python_date_format + if date_format and date_format not in ("epoch_s", "epoch_ms"): + formats.append(date_format) + pairs = utils.remove_duplicates( + tuple(canary.strftime(layout) for canary in _EPOCH_CANARIES) + for layout in formats + ) + return [ + text + for pair in pairs + if all(value_round_trips(database, text) for text in pair) + for text in pair + ] + + +def _canary_answers( + datasource: SqlaTable, + mapped_column: str, + partition_column: TableColumn, + transform: str, +) -> list[Any] | None: + """ + The transform's answers at the canaries, or ``None`` when not applicable. + + A probe of its own rather than extra values in the chart's batch, so the + filter values' probe and its cache entry are untouched; the canaries are + constant, so after the first chart this is a cache hit. Calls `_probe` + rather than `_run_probe` so a canary the engine rejects records no verdict + about a transform that evaluates the filter values perfectly well. + """ + canaries = _epoch_canaries(datasource, mapped_column, partition_column) + if not canaries: + return None + database = datasource.database + key = _probe_cache_key( + database, + datasource.catalog, + datasource.schema, + transform, + canaries, + ) + answers = _cache_get(key) + if answers is None: + answers = _probe_canaries(datasource, transform, canaries) + # A failure is not cached: a transient blip must not switch the check + # off for the whole cache timeout. + if answers is not None: + _cache_set(key, answers) + return answers + + +def raw_instant_refusal( + datasource: SqlaTable, + mapped_column: str | None, + partition_column: str | None, + transform: str | None, + *, + probe: bool, +) -> dict[str, str] | None: + """ + The refusal of a transform that answers raw epoch instants, if it does. + + With ``probe`` false only the canary cache is read, so the dataset API can + report a transform the engine was seen to refuse without a round trip per + request -- ``None`` on a cold cache, as ``evaluable`` is. Data validation + probes, so a sample of rows on the hour boundary cannot report a transform + the mirror declines as matching. + """ + target = next( + (col for col in datasource.columns if col.column_name == partition_column), + None, + ) + if not transform or not mapped_column or target is None: + return None + if probe: + answers = _canary_answers(datasource, mapped_column, target, transform) + else: + canaries = _epoch_canaries(datasource, mapped_column, target) + answers = ( + _cache_get( + _probe_cache_key( + datasource.database, + datasource.catalog, + datasource.schema, + transform, + canaries, + ) + ) + if canaries + else None + ) + if not _answers_raw_instants(answers): + return None + return { + "reason": "unbucketed_epoch", + "mapped_column": mapped_column, + "partition_column": str(partition_column), + "value_transform": transform, + "message": _unbucketed_epoch_message(), + } + + +def _probe_canaries( + datasource: SqlaTable, transform: str, canaries: list[Any] +) -> list[Any] | None: + """ + Probe every canary pair at once, falling back to a probe per pair. + + An engine that raises on a text layout its transform cannot parse, rather + than answering ``NULL``, fails the whole batch; the other layouts must still + be asked. A pair that fails alone answers ``None`` twice, and ``None`` + overall only when every pair failed. + """ + database = datasource.database + answers = _probe( + database, datasource.catalog, datasource.schema, transform, canaries + ) + if answers is not None or len(canaries) <= len(_EPOCH_CANARIES): + return answers + step = len(_EPOCH_CANARIES) + per_pair = [ + _probe( + database, + datasource.catalog, + datasource.schema, + transform, + canaries[start : start + step], + ) + for start in range(0, len(canaries), step) + ] + if all(pair is None for pair in per_pair): + return None + return [answer for pair in per_pair for answer in (pair or [None] * step)] + + +def _declined_as_raw_instants( + datasource: SqlaTable, + mapping: PartitionMapping, + partition_column: TableColumn, + epoch_errors: list[str] | None, +) -> bool: + """Whether to decline because the transform answers raw epoch instants.""" + if not _answers_raw_instants( + _canary_answers( + datasource, + mapping.mapped_column, + partition_column, + mapping.value_transform, + ) + ): + return False + logger.warning( + "Partition transform answers raw epoch instants for a numeric key; " + "queries will not prune" + ) + # Every probe of this transform re-records it, cache hit or not, so the + # editor and Explore stop promising pruning the query does not carry. + record_mirror_verdict( + datasource.database, + datasource.catalog, + datasource.schema, + mapping.value_transform, + mirrors=False, + ) + if epoch_errors is not None: + epoch_errors.append(_unbucketed_epoch_message()) + return True + + +def _answers_raw_instants(answers: list[Any] | None) -> bool: + """Whether any pair of canary answers sits exactly as far apart as its inputs.""" + step = len(_EPOCH_CANARIES) + if not answers or len(answers) % step: + return False + return any( + _pair_is_raw_instants(answers[start : start + step]) + for start in range(0, len(answers), step) + ) + + +def _pair_is_raw_instants(pair: list[Any]) -> bool: + low, high = (_to_python_scalar(answer) for answer in pair) + if any( + isinstance(answer, bool) or not isinstance(answer, (int, float, Decimal)) + for answer in (low, high) + ): + return False + return high - low in _RAW_INSTANT_STEPS + + def resolve_partition_mapping(datasource: SqlaTable) -> PartitionMapping | None: """ Resolve the dataset's mapping, or ``None`` when nothing may be mirrored. @@ -1179,6 +1477,11 @@ def resolve_partition_mapping(datasource: SqlaTable) -> PartitionMapping | None: # about does not exist and mirroring would apply the wrong values. return None + if epoch_mapping_refusal( + datasource, mapped_column_name, partition_column, transform + ): + return None + return PartitionMapping( partition_column=str(partition_column), mapped_column=str(mapped_column_name), @@ -1824,6 +2127,7 @@ def _evaluate_with_text_forms( values: list[Any], errors: list[str] | None, text_answers: list[tuple[int, Any]], + fresh: bool = False, ) -> tuple[bool, list[Any] | None]: """ `evaluate_transform` with each temporal value also asked as text -- see its @@ -1843,6 +2147,7 @@ def _evaluate_with_text_forms( transform, values + [_as_filter_text(values[index]) for index in indexes], errors=errors, + fresh=fresh, ) if combined is not None: text_answers.extend(zip(indexes, combined[len(values) :], strict=False)) @@ -1854,7 +2159,9 @@ def _evaluate_with_text_forms( # range. The typed values are asked alone, a second round trip only # on this path, and `_declined_for_reading_by_type` checks what they # answer against the instants they were asked about instead. - typed = evaluate_transform(database, catalog, schema, transform, values) + typed = evaluate_transform( + database, catalog, schema, transform, values, fresh=fresh + ) if typed is not None: text_answers.extend((index, TEXT_REFUSED) for index in indexes) return True, typed @@ -1870,6 +2177,7 @@ def evaluate_transform( *, errors: list[str] | None = None, text_answers: list[tuple[int, Any]] | None = None, + fresh: bool = False, ) -> list[Any] | None: """ Evaluate ``transform`` against the engine once per distinct value. @@ -1899,7 +2207,7 @@ def evaluate_transform( if text_answers is not None: answered, result = _evaluate_with_text_forms( - database, catalog, schema, transform, values, errors, text_answers + database, catalog, schema, transform, values, errors, text_answers, fresh ) if answered: return result @@ -1939,6 +2247,11 @@ def evaluate_transform( ) return None + if fresh: + return _fresh_probe_values( + database, catalog, schema, transform, distinct, values, errors + ) + cache_key = _probe_cache_key(database, catalog, schema, transform, distinct) cached = _cache_get(cache_key) if cached is None: @@ -1957,6 +2270,23 @@ def evaluate_transform( return [evaluated[_hashable(value)] for value in values] +def _fresh_probe_values( + database: Database, + catalog: str | None, + schema: str | None, + transform: str, + distinct: list[Any], + values: list[Any], + errors: list[str] | None, +) -> list[Any] | None: + """Evaluate diagnostic samples without reading or recording cached verdicts.""" + result = _probe(database, catalog, schema, transform, distinct, errors=errors) + if result is None: + return None + answers = dict(zip((_hashable(value) for value in distinct), result, strict=True)) + return [answers[_hashable(value)] for value in values] + + def _run_probe( database: Database, catalog: str | None, @@ -2036,10 +2366,24 @@ def _probe( ) return None + # After the gate above, which reads the transform as stored -- a `:value` + # inside a comment is refused there, not silently dropped here. A comment + # the owner wrote is inert, but `probe_sql_is_evaluable` refuses any comment + # in the compiled probe, since that is how a bound value hides what + # follows it; so it is removed before compiling, and a comment the gate + # still finds can only have come from a value. + stripped = strip_comments(transform, database.backend) + if stripped is None: + logger.warning( + "Partition transform could not be read with its comments removed; " + "queries will not prune" + ) + return None + try: from_suffix = database.db_engine_spec.select_without_from_suffix sql = build_probe_sql( - transform, + stripped, distinct, _dialect_for(database), from_suffix, @@ -2875,6 +3219,15 @@ def preview_partition_mapping( # pylint: disable=too-many-return-statements ): return {"valid": False, "reason": "validation", "error": reason} + if refusal := epoch_mapping_refusal( + datasource, mapped_column, partition_column, value_transform + ): + return { + "valid": False, + "reason": refusal["reason"], + "error": refusal["message"], + } + mapping = PartitionMapping( partition_column=str(partition_column), mapped_column=mapped_column, @@ -3081,6 +3434,7 @@ def _preview_probe_value( type_errors: list[str] = [] escaping_errors: list[str] = [] shape_errors: list[str] = [] + epoch_errors: list[str] = [] try: predicates = build_mirrored_predicates( datasource, @@ -3090,6 +3444,7 @@ def _preview_probe_value( type_errors=type_errors, escaping_errors=escaping_errors, shape_errors=shape_errors, + epoch_errors=epoch_errors, ) except Exception: # pylint: disable=broad-except # The chart path fails open on the same call; the preview reports it @@ -3101,6 +3456,13 @@ def _preview_probe_value( "sample_input": sample_input, "error": _("The transform could not be evaluated against the database."), } + if epoch_errors: + return { + "valid": False, + "reason": "unbucketed_epoch", + "sample_input": sample_input, + "error": epoch_errors[0], + } if shape_errors: return { "valid": False, @@ -3413,6 +3775,7 @@ def build_mirrored_predicates( type_errors: list[str] | None = None, escaping_errors: list[str] | None = None, shape_errors: list[str] | None = None, + epoch_errors: list[str] | None = None, ) -> list[ColumnElement[Any]]: """ Turn collected ``(operator, value)`` mirror requests into predicates. @@ -3440,6 +3803,8 @@ def build_mirrored_predicates( this database cannot be asked about" is a third problem, and the only one of the three whose fix is to stop the transform producing the character. + :param epoch_errors: optional sink for a transform answering raw epoch + instants onto a numeric key -- see `_EPOCH_CANARIES`. """ if not requests: return [] @@ -3480,7 +3845,9 @@ def build_mirrored_predicates( if not _bounds_are_ordered(evaluated, spans): return [] - if _declined_for_reading_by_type(flat, evaluated, text_answers, shape_errors): + if _declined_for_reading_by_type( + flat, evaluated, text_answers, shape_errors + ) or _declined_as_raw_instants(datasource, mapping, partition_column, epoch_errors): return [] # Declining here is the last chance to decline at all: once these diff --git a/superset/connectors/sqla/partition_validation.py b/superset/connectors/sqla/partition_validation.py new file mode 100644 index 000000000000..b286a73aeeec --- /dev/null +++ b/superset/connectors/sqla/partition_validation.py @@ -0,0 +1,226 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Bounded, explicit checks of partition transforms against stored row pairs.""" + +from __future__ import annotations + +from typing import Any, cast, TYPE_CHECKING + +import pandas as pd +from flask_babel import gettext as _ + +from superset.common.query_context_factory import QueryContextFactory +from superset.connectors.sqla.partition_mapping import ( + _declined_for_reading_by_type, + _native_generic_type, + _to_python_scalar, + epoch_mapping_refusal, + evaluate_transform, + has_active_advanced_data_type, + raw_instant_refusal, + stored_expression_error, + validate_partition_mapping, +) +from superset.utils.core import GenericDataType, QueryStatus, remove_duplicates + +if TYPE_CHECKING: + from superset.connectors.sqla.models import SqlaTable + + +def validate_partition_data( + datasource: SqlaTable, + *, + mapped_column: str, + partition_column: str | None, + value_transform: str | None, + partition_values: list[str], +) -> dict[str, Any]: + """Compare fresh transform outputs with keys from the same authorized rows. + + Partition keys are supplied independently of the transform. Filtering by + its output could exclude precisely the rows that demonstrate a mismatch. + Sampling is diagnostic, never a persistent or universal mirror verdict. + """ + result: dict[str, Any] = { + "status": "inconclusive", + "checked_rows": 0, + "skipped_rows": 0, + "mismatch_count": 0, + "partitions_checked": 0, + "partitions_requested": len(set(partition_values)), + "examples": [], + } + target_name = partition_column or datasource.partition_column + if declined := _declined_before_sampling( + datasource, mapped_column, target_name, value_transform + ): + result["status"], result["message"] = declined + return result + # Declined above when empty. + transform = cast(str, value_transform) + + keys = remove_duplicates(int(value) for value in partition_values) + result["partitions_requested"] = len(keys) + context = QueryContextFactory().create( + datasource={"type": datasource.type, "id": datasource.id}, + queries=[ + { + "columns": [mapped_column, target_name], + "filters": [{"col": target_name, "op": "==", "val": key}], + "row_limit": 50, + "time_range": "No filter", + "orderby": [], + } + for key in keys + ], + force=True, + ) + # Editorship alone does not authorize reading the underlying data. + context.raise_for_access() + failed_keys: list[int] = [] + for key, query in zip(keys, context.queries, strict=True): + try: + # The raw datasource result retains exact integers and stored + # timestamps, before display offsets/JSON datetime normalization. + # Query assembly retains RLS; no chart cache or COUNT(*) is used. + sample = datasource.query(query.to_dict()) + if sample.status != QueryStatus.SUCCESS: + failed_keys.append(key) + continue + pairs = [ + (_to_python_scalar(value), _to_python_scalar(key)) + for value, key in sample.df[[mapped_column, target_name]] + .head(50) + .itertuples(index=False, name=None) + ] + if _check_row_pairs(datasource, transform, pairs, result): + result["partitions_checked"] += 1 + else: + failed_keys.append(key) + except Exception: # pylint: disable=broad-except + # Never turn an engine failure or incomplete sample into success. + failed_keys.append(key) + if result["mismatch_count"]: + result["status"] = "mismatch" + result["message"] = _("The transform does not match the stored partition keys.") + elif not failed_keys and result["partitions_checked"] == len(keys): + result["status"] = "matched" + result["message"] = _( + "The transform matched the sampled rows, not the entire dataset." + ) + else: + result["inconclusive_keys"] = [str(key) for key in failed_keys] + result["message"] = _( + "No checkable rows, or the query or transform failed, for partition " + "keys: %(keys)s.", + keys=", ".join(result["inconclusive_keys"]), + ) + return result + + +def _declined_before_sampling( + datasource: SqlaTable, + mapped_column: str, + target_name: str | None, + value_transform: str | None, +) -> tuple[str, str] | None: + """The status and message when no rows should be read, else ``None``.""" + columns = {col.column_name: col for col in datasource.columns} + source = columns.get(mapped_column) + target = columns.get(target_name) if target_name else None + if ( + datasource.sql + or source is None + or target is None + or source.expression + or target.expression + or has_active_advanced_data_type(source) + or _native_generic_type(target) != GenericDataType.NUMERIC + ): + return "inconclusive", _( + "Data validation requires physical columns and a numeric partition key." + ) + issues = validate_partition_mapping( + column_names=set(columns), + partition_column=target_name, + partition_mapped_column=mapped_column, + main_dttm_col=datasource.main_dttm_col, + transform=value_transform, + engine=datasource.database.backend, + ) + if issues or not value_transform: + return "inconclusive", _( + "Correct the mapping expression before validating data." + ) + if error := stored_expression_error( + datasource.database, datasource.catalog, datasource.schema, value_transform + ): + return "inconclusive", str(error) + # Rows on the hour boundary match a raw-instant transform exactly, so a + # sample could report as matched a transform the mirror refuses. + if refusal := epoch_mapping_refusal( + datasource, mapped_column, target_name, value_transform + ) or raw_instant_refusal( + datasource, mapped_column, target_name, value_transform, probe=True + ): + return "refused", refusal["message"] + return None + + +def _check_row_pairs( + datasource: SqlaTable, + transform: str, + pairs: list[tuple[Any, Any]], + result: dict[str, Any], +) -> bool: + """Record mismatches and return whether this partition had checkable rows.""" + checkable = [] + for value, key in pairs: + if pd.isna(value) or pd.isna(key): + result["skipped_rows"] += 1 + else: + checkable.append((value, key)) + if not checkable: + return False + inputs = [value for value, _key in checkable] + text_answers: list[tuple[int, Any]] = [] + answers = evaluate_transform( + datasource.database, + datasource.catalog, + datasource.schema, + transform, + inputs, + text_answers=text_answers, + fresh=True, + ) + if answers is None or _declined_for_reading_by_type( + inputs, answers, text_answers, [] + ): + return False + for (value, key), answer in zip(checkable, answers, strict=True): + result["checked_rows"] += 1 + if answer != key: + result["mismatch_count"] += 1 + if len(result["examples"]) < 5: + result["examples"].append( + { + "source_value": str(value), + "stored_key": str(key), + "transformed_value": None if answer is None else str(answer), + } + ) + return True diff --git a/superset/datasets/api.py b/superset/datasets/api.py index c7977f3a93db..8c4f89c044fa 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -76,6 +76,7 @@ ) from superset.connectors.sqla.models import SqlaTable from superset.connectors.sqla.partition_mapping import preview_partition_mapping +from superset.connectors.sqla.partition_validation import validate_partition_data from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.daos.dashboard import DashboardDAO from superset.daos.dataset import DatasetDAO @@ -2212,13 +2213,18 @@ def partition_mapping_preview(self, pk: int) -> Response: properties: valid: type: boolean + description: >- + Expression/predicate evaluation succeeded; + not a guarantee of partition correspondence. reason: type: string description: >- - Why the mapping is not valid. Absent when it is. + Why predicate evaluation was declined. + Absent on evaluation success. The editor branches on this to decide how to present the failure. enum: + - unbucketed_epoch - unconfigured - validation - parse @@ -2229,6 +2235,48 @@ def partition_mapping_preview(self, pk: int) -> Response: - transform_escaping - engine - type + data_validation: + type: object + properties: + status: + type: string + enum: + - not_checked + - matched + - mismatch + - inconclusive + - refused + checked_rows: + type: integer + skipped_rows: + type: integer + mismatch_count: + type: integer + partitions_checked: + type: integer + partitions_requested: + type: integer + inconclusive_keys: + type: array + description: >- + Partition keys whose rows could not be read, + present when the status is inconclusive. + items: + type: string + message: + type: string + examples: + type: array + items: + type: object + properties: + source_value: + type: string + stored_key: + type: string + transformed_value: + type: string + nullable: true sample_input: type: string emitted_predicate: @@ -2280,18 +2328,28 @@ def partition_mapping_preview(self, pk: int) -> Response: ), ) - return self.response( - 200, - result=preview_partition_mapping( - dataset, - mapped_column=payload["mapped_column"], - value_transform=payload["value_transform"], - sample_values=payload["sample_values"], - operator=FilterOperator(payload["operator"]), - is_monotonic=payload["is_monotonic"], - partition_column=payload["partition_column"], - ), + result = preview_partition_mapping( + dataset, + mapped_column=payload["mapped_column"], + value_transform=payload["value_transform"], + sample_values=payload["sample_values"], + operator=FilterOperator(payload["operator"]), + is_monotonic=payload["is_monotonic"], + partition_column=payload["partition_column"], ) + result["data_validation"] = {"status": "not_checked"} + if keys := payload["validation_partition_values"]: + try: + result["data_validation"] = validate_partition_data( + dataset, + mapped_column=payload["mapped_column"], + partition_column=payload["partition_column"], + value_transform=payload["value_transform"], + partition_values=keys, + ) + except SupersetSecurityException: + return self.response_403() + return self.response(200, result=result) @expose("//drill_info/", methods=("GET",)) @protect() diff --git a/superset/datasets/schemas.py b/superset/datasets/schemas.py index 50b46b64c3a9..899ceb615cb3 100644 --- a/superset/datasets/schemas.py +++ b/superset/datasets/schemas.py @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import re from datetime import datetime from typing import Any from uuid import UUID @@ -29,7 +30,7 @@ validates_schema, ValidationError, ) -from marshmallow.validate import Length, OneOf, Range +from marshmallow.validate import Length, OneOf, Range, Regexp from superset import security_manager from superset.connectors.sqla.partition_mapping import ( @@ -97,6 +98,25 @@ def validate_d3format(value: str | None) -> None: ) +_PARTITION_KEY_RE = re.compile(r"-?[0-9]{1,19}") + + +def validate_partition_key(value: str) -> None: + """ + A 64-bit integer partition key, written as plain digits. + + `re.fullmatch` rather than a ``$`` anchor, which also matches before a + trailing newline; and bounded to the 64-bit range, so a key no partition + column can hold is refused here rather than sent to the engine. + """ + if not _PARTITION_KEY_RE.fullmatch(value) or not (-(2**63) <= int(value) < 2**63): + # `str`, because marshmallow reads a lazy string as a list of messages + # and reports it one character at a time. + raise ValidationError( + str(_("A partition key must be a whole number within the 64-bit range.")) + ) + + class ResolveIsCertifiedMixin(Schema): """ ``is_certified`` is derived from ``certified_by``/``certification_details``. @@ -636,6 +656,21 @@ class PartitionMappingPreviewSchema(Schema): ) }, ) + validation_partition_values = fields.List( + fields.String( + # The pattern documents the shape in the spec; the validator + # enforces it, without the trailing newline `$` lets through. + validate=[Regexp(r"^-?[0-9]{1,19}$"), validate_partition_key] + ), + load_default=None, + validate=Length(1, 3), + metadata={ + "description": ( + "One to three existing integer partition keys " + "for explicit data validation" + ) + }, + ) operator = fields.String( # `=` mirrors under any transform, so the default is meaningful on its # own. A range default would refuse unless `is_monotonic` came with it. diff --git a/superset/db_engine_specs/hive.py b/superset/db_engine_specs/hive.py index b85868ea7735..a62f3bd29407 100644 --- a/superset/db_engine_specs/hive.py +++ b/superset/db_engine_specs/hive.py @@ -119,9 +119,8 @@ class HiveEngineSpec(PrestoEngineSpec): supports_dynamic_schema = True supports_cross_catalog_queries = False - # Epoch-integer partition keys are the common Hive layout, and the - # one-argument form takes the bound being mirrored rather than "now". - partition_value_transform_default = "unix_timestamp(:value)" + # A numeric key does not reveal its bucket size or timezone. + partition_value_transform_default = None # Explicitly opt out (overriding the inherited PrestoEngineSpec value): # Hive/Spark's GROUPING SETS + GROUPING() marker semantics have not been # verified against this query pattern, so fall back to one query per diff --git a/superset/db_engine_specs/impala.py b/superset/db_engine_specs/impala.py index e5134e21b947..6b79f1822a85 100644 --- a/superset/db_engine_specs/impala.py +++ b/superset/db_engine_specs/impala.py @@ -52,9 +52,8 @@ class ImpalaEngineSpec(BaseEngineSpec): # ``partition_col = T(v)`` agrees with the ``col = v`` it stands in for. binary_string_comparison = True - # Epoch-integer partition keys are the common Impala layout, and the - # one-argument form takes the bound being mirrored rather than "now". - partition_value_transform_default = "unix_timestamp(:value)" + # A numeric key does not reveal its bucket size or timezone. + partition_value_transform_default = None metadata = { "description": ( diff --git a/superset/sql/parse.py b/superset/sql/parse.py index 3627d6362451..897f8751c5c8 100644 --- a/superset/sql/parse.py +++ b/superset/sql/parse.py @@ -268,6 +268,48 @@ def has_aggregate( ) +def strip_comments(sql: str, engine: str = "base") -> str | None: + """ + ``sql`` with its comments removed and every other character as written. + + Read off the engine's own lexer, so a comment marker inside a string literal + is data and stays. A comment between two tokens becomes one space, so it + cannot join them; one before the first token or after the last is dropped. + Returns ``None`` when the text does not tokenize, or when what is left does + not lex to the same tokens with no comment -- the text is never guessed at. + """ + dialect = SQLGLOT_DIALECTS.get(engine) + try: + lexer = Dialect.get_or_raise(dialect) + tokens = lexer.tokenize(sql) + except Exception: # pylint: disable=broad-except + return None + if not tokens: + # Nothing but whitespace and comments: nothing is left once they go. + return "" + pieces = [] + previous_end = -1 + for token in tokens: + gap = sql[previous_end + 1 : token.start] + if gap.strip(): + gap = " " if previous_end >= 0 else "" + pieces.append(gap) + pieces.append(sql[token.start : token.end + 1]) + previous_end = token.end + tail = sql[previous_end + 1 :] + pieces.append("" if tail.strip() else tail) + stripped = "".join(pieces) + try: + relexed = lexer.tokenize(stripped) + except Exception: # pylint: disable=broad-except + return None + if any(token.comments for token in relexed) or [ + (token.token_type, token.text) for token in relexed + ] != [(token.token_type, token.text) for token in tokens]: + return None + return stripped + + def is_constant_expression(expression: str, engine: str = "base") -> bool: """ Return True if the SQL expression is a bare constant: a string or numeric @@ -1390,6 +1432,22 @@ def count_select_expressions(self) -> int: return 0 return len(self._parsed.expressions) + def has_same_projection(self, other: SQLStatement) -> bool: + """ + Whether both statements select one structurally equal expression. + + Redundant parentheses, letter case, whitespace and comments do not + count as differences, so a caller can recognize a known expression + however it was typed. + """ + projections = [ + statement._parsed.expressions[0].unnest() + for statement in (self, other) + if isinstance(statement._parsed, exp.Select) + and len(statement._parsed.expressions) == 1 + ] + return len(projections) == 2 and projections[0] == projections[1] + def is_bare_select_expression(self) -> bool: """ Whether this is ``SELECT `` and nothing else. diff --git a/superset/translations/ar/LC_MESSAGES/messages.po b/superset/translations/ar/LC_MESSAGES/messages.po index 5f5fb8a82ff8..e28c1cf790ab 100644 --- a/superset/translations/ar/LC_MESSAGES/messages.po +++ b/superset/translations/ar/LC_MESSAGES/messages.po @@ -1390,6 +1390,9 @@ msgstr "سيتم إنشاء مخطط جديد." msgid "A new dashboard will be created." msgstr "سيتم إنشاء لوحة معلومات جديدة." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4327,6 +4330,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "موضع تسمية الطفل" @@ -5377,6 +5385,12 @@ msgstr "مع عنوان فرعي" msgid "Corner Radius" msgstr "الشعاع الداخلي" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "الارتباط" @@ -6092,6 +6106,12 @@ msgstr "تم تحديث البيانات" msgid "Data type" msgstr "نوع البيانات" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "يتضمن DataFrame سلسلة واحدة على الأقل" @@ -7977,6 +7997,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "ما إذا كنت تريد ملء الكائنات" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "اسم التنبيه" @@ -7996,10 +8019,16 @@ msgstr "أدخل اسم المجموعة" msgid "Enter minimum and maximum values for the range filter" msgstr "أدخل القيم الدنيا والقصوى لفلتر النطاق" +msgid "Enter one to three existing partition keys." +msgstr "" + #, fuzzy msgid "Enter report name" msgstr "اسم التقرير" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8321,6 +8350,12 @@ msgstr "مجموعة البيانات الحالية" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8525,6 +8560,9 @@ msgstr "تعبير SQL" msgid "Expression cannot be empty" msgstr "لا يمكن أن تكون فارغة" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -9046,6 +9084,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -11258,6 +11306,16 @@ msgstr "مطابقة لون إزاحة الوقت مع السلسلة الأصل msgid "Match type" msgstr "نوع البيانات" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -12074,6 +12132,12 @@ msgstr "لا توجد فلاتر متاحة." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -13080,6 +13144,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13890,6 +13960,12 @@ msgstr "الترتيب" msgid "Ratio" msgstr "نسبة" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "السجلات الخام" @@ -15015,6 +15091,9 @@ msgstr "" msgid "Save query" msgstr "حفظ الاستعلام" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16867,6 +16946,10 @@ msgstr "فئة المصدر" msgid "Source location" msgstr "فئة المصدر" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "سباركلاين" @@ -18841,6 +18924,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18850,6 +18936,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -21333,13 +21422,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "يستخدم أول 25 قيمة إذا احتوى البُعد على قيم أكثر." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "تعبير SQL" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "عرض الاستعلام" @@ -21358,6 +21447,9 @@ msgstr "التحقق من الاتصال لـ %s" msgid "Validating..." msgstr "جارٍ التحميل…" +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "القيمة " diff --git a/superset/translations/ca/LC_MESSAGES/messages.po b/superset/translations/ca/LC_MESSAGES/messages.po index 3d79913b7e82..49e4fbf4704d 100644 --- a/superset/translations/ca/LC_MESSAGES/messages.po +++ b/superset/translations/ca/LC_MESSAGES/messages.po @@ -1241,6 +1241,9 @@ msgstr "Es crearà un nou gràfic." msgid "A new dashboard will be created." msgstr "Es crearà un nou dashboard." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4144,6 +4147,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Posició de l'etiqueta filla" @@ -5164,6 +5172,12 @@ msgstr "Amb un subtítol" msgid "Corner Radius" msgstr "Radi de Cantonada" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlació" @@ -5864,6 +5878,12 @@ msgstr "Dades actualitzades" msgid "Data type" msgstr "Tipus de dades" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "El DataFrame ha d'incloure almenys una sèrie" @@ -7652,6 +7672,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Si omplir els objectes" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Introdueix nom de l'alerta" @@ -7670,9 +7693,15 @@ msgstr "Introduïu el nom del grup" msgid "Enter minimum and maximum values for the range filter" msgstr "Introduïu els valors mínim i màxim per al filtre de rang" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Introdueix nom de l'informe" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7969,6 +7998,12 @@ msgstr "Conjunt de dades existent" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8173,6 +8208,9 @@ msgstr "Expressió SQL" msgid "Expression cannot be empty" msgstr "no pot estar buit" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8680,6 +8718,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10849,6 +10897,12 @@ msgstr "Coincidir color de desplaçament temporal amb sèrie original" msgid "Match type" msgstr "Tipus de dades" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -11632,6 +11686,12 @@ msgstr "Cap filtre disponible." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12635,6 +12695,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13406,6 +13472,12 @@ msgstr "Classificació" msgid "Ratio" msgstr "Proporció" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Registres en brut" @@ -14499,6 +14571,9 @@ msgstr "" msgid "Save query" msgstr "Desar consulta" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16334,6 +16409,10 @@ msgstr "Categoria font" msgid "Source location" msgstr "Categoria font" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Línia de tendència" @@ -18268,6 +18347,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18277,6 +18359,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20731,13 +20816,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Utilitza els primers 25 valors si la dimensió en té més." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Expressió SQL" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Veure consulta" @@ -20754,6 +20839,9 @@ msgstr "Validant connectivitat per %s" msgid "Validating..." msgstr "Carregant..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Valor" diff --git a/superset/translations/cs/LC_MESSAGES/messages.po b/superset/translations/cs/LC_MESSAGES/messages.po index b609620508cb..2546b8e8b611 100644 --- a/superset/translations/cs/LC_MESSAGES/messages.po +++ b/superset/translations/cs/LC_MESSAGES/messages.po @@ -1250,6 +1250,9 @@ msgstr "Bude vytvořen nový graf." msgid "A new dashboard will be created." msgstr "Bude vytvořena nová nástěnka." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4030,6 +4033,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Pozice popisku podřízené položky" @@ -5009,6 +5017,12 @@ msgstr "Kopírovat s hlavičkami" msgid "Corner Radius" msgstr "Poloměr rohu" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Korelace" @@ -5680,6 +5694,12 @@ msgstr "Data obnovena" msgid "Data type" msgstr "Datový typ" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "Datový rámec musí obsahovat alespoň jednu sérii" @@ -7425,6 +7445,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Zadejte část názvu objektu" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Zadejte název upozornění" @@ -7440,9 +7463,15 @@ msgstr "Zadejte název skupiny" msgid "Enter minimum and maximum values for the range filter" msgstr "Zadejte minimální a maximální hodnoty pro filtr rozsahu" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Zadejte název reportu" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7732,6 +7761,12 @@ msgstr "Existující sada dat" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7933,6 +7968,9 @@ msgstr "SQL výraz" msgid "Expression cannot be empty" msgstr "Výraz nesmí být prázdný" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8410,6 +8448,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10495,6 +10543,13 @@ msgstr "Sladit barvu časového posunu s původní sérií" msgid "Match type" msgstr "Datový typ" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Matrixify" msgstr "Matrixify" @@ -11258,6 +11313,12 @@ msgstr "Nejsou k dispozici žádné filtry." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12242,6 +12303,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12998,6 +13065,12 @@ msgstr "Pořadí" msgid "Ratio" msgstr "Poměr" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Nezpracované záznamy" @@ -14070,6 +14143,9 @@ msgstr "" msgid "Save query" msgstr "Uložit dotaz" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15839,6 +15915,10 @@ msgstr "Zdrojová kategorie" msgid "Source location" msgstr "Zdrojová kategorie" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Minigraf" @@ -17688,6 +17768,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17697,6 +17780,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20050,13 +20136,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Použije prvních 25 hodnot, pokud dimenze obsahuje více." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Platný SQL výraz" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Ověřit dotaz" @@ -20073,6 +20159,9 @@ msgstr "Ověřuje se konektivita pro %s" msgid "Validating..." msgstr "Ověřování..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Hodnota" diff --git a/superset/translations/de/LC_MESSAGES/messages.po b/superset/translations/de/LC_MESSAGES/messages.po index 763ac211496f..be51bc571f9d 100644 --- a/superset/translations/de/LC_MESSAGES/messages.po +++ b/superset/translations/de/LC_MESSAGES/messages.po @@ -1307,6 +1307,9 @@ msgstr "Es wird ein neues Diagramm erstellt." msgid "A new dashboard will be created." msgstr "Es wird ein neues Dashboard erstellt." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4172,6 +4175,11 @@ msgstr "Diagramme und Dashboards, die diesen Datensatz verwenden, werden geprüf msgid "Checking for affected charts and dashboards…" msgstr "Betroffene Diagramme und Dashboards werden geprüft…" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Untergeordnete Beschriftungsposition" @@ -5162,6 +5170,12 @@ msgstr "Mit Kopfzeilen kopieren" msgid "Corner Radius" msgstr "Eckenradius" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Korrelation" @@ -5850,6 +5864,12 @@ msgstr "Daten aktualisiert" msgid "Data type" msgstr "Datentyp" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame mit mindestens eine Zeitreihe enhalten" @@ -7575,6 +7595,9 @@ msgstr "Neues Passwort eingeben" msgid "Enter a part of the object name" msgstr "Geben Sie einen Teil des Objektnamens ein" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Name des Alarms eingeben" @@ -7590,9 +7613,15 @@ msgstr "Gruppennamen eingeben" msgid "Enter minimum and maximum values for the range filter" msgstr "Mindest- und Höchstwert für den Bereichsfilter eingeben" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Name des Berichts" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7880,6 +7909,12 @@ msgstr "" "Bisheriges Minimum: %s Sekunden. Beim Bearbeiten wird es durch ganze " "Minuten ersetzt." +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "Bearbeitungsmodus beenden" @@ -8086,6 +8121,9 @@ msgstr "Ausdrücke" msgid "Expression cannot be empty" msgstr "Ausdruck darf nicht leer sein" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8579,6 +8617,16 @@ msgstr "" "Filter auf die zugeordnete Spalte werden auf diese Spalte gespiegelt, " "damit die Engine Partitionen ausschließen kann." +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "Filter auf diese Spalte werden auf die Partitionsspalte gespiegelt." @@ -10664,6 +10712,12 @@ msgstr "Farbe der Zeitverschiebung an die Originalserie anpassen" msgid "Match type" msgstr "Vergleichstyp" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + msgid "Matrixify" msgstr "Matrixify" @@ -11424,6 +11478,12 @@ msgstr "Keine Filter verfügbar." msgid "No charts or dashboards depend on the selected %s." msgstr "Keine Diagramme oder Dashboards hängen von den ausgewählten %s ab." +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" "Keine Farbzuordnungen definiert. Klicken Sie auf „+ Weitere hinzufügen“, " @@ -12452,6 +12512,12 @@ msgstr "Partitionsfeld:" msgid "Partition filter mapping" msgstr "Partitionsfilter-Zuordnung" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13203,6 +13269,12 @@ msgstr "Rangliste" msgid "Ratio" msgstr "Verhältnis" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Roheinträge" @@ -14314,6 +14386,9 @@ msgstr "" msgid "Save query" msgstr "Abfrage speichern" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16061,6 +16136,10 @@ msgstr "Quellkategorie" msgid "Source location" msgstr "Quellort" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Sparkline" @@ -18055,6 +18134,9 @@ msgstr "" "die Transformation so, dass sie :value sowohl als Zeitstempel als auch " "als Text liest." +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18070,6 +18152,9 @@ msgstr "" "Zuordnung werden nicht gespiegelt. Schreiben Sie die Transformation so, " "dass sie :value in beiden Formen auf dieselbe Weise liest." +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20616,6 +20701,9 @@ msgstr "Gültig" msgid "Valid SQL expression" msgstr "Gültiger SQL-Ausdruck" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Abfrage prüfen" @@ -20629,6 +20717,9 @@ msgstr "Prüfe Verbindung für %s" msgid "Validating..." msgstr "Wird geprüft..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Wert" diff --git a/superset/translations/en/LC_MESSAGES/messages.po b/superset/translations/en/LC_MESSAGES/messages.po index c68a1857eeb7..cea8a9996fd9 100644 --- a/superset/translations/en/LC_MESSAGES/messages.po +++ b/superset/translations/en/LC_MESSAGES/messages.po @@ -1118,6 +1118,9 @@ msgstr "" msgid "A new dashboard will be created." msgstr "" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -3653,6 +3656,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "" @@ -4554,6 +4562,12 @@ msgstr "" msgid "Corner Radius" msgstr "" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "" @@ -5173,6 +5187,12 @@ msgstr "" msgid "Data type" msgstr "" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "" @@ -6724,6 +6744,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "" @@ -6739,9 +6762,15 @@ msgstr "" msgid "Enter minimum and maximum values for the range filter" msgstr "" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7016,6 +7045,12 @@ msgstr "" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7199,6 +7234,9 @@ msgstr "" msgid "Expression cannot be empty" msgstr "" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -7653,6 +7691,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -9571,6 +9619,12 @@ msgstr "" msgid "Match type" msgstr "" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + msgid "Matrixify" msgstr "" @@ -10284,6 +10338,12 @@ msgstr "" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -11185,6 +11245,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -11882,6 +11948,12 @@ msgstr "" msgid "Ratio" msgstr "" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "" @@ -12896,6 +12968,9 @@ msgstr "" msgid "Save query" msgstr "" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -14440,6 +14515,10 @@ msgstr "" msgid "Source location" msgstr "" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "" @@ -16038,6 +16117,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -16047,6 +16129,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -18127,10 +18212,10 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "" -msgid "Valid" +msgid "Valid SQL expression" msgstr "" -msgid "Valid SQL expression" +msgid "Validate against data" msgstr "" msgid "Validate query" @@ -18146,6 +18231,9 @@ msgstr "" msgid "Validating..." msgstr "" +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "" diff --git a/superset/translations/es/LC_MESSAGES/messages.po b/superset/translations/es/LC_MESSAGES/messages.po index ce89f503c859..59c06e4ac56a 100644 --- a/superset/translations/es/LC_MESSAGES/messages.po +++ b/superset/translations/es/LC_MESSAGES/messages.po @@ -1227,6 +1227,9 @@ msgstr "Se creará un nuevo gráfico." msgid "A new dashboard will be created." msgstr "Se creará un nuevo panel de control." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4100,6 +4103,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Posición de la etiqueta secundaria" @@ -5107,6 +5115,12 @@ msgstr "Con un subencabezado" msgid "Corner Radius" msgstr "Radio de la esquina" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlación" @@ -5804,6 +5818,12 @@ msgstr "Datos actualizados" msgid "Data type" msgstr "Tipo de datos" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "El marco de datos incluye al menos una serie" @@ -7569,6 +7589,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Si se deben rellenar los objetos" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Introduce el nombre de la alerta" @@ -7586,9 +7609,15 @@ msgstr "Introducir el nombre del grupo" msgid "Enter minimum and maximum values for the range filter" msgstr "Ingrese los valores mínimo y máximo para el filtro de rango" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Introduce el nombre del informe" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7886,6 +7915,12 @@ msgstr "Conjunto de datos existente" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8084,6 +8119,9 @@ msgstr "Expresión" msgid "Expression cannot be empty" msgstr "no puede estar vacío" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8581,6 +8619,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10741,6 +10789,12 @@ msgstr "Hacer coincidir el color del cambio de tiempo con la serie original" msgid "Match type" msgstr "Tipo de datos" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: fr, ru] #, fuzzy msgid "Matrixify" @@ -11521,6 +11575,12 @@ msgstr "No hay filtros disponibles." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12524,6 +12584,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13298,6 +13364,12 @@ msgstr "Clasificación" msgid "Ratio" msgstr "Proporción" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Registros en bruto" @@ -14394,6 +14466,9 @@ msgstr "" msgid "Save query" msgstr "Guardar consulta" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16196,6 +16271,10 @@ msgstr "Categoría de fuente" msgid "Source location" msgstr "Ubicación de origen" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Minigráfica" @@ -18155,6 +18234,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18164,6 +18246,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20611,13 +20696,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Usa los primeros 25 valores si la dimensión tiene más." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Expresión SQL" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Ver consulta" @@ -20634,6 +20719,9 @@ msgstr "Validando conectividad para %s" msgid "Validating..." msgstr "Cargando..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Valor" diff --git a/superset/translations/fa/LC_MESSAGES/messages.po b/superset/translations/fa/LC_MESSAGES/messages.po index b4920a17aaec..1842238280d3 100644 --- a/superset/translations/fa/LC_MESSAGES/messages.po +++ b/superset/translations/fa/LC_MESSAGES/messages.po @@ -1253,6 +1253,9 @@ msgstr "یک نمودار جدید ایجاد خواهد شد." msgid "A new dashboard will be created." msgstr "یک داشبورد جدید ایجاد خواهد شد." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4141,6 +4144,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "موقعیت برچسب کودک" @@ -5180,6 +5188,12 @@ msgstr "با یک زیرعنوان" msgid "Corner Radius" msgstr "شعاع داخلی" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "همبستگی" @@ -5884,6 +5898,12 @@ msgstr "داده‌ها به‌روز شد" msgid "Data type" msgstr "نوع داده" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame شامل حداقل یک سری است." @@ -7669,6 +7689,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "آیا اشیا را پر کنیم؟" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "نام هشدار را وارد کنید" @@ -7687,9 +7710,15 @@ msgstr "نام گروه را وارد کنید" msgid "Enter minimum and maximum values for the range filter" msgstr "مقادیر حداقل و حداکثر را برای فیلتر بازه وارد کنید" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "نام گزارش را وارد کنید" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8001,6 +8030,12 @@ msgstr "دیتاست موجود" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8205,6 +8240,9 @@ msgstr "عبارت SQL" msgid "Expression cannot be empty" msgstr "نمی‌تواند خالی باشد" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8720,6 +8758,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10916,6 +10964,12 @@ msgstr "تطبیق رنگ جابجایی زمانی با سری اصلی" msgid "Match type" msgstr "نوع داده" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -11711,6 +11765,12 @@ msgstr "هیچ فیلتری موجود نیست." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12706,6 +12766,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13497,6 +13563,12 @@ msgstr "رتبه‌بندی" msgid "Ratio" msgstr "نسبت" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "رکوردهای خام" @@ -14598,6 +14670,9 @@ msgstr "" msgid "Save query" msgstr "ذخیره کوئری" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16430,6 +16505,10 @@ msgstr "دسته منبع" msgid "Source location" msgstr "دسته منبع" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "اسپرک‌لاین" @@ -18364,6 +18443,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18373,6 +18455,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20833,13 +20918,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "اگر بُعد بیشتر داشته باشد، از ۲۵ مقدار اول استفاده می‌کند." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "عبارت SQL" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "مشاهده کوئری" @@ -20858,6 +20943,9 @@ msgstr "در حال تأیید اتصال برای %s" msgid "Validating..." msgstr "در حال بارگذاری..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "مقدار" diff --git a/superset/translations/fi/LC_MESSAGES/messages.po b/superset/translations/fi/LC_MESSAGES/messages.po index 6be5a5b37504..a4ab64c259d1 100644 --- a/superset/translations/fi/LC_MESSAGES/messages.po +++ b/superset/translations/fi/LC_MESSAGES/messages.po @@ -1710,6 +1710,9 @@ msgstr "Uusi kaavio luodaan." msgid "A new dashboard will be created." msgstr "Uusi koontinäyttö luodaan." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -6246,6 +6249,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -7863,6 +7871,12 @@ msgstr "Kopioi otsikoineen" msgid "Corner Radius" msgstr "Kulman säde" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -8961,6 +8975,12 @@ msgstr "Tiedot päivitetty" msgid "Data type" msgstr "Tietotyyppi" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, it, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, uk, zh, # zh_TW] @@ -11788,6 +11808,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Syötä osa objektin nimestä" +msgid "Enter a value transform before validating against data." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ca, cs, de, # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] #, fuzzy @@ -11815,12 +11838,18 @@ msgstr "Syötä ryhmän nimi" msgid "Enter minimum and maximum values for the range filter" msgstr "Syötä aluesuodattimen minimi- ja maksimiarvot" +msgid "Enter one to three existing partition keys." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ca, cs, de, # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] #, fuzzy msgid "Enter report name" msgstr "Syötä raportin nimi" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -12295,6 +12324,12 @@ msgstr "Olemassa oleva tietojoukko" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -12604,6 +12639,9 @@ msgstr "SQL-lauseke" msgid "Expression cannot be empty" msgstr "Lauseke ei voi olla tyhjä" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -13343,6 +13381,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -16768,6 +16816,12 @@ msgstr "Sovita aikasiirtymän väri alkuperäiseen sarjaan" msgid "Match type" msgstr "Tietotyyppi" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, fr, ja, # lv, ru, sk, uk] #, fuzzy @@ -18027,6 +18081,12 @@ msgstr "Ei käytettävissä olevia suodattimia." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -19599,6 +19659,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -20902,6 +20968,12 @@ msgstr "Sijoitusjärjestys" msgid "Ratio" msgstr "Suhde" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -22614,6 +22686,9 @@ msgstr "" msgid "Save query" msgstr "Tallenna kysely" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -25426,6 +25501,10 @@ msgstr "Lähdeluokka" msgid "Source location" msgstr "Lähdeluokka" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk] #, fuzzy @@ -28200,6 +28279,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -28209,6 +28291,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -31669,15 +31754,15 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Käyttää ensimmäisiä 25 arvoa, jos dimensiossa on enemmän." -msgid "Valid" -msgstr "" - # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ja, lv, ru, # uk] #, fuzzy msgid "Valid SQL expression" msgstr "Kelvollinen SQL-lauseke" +msgid "Validate against data" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ja, lv, ru, # uk] #, fuzzy @@ -31702,6 +31787,9 @@ msgstr "Tarkistetaan yhteyttä kohteelle %s" msgid "Validating..." msgstr "Tarkistetaan..." +msgid "Validation inconclusive" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, tr, uk, zh, # zh_TW] diff --git a/superset/translations/fr/LC_MESSAGES/messages.po b/superset/translations/fr/LC_MESSAGES/messages.po index 969e9beee600..3d1fac30fbba 100644 --- a/superset/translations/fr/LC_MESSAGES/messages.po +++ b/superset/translations/fr/LC_MESSAGES/messages.po @@ -1226,6 +1226,9 @@ msgstr "Un nouveau graphique va être créé." msgid "A new dashboard will be created." msgstr "Un nouveau tableau de bord va être créé." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4034,6 +4037,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Position de l'étiquette enfant" @@ -5009,6 +5017,12 @@ msgstr "Copier avec les en-têtes" msgid "Corner Radius" msgstr "Rayon des coins" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Corrélation" @@ -5680,6 +5694,12 @@ msgstr "Données actualisées" msgid "Data type" msgstr "Type de données" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame doit comprendre au moins une série" @@ -7378,6 +7398,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Saisir une partie du nom de l'objet" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Saisir le nom de l'alerte" @@ -7395,9 +7418,15 @@ msgstr "Saisir le nom du groupe" msgid "Enter minimum and maximum values for the range filter" msgstr "Entrer une valeur minimal et maximal pour la plage du filter" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Saisir le nom du rapport" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7680,6 +7709,12 @@ msgstr "Jeu de données existant" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7872,6 +7907,9 @@ msgstr "Expression" msgid "Expression cannot be empty" msgstr "L'expression ne peut pas être vide" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8347,6 +8385,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10412,6 +10460,12 @@ msgstr "Associer la couleur du décalage temporel à la série originale" msgid "Match type" msgstr "Correspondance du type" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + msgid "Matrixify" msgstr "Matrixify" @@ -11164,6 +11218,12 @@ msgstr "Aucun filtre disponible." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12153,6 +12213,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12896,6 +12962,12 @@ msgstr "Rang" msgid "Ratio" msgstr "Ratio" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Registres bruts" @@ -13967,6 +14039,9 @@ msgstr "" msgid "Save query" msgstr "Enregistrer la requête" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15704,6 +15779,10 @@ msgstr "Catégorie de source" msgid "Source location" msgstr "Localisation de la source" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Sparkline" @@ -17610,6 +17689,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17619,6 +17701,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20007,12 +20092,12 @@ msgstr "" "Utiliser uniquement les 25 premières valeurs de la dimension s'il en " "existe plus." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Expression SQL valide" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Valider la requête" @@ -20026,6 +20111,9 @@ msgstr "Validation de la connectivité pour %s" msgid "Validating..." msgstr "Validation..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Valeur" diff --git a/superset/translations/it/LC_MESSAGES/messages.po b/superset/translations/it/LC_MESSAGES/messages.po index c6d42d0b552f..1d64a8b59791 100644 --- a/superset/translations/it/LC_MESSAGES/messages.po +++ b/superset/translations/it/LC_MESSAGES/messages.po @@ -1459,6 +1459,9 @@ msgstr "Sarà creato un nuovo grafico." msgid "A new dashboard will be created." msgstr "Una nuova dashboard sarà creata." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4798,6 +4801,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -6082,6 +6090,12 @@ msgstr "Copia con intestazioni" msgid "Corner Radius" msgstr "Raggio d'angolo" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlazione" @@ -6861,6 +6875,12 @@ msgstr "Dati aggiornati" msgid "Data type" msgstr "Tipo" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "Seleziona almeno una metrica" @@ -8879,6 +8899,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Inserire una parte del nome oggetto" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Immettere il nome dell'avviso" @@ -8899,9 +8922,15 @@ msgstr "Immettere il nome del gruppo" msgid "Enter minimum and maximum values for the range filter" msgstr "Inserisci i valori minimo e massimo per il filtro intervallo" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Immettere il nome del report" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -9262,6 +9291,12 @@ msgstr "Dataset esistente" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "Esci dalla modalità di modifica" @@ -9480,6 +9515,9 @@ msgstr "Espressione" msgid "Expression cannot be empty" msgstr "L'espressione non può essere vuota" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -10050,6 +10088,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -12759,6 +12807,12 @@ msgstr "Abbina il colore del timeshift alla serie originale" msgid "Match type" msgstr "Tipo" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] msgid "Matrixify" @@ -13701,6 +13755,12 @@ msgstr "Nessun filtro disponibile." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -14920,6 +14980,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -15880,6 +15946,12 @@ msgstr "Classifica" msgid "Ratio" msgstr "Rapporto" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -17190,6 +17262,9 @@ msgstr "" msgid "Save query" msgstr "query condivisa" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -19230,6 +19305,10 @@ msgstr "Categoria sorgente" msgid "Source location" msgstr "Posizione sorgente" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk] @@ -21548,6 +21627,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -21557,6 +21639,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -24460,12 +24545,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Utilizza i primi 25 valori se la dimensione ne contiene di più." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Espressione SQL valida" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Convalida query" @@ -24481,6 +24566,9 @@ msgstr "Convalida della connettività per %s" msgid "Validating..." msgstr "Convalida..." +msgid "Validation inconclusive" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt, pt_BR, ro, ru, sk, sl, sr, sr_Latn, # tr, uk, zh, zh_TW] diff --git a/superset/translations/ja/LC_MESSAGES/messages.po b/superset/translations/ja/LC_MESSAGES/messages.po index 5e97a9a3dcda..5354c1b3ada1 100644 --- a/superset/translations/ja/LC_MESSAGES/messages.po +++ b/superset/translations/ja/LC_MESSAGES/messages.po @@ -1126,6 +1126,9 @@ msgstr "新しいチャートが作成されます。" msgid "A new dashboard will be created." msgstr "新しいダッシュボードが作成されます。" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -3676,6 +3679,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "子ラベルの位置" @@ -4579,6 +4587,12 @@ msgstr "ヘッダー付きでコピー" msgid "Corner Radius" msgstr "角の丸み" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "相関" @@ -5199,6 +5213,12 @@ msgstr "データを更新しました" msgid "Data type" msgstr "データ型" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "データフレームには少なくとも1つの系列が必要です" @@ -6733,6 +6753,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "オブジェクト名の一部を入力" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "アラート名を入力" @@ -6748,9 +6771,15 @@ msgstr "グループ名を入力" msgid "Enter minimum and maximum values for the range filter" msgstr "範囲フィルターの最小値と最大値を入力してください" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "レポート名を入力" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7025,6 +7054,12 @@ msgstr "既存のデータセット" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7210,6 +7245,9 @@ msgstr "式" msgid "Expression cannot be empty" msgstr "式を空にすることはできません" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -7663,6 +7701,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -9592,6 +9640,11 @@ msgstr "タイムシフト(比較用)の色を元の系列に合わせる" msgid "Match type" msgstr "一致種類" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" + msgid "Matrixify" msgstr "マトリックス化" @@ -10302,6 +10355,12 @@ msgstr "利用可能なフィルターはありません。" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -11205,6 +11264,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -11903,6 +11968,12 @@ msgstr "ランキング" msgid "Ratio" msgstr "比率" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "未加工レコード" @@ -12924,6 +12995,9 @@ msgstr "" msgid "Save query" msgstr "クエリを保存" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -14466,6 +14540,10 @@ msgstr "ソースカテゴリ" msgid "Source location" msgstr "ソースの場所" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "スパークライン" @@ -16104,6 +16182,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -16113,6 +16194,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -18215,12 +18299,12 @@ msgstr "円を用いてシステム内の異なるステージを通るデータ msgid "Uses the first 25 values if the dimension has more." msgstr "ディメンションに値が25件以上ある場合、最初の25件の値を使用します。" -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "有効なSQL式" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "クエリを検証" @@ -18234,6 +18318,9 @@ msgstr "%s の接続を確認中" msgid "Validating..." msgstr "検証中..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "値" diff --git a/superset/translations/ko/LC_MESSAGES/messages.po b/superset/translations/ko/LC_MESSAGES/messages.po index 40bb68c3d583..0b7828740e4c 100644 --- a/superset/translations/ko/LC_MESSAGES/messages.po +++ b/superset/translations/ko/LC_MESSAGES/messages.po @@ -1564,6 +1564,9 @@ msgstr "차트를 생성할 수 없습니다." msgid "A new dashboard will be created." msgstr "대시보드를 생성할 수 없습니다." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -5586,6 +5589,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -7074,6 +7082,12 @@ msgstr "헤더와 함께 복사" msgid "Corner Radius" msgstr "모서리 반경" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, uk, # zh, zh_TW] @@ -7994,6 +8008,12 @@ msgstr "데이터가 새로고침되었습니다" msgid "Data type" msgstr "차트 유형" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, it, ja, lv, mi, nl, pl, pt, pt_BR, ro, ru, sk, sl, sr, # sr_Latn, tr, uk, zh, zh_TW] @@ -10458,6 +10478,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "차트 유형" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "테이블 명" @@ -10482,10 +10505,16 @@ msgstr "그룹 이름 입력" msgid "Enter minimum and maximum values for the range filter" msgstr "범위 필터의 최솟값과 최댓값을 입력하세요" +msgid "Enter one to three existing partition keys." +msgstr "" + #, fuzzy msgid "Enter report name" msgstr "차트 유형" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -10916,6 +10945,12 @@ msgstr "차트 수정" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -11203,6 +11238,9 @@ msgstr "표현식" msgid "Expression cannot be empty" msgstr "표현식은 비워둘 수 없습니다" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -11885,6 +11923,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -15080,6 +15128,11 @@ msgstr "시간 이동 색상을 원본 시리즈와 일치" msgid "Match type" msgstr "차트 유형" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -16235,6 +16288,12 @@ msgstr "필터" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -17722,6 +17781,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -18948,6 +19013,12 @@ msgstr "순위" msgid "Ratio" msgstr "생성자" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -20509,6 +20580,9 @@ msgstr "" msgid "Save query" msgstr "Query 저장" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -22953,6 +23027,10 @@ msgstr "데이터소스 명" msgid "Source location" msgstr "데이터소스 명" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk] @@ -25558,6 +25636,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -25567,6 +25648,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -28804,13 +28888,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "차원에 더 많은 값이 있으면 처음 25개 값을 사용합니다." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "표현식" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Query 공유" @@ -28831,6 +28915,9 @@ msgstr "%s에 대한 연결을 확인 중" msgid "Validating..." msgstr "확인 중..." +msgid "Validation inconclusive" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt, pt_BR, ro, ru, sk, sl, sr, sr_Latn, # tr, uk, zh, zh_TW] diff --git a/superset/translations/lv/LC_MESSAGES/messages.po b/superset/translations/lv/LC_MESSAGES/messages.po index 0363f6e2b6a5..dcd69af61ab9 100644 --- a/superset/translations/lv/LC_MESSAGES/messages.po +++ b/superset/translations/lv/LC_MESSAGES/messages.po @@ -1249,6 +1249,9 @@ msgstr "Tiks izveidota jauna diagramma." msgid "A new dashboard will be created." msgstr "Tiks izveidots jauns infopanelis." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -3993,6 +3996,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Bērna apzīmējuma novietojums" @@ -4957,6 +4965,12 @@ msgstr "Kopēt ar virsrakstiem" msgid "Corner Radius" msgstr "Stūra rādiuss" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Korelācija" @@ -5619,6 +5633,12 @@ msgstr "Dati atsvaidzināti" msgid "Data type" msgstr "Datu tips" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame jāiekļauj vismaz viena sērija" @@ -7305,6 +7325,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Ievadiet daļu no objekta nosaukuma" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Ievadiet brīdinājuma nosaukumu" @@ -7320,9 +7343,15 @@ msgstr "Ievadiet grupas nosaukumu" msgid "Enter minimum and maximum values for the range filter" msgstr "Ievadiet minimālās un maksimālās vērtības diapazona filtram" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Ievadiet atskaites nosaukumu" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7602,6 +7631,12 @@ msgstr "Esoša datu kopa" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7793,6 +7828,9 @@ msgstr "SQL izteiksme" msgid "Expression cannot be empty" msgstr "Izteiksme nevar būt tukša" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8262,6 +8300,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10306,6 +10354,13 @@ msgstr "Saskaņot laika nobīdes krāsu ar sākotnējo sēriju" msgid "Match type" msgstr "Datu tips" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Matrixify" msgstr "Iespējot Matrixify" @@ -11059,6 +11114,12 @@ msgstr "Nav pieejamu filtru." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12028,6 +12089,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12768,6 +12835,12 @@ msgstr "Ranžēšana" msgid "Ratio" msgstr "Attiecība" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Neapstrādāti ieraksti" @@ -13821,6 +13894,9 @@ msgstr "" msgid "Save query" msgstr "Saglabāt vaicājumu" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15540,6 +15616,10 @@ msgstr "Avota kategorija" msgid "Source location" msgstr "Avota kategorija" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Dzirkstlīnija" @@ -17381,6 +17461,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17390,6 +17473,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -19693,12 +19779,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Izmanto pirmās 25 vērtības, ja dimensijai ir vairāk." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Derīga SQL izteiksme" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Pārbaudīt vaicājumu" @@ -19712,6 +19798,9 @@ msgstr "Pārbauda savienojamību priekš %s" msgid "Validating..." msgstr "Pārbaudīšana..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Vērtība" diff --git a/superset/translations/messages.pot b/superset/translations/messages.pot index 349600d099c6..5246c5447b58 100644 --- a/superset/translations/messages.pot +++ b/superset/translations/messages.pot @@ -1040,6 +1040,9 @@ msgstr "" msgid "A new dashboard will be created." msgstr "" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -3384,6 +3387,9 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "Checks up to 50 rows per selected partition. Sampling cannot verify every row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "" @@ -4241,6 +4247,12 @@ msgstr "" msgid "Corner Radius" msgstr "" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "" @@ -4819,6 +4831,12 @@ msgstr "" msgid "Data type" msgstr "" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "" @@ -6243,6 +6261,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "" @@ -6258,9 +6279,15 @@ msgstr "" msgid "Enter minimum and maximum values for the range filter" msgstr "" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "Enter the email address of your account and we will send you a link to reset your password." msgstr "" @@ -6530,6 +6557,12 @@ msgstr "" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -6703,6 +6736,9 @@ msgstr "" msgid "Expression cannot be empty" msgstr "" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "Extension '%(extension_id)s' has exceeded its persistent storage quota of %(quota)d bytes." msgstr "" @@ -7133,6 +7169,12 @@ msgstr "" msgid "Filters on the mapped column are mirrored onto this column so the engine can prune partitions." msgstr "" +msgid "Filters on this column are mapped onto the partition column, but are not mirrored." +msgstr "" + +msgid "Filters on this column are mapped onto the partition field, but with this transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -8944,6 +8986,12 @@ msgstr "" msgid "Match type" msgstr "" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + msgid "Matrixify" msgstr "" @@ -9620,6 +9668,10 @@ msgstr "" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "No checkable rows, or the query or transform failed, for partition keys: %(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -10453,6 +10505,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "Partitions whose height to parent height proportions are below this value are pruned" msgstr "" @@ -11123,6 +11181,9 @@ msgstr "" msgid "Ratio" msgstr "" +msgid "Raw epoch conversion does not specify the partition bucket. Use the expression that populates the partition key, including its hour or day rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "" @@ -12093,6 +12154,9 @@ msgstr "" msgid "Save query" msgstr "" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "Save the dataset to preview this mapping. The preview runs against the saved columns, and this mapping uses one that has not been saved yet." msgstr "" @@ -13509,6 +13573,10 @@ msgstr "" msgid "Source location" msgstr "" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "" @@ -14848,10 +14916,16 @@ msgstr "" msgid "The transform could not read %(value)s written as text, which a filter typed as text sends, so its answer for the timestamp can only be checked by being a date or timestamp within a year of it. It answered %(typed)s, so filters on this mapping will not be mirrored. Write the transform so it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "The transform gives a different partition value for a timestamp (%(typed)s) than for the same instant written as text (%(text)s). A time range and a filter typed as text both reach it, so it cannot be right for both; filters on this mapping will not be mirrored. Write the transform so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "The transform returned %(returned)s for %(column)s, which is %(kind)s. Mirrored filters on this mapping would fail at the database." msgstr "" @@ -16661,10 +16735,10 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "" -msgid "Valid" +msgid "Valid SQL expression" msgstr "" -msgid "Valid SQL expression" +msgid "Validate against data" msgstr "" msgid "Validate query" @@ -16680,6 +16754,9 @@ msgstr "" msgid "Validating..." msgstr "" +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "" diff --git a/superset/translations/mi/LC_MESSAGES/messages.po b/superset/translations/mi/LC_MESSAGES/messages.po index 32c981fc8823..89e50d60fdc8 100644 --- a/superset/translations/mi/LC_MESSAGES/messages.po +++ b/superset/translations/mi/LC_MESSAGES/messages.po @@ -1245,6 +1245,9 @@ msgstr "Ka hangaia he kauwhata hou." msgid "A new dashboard will be created." msgstr "Ka hangaia he papatohu hou." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4118,6 +4121,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Tūnga tapanga tamaiti" @@ -5135,6 +5143,12 @@ msgstr "Me te pane-iti" msgid "Corner Radius" msgstr "Pūtoro Koki" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Hononga" @@ -5822,6 +5836,12 @@ msgstr "Raraunga kua whakahouhia" msgid "Data type" msgstr "Momo raraunga" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "Me whai te DataFrame i te kotahi raupapa nui rawa" @@ -7588,6 +7608,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Mēnā ka whakakī i ngā ahanoa" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Tāuru ingoa matohi" @@ -7606,9 +7629,15 @@ msgstr "Tāuru i te ingoa rōpū" msgid "Enter minimum and maximum values for the range filter" msgstr "Tāuruhia ngā uara iti rawa me ngā uara nui rawa mō te tātari āputa" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Tāuru ingoa pūrongo" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7905,6 +7934,12 @@ msgstr "Rārangi raraunga kei te tīari" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8105,6 +8140,9 @@ msgstr "Kīanga SQL" msgid "Expression cannot be empty" msgstr "kāore e taea te noho kau" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8612,6 +8650,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10779,6 +10827,12 @@ msgstr "Whakatau tae neke wā ki te raupapa taketake" msgid "Match type" msgstr "Momo raraunga" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -11557,6 +11611,12 @@ msgstr "Kāore he tātari wātea." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12547,6 +12607,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13327,6 +13393,12 @@ msgstr "Kōmaka" msgid "Ratio" msgstr "Ōwehenga" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Rekoata mata" @@ -14425,6 +14497,9 @@ msgstr "" msgid "Save query" msgstr "Tiaki pātai" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16252,6 +16327,10 @@ msgstr "Kāwai puna" msgid "Source location" msgstr "Kāwai puna" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Rārangi Kōhā" @@ -18170,6 +18249,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18179,6 +18261,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20634,13 +20719,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Ka whakamahi i ngā uara 25 tuatahi mehemea he nui ake ngā uara o te āhua." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Kīanga SQL" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Tirohia pātai" @@ -20657,6 +20742,9 @@ msgstr "E manatoko ana i te hononga mō %s" msgid "Validating..." msgstr "E uta ana..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Uara" diff --git a/superset/translations/nl/LC_MESSAGES/messages.po b/superset/translations/nl/LC_MESSAGES/messages.po index cefc45e7fa5a..d3acdcefd845 100644 --- a/superset/translations/nl/LC_MESSAGES/messages.po +++ b/superset/translations/nl/LC_MESSAGES/messages.po @@ -1257,6 +1257,9 @@ msgstr "Er zal een nieuwe grafiek worden aangemaakt." msgid "A new dashboard will be created." msgstr "Er zal een nieuw dashboard worden aangemaakt." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4227,6 +4230,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Onderliggend label positie" @@ -5291,6 +5299,12 @@ msgstr "Met een ondertitel" msgid "Corner Radius" msgstr "Inner Radius" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlatie" @@ -6011,6 +6025,12 @@ msgstr "Gegevens verversen" msgid "Data type" msgstr "Data type" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame bevat ten minste één reeks" @@ -7833,6 +7853,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Of de objecten moeten worden gevuld" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "Naam van de waarschuwing" @@ -7852,10 +7875,16 @@ msgstr "Groepsnaam invoeren" msgid "Enter minimum and maximum values for the range filter" msgstr "Voer minimale en maximale waarden in voor het bereikfilter" +msgid "Enter one to three existing partition keys." +msgstr "" + #, fuzzy msgid "Enter report name" msgstr "Naam rapport" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8174,6 +8203,12 @@ msgstr "Bestaande dataset" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8381,6 +8416,9 @@ msgstr "SQL expressie" msgid "Expression cannot be empty" msgstr "mag niet leeg zijn" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8902,6 +8940,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -11136,6 +11184,12 @@ msgstr "Kleur van tijdsverschuiving afstemmen op de originele reeks" msgid "Match type" msgstr "Data type" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -11953,6 +12007,12 @@ msgstr "Geen beschikbare filters." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12968,6 +13028,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13772,6 +13838,12 @@ msgstr "Ranglijst" msgid "Ratio" msgstr "Verhouding" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Ruwe records" @@ -14896,6 +14968,9 @@ msgstr "" msgid "Save query" msgstr "Zoekopdracht opslaan" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16763,6 +16838,10 @@ msgstr "Bron categorie" msgid "Source location" msgstr "Bron categorie" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Sparkline" @@ -18784,6 +18863,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18793,6 +18875,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -21307,13 +21392,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Gebruikt de eerste 25 waarden als de dimensie er meer heeft." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "SQL expressie" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Bekijk zoekopdracht" @@ -21332,6 +21417,9 @@ msgstr "Connectiviteit valideren voor %s" msgid "Validating..." msgstr "Bezig met laden…" +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Waarde" diff --git a/superset/translations/pl/LC_MESSAGES/messages.po b/superset/translations/pl/LC_MESSAGES/messages.po index 7d35018e0130..7c07ff235e4f 100644 --- a/superset/translations/pl/LC_MESSAGES/messages.po +++ b/superset/translations/pl/LC_MESSAGES/messages.po @@ -1290,6 +1290,9 @@ msgstr "Zostanie utworzony nowy wykres." msgid "A new dashboard will be created." msgstr "Zostanie utworzony nowy panel kontrolny." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4240,6 +4243,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Pozycja etykiety podrzędnej" @@ -5334,6 +5342,12 @@ msgstr "Z podtytułem" msgid "Corner Radius" msgstr "Promień wewnętrzny" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + #, fuzzy msgid "Correlation" msgstr "Korelacja" @@ -6083,6 +6097,12 @@ msgstr "Dane odświeżone" msgid "Data type" msgstr "Typ danych" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame musi zawierać co najmniej jedną serię" @@ -7946,6 +7966,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Czy wypełniać obiekty" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "Wprowadź nazwę alertu" @@ -7965,10 +7988,16 @@ msgstr "Wprowadź nazwę grupy" msgid "Enter minimum and maximum values for the range filter" msgstr "Wprowadź wartości minimalne i maksymalne dla filtru zakresu" +msgid "Enter one to three existing partition keys." +msgstr "" + #, fuzzy msgid "Enter report name" msgstr "Wprowadź nazwę raportu" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8293,6 +8322,12 @@ msgstr "Brakujący zestaw danych" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8501,6 +8536,9 @@ msgstr "Wyrażenie SQL" msgid "Expression cannot be empty" msgstr "nie może być puste" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -9037,6 +9075,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -11287,6 +11335,12 @@ msgstr "Dopasuj kolor przesunięcia czasowego do oryginalnej serii" msgid "Match type" msgstr "Typ danych" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -12119,6 +12173,12 @@ msgstr "Brak dostępnych filtrów." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -13136,6 +13196,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13975,6 +14041,12 @@ msgstr "Ranking" msgid "Ratio" msgstr "Stosunek" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Surowe dane" @@ -15134,6 +15206,9 @@ msgstr "" msgid "Save query" msgstr "Zapisz zapytanie" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -17067,6 +17142,10 @@ msgstr "Kategoria źródłowa" msgid "Source location" msgstr "Kategoria źródłowa" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Wykres liniowy (Sparkline)" @@ -19082,6 +19161,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -19091,6 +19173,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -21652,13 +21737,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Używa pierwszych 25 wartości, jeśli wymiar zawiera ich więcej." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Wyrażenie SQL" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Zobacz zapytanie" @@ -21677,6 +21762,9 @@ msgstr "Sprawdzanie połączenia dla %s" msgid "Validating..." msgstr "Ładowanie..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Wartość" diff --git a/superset/translations/pt/LC_MESSAGES/messages.po b/superset/translations/pt/LC_MESSAGES/messages.po index 96e13cb39ca3..2c66f8218398 100644 --- a/superset/translations/pt/LC_MESSAGES/messages.po +++ b/superset/translations/pt/LC_MESSAGES/messages.po @@ -1511,6 +1511,9 @@ msgstr "Um novo gráfico será criado." msgid "A new dashboard will be created." msgstr "Um novo painel será criado." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -5017,6 +5020,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -6269,6 +6277,12 @@ msgstr "Copiar com cabeçalhos" msgid "Corner Radius" msgstr "Raio de canto" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlação" @@ -7074,6 +7088,12 @@ msgstr "Dados atualizados" msgid "Data type" msgstr "Tipo de dado" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame inclui pelo menos uma série" @@ -9226,6 +9246,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Insira uma parte do nome do objeto" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Insira o nome do alerta" @@ -9246,9 +9269,15 @@ msgstr "Introduza o nome do grupo" msgid "Enter minimum and maximum values for the range filter" msgstr "Digite os valores mínimo e máximo para o filtro de intervalo" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Insira o nome do relatório" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -9614,6 +9643,12 @@ msgstr "Conjunto de dados existente" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -9842,6 +9877,9 @@ msgstr "Expressão" msgid "Expression cannot be empty" msgstr "A expressão não pode ficar vazia" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -10431,6 +10469,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -13150,6 +13198,12 @@ msgstr "Combinar a cor do deslocamento de tempo com a série original" msgid "Match type" msgstr "Tipo de correspondência" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] msgid "Matrixify" @@ -14134,6 +14188,12 @@ msgstr "Não há filtros disponíveis." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -15421,6 +15481,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -16452,6 +16518,12 @@ msgstr "Classificação" msgid "Ratio" msgstr "Proporção" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk, zh, zh_TW] @@ -17778,6 +17850,9 @@ msgstr "" msgid "Save query" msgstr "Salvar consulta" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -19885,6 +19960,10 @@ msgstr "Categoria de origem" msgid "Source location" msgstr "Local de origem" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ro, ru, sk, sl, sr, sr_Latn, tr, # uk] @@ -22274,6 +22353,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -22283,6 +22365,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -25187,12 +25272,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Usa os primeiros 25 valores se a dimensão tiver mais." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Expressão SQL válida" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Validar consulta" @@ -25210,6 +25295,9 @@ msgstr "Validando a conectividade para%s" msgid "Validating..." msgstr "Validando..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Valor" diff --git a/superset/translations/pt_BR/LC_MESSAGES/messages.po b/superset/translations/pt_BR/LC_MESSAGES/messages.po index 45ceeda9f7f1..b87200c641fa 100644 --- a/superset/translations/pt_BR/LC_MESSAGES/messages.po +++ b/superset/translations/pt_BR/LC_MESSAGES/messages.po @@ -1246,6 +1246,9 @@ msgstr "Um novo gráfico será criado." msgid "A new dashboard will be created." msgstr "Um novo painel será criado." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4214,6 +4217,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Posição do rótulo filho" @@ -5273,6 +5281,12 @@ msgstr "Copiar com cabeçalhos" msgid "Corner Radius" msgstr "Raio de canto" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlação" @@ -6025,6 +6039,12 @@ msgstr "Dados atualizados" msgid "Data type" msgstr "Tipo de dado" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame inclui pelo menos uma série" @@ -7843,6 +7863,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Insira uma parte do nome do objeto" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "Insira o nome do alerta" @@ -7859,10 +7882,16 @@ msgstr "Insira o nome do grupo" msgid "Enter minimum and maximum values for the range filter" msgstr "Digite os valores mínimo e máximo para o filtro de intervalo" +msgid "Enter one to three existing partition keys." +msgstr "" + #, fuzzy msgid "Enter report name" msgstr "Insira o nome do relatório" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8175,6 +8204,12 @@ msgstr "Conjunto de dados existente" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8389,6 +8424,9 @@ msgstr "Expressão" msgid "Expression cannot be empty" msgstr "A expressão não pode ficar vazia" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8918,6 +8956,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -11204,6 +11252,12 @@ msgstr "Combinar a cor do deslocamento de tempo com a série original" msgid "Match type" msgstr "Tipo de correspondência" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -12002,6 +12056,12 @@ msgstr "Não há filtros disponíveis." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -13046,6 +13106,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13880,6 +13946,12 @@ msgstr "Classificação" msgid "Ratio" msgstr "Proporção" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Registros Brutos" @@ -15023,6 +15095,9 @@ msgstr "" msgid "Save query" msgstr "Salvar consulta" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16903,6 +16978,10 @@ msgstr "Categoria de origem" msgid "Source location" msgstr "Local de origem" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Minigráfico" @@ -18912,6 +18991,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18921,6 +19003,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -21427,13 +21512,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Usa os primeiros 25 valores se a dimensão tiver mais." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Expressão SQL válida" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Validar consulta" @@ -21452,6 +21537,9 @@ msgstr "Validando a conectividade para%s" msgid "Validating..." msgstr "Validando..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Valor" diff --git a/superset/translations/ro/LC_MESSAGES/messages.po b/superset/translations/ro/LC_MESSAGES/messages.po index 5dc99a3b5cdc..7e96d2dc8322 100644 --- a/superset/translations/ro/LC_MESSAGES/messages.po +++ b/superset/translations/ro/LC_MESSAGES/messages.po @@ -1307,6 +1307,9 @@ msgstr "Un grafic nou va fi creat." msgid "A new dashboard will be created." msgstr "Un panou de control va fi creat." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4210,6 +4213,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Poziție etichetă copil" @@ -5235,6 +5243,12 @@ msgstr "Copiază cu Antete" msgid "Corner Radius" msgstr "Rază Colț" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Corelație" @@ -5949,6 +5963,12 @@ msgstr "Date reîmprospătate" msgid "Data type" msgstr "Tip date" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame-ul trebuie să includă cel puțin o serie" @@ -7774,6 +7794,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Introduceți o parte din numele obiectului" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Introduceți numele alertei" @@ -7789,9 +7812,15 @@ msgstr "Introduceți numele grupului" msgid "Enter minimum and maximum values for the range filter" msgstr "Introduceți valorile minime și maxime pentru filtrul de interval" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Introduceți numele raportului" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8083,6 +8112,12 @@ msgstr "Set de date existent" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8288,6 +8323,9 @@ msgstr "Expresie" msgid "Expression cannot be empty" msgstr "Expresia nu poate fi goală" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8792,6 +8830,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10982,6 +11030,13 @@ msgstr "Potrivește culoarea deplasării de timp cu seria originală" msgid "Match type" msgstr "Tip de potrivire" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Matrixify" msgstr "Matrixify" @@ -11777,6 +11832,12 @@ msgstr "Fără filtre disponibile." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12781,6 +12842,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13557,6 +13624,12 @@ msgstr "Clasament" msgid "Ratio" msgstr "Raport" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Înregistrări brute" @@ -14666,6 +14739,9 @@ msgstr "" msgid "Save query" msgstr "Salvează interogare" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -16513,6 +16589,10 @@ msgstr "Categorie sursă" msgid "Source location" msgstr "Locație sursă" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Sparkline" @@ -18461,6 +18541,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18470,6 +18553,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20939,12 +21025,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Folosește primele 25 de valori dacă dimensiunea conține mai multe." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Expresie SQL validă" +msgid "Validate against data" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: de, it, ja, # lv, ru, sr, sr_Latn, uk] #, fuzzy @@ -20961,6 +21047,9 @@ msgstr "Validare conectivitate pentru %s" msgid "Validating..." msgstr "Validare..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Valoare" diff --git a/superset/translations/ru/LC_MESSAGES/messages.po b/superset/translations/ru/LC_MESSAGES/messages.po index 4332eefc8561..3a9e571cb996 100644 --- a/superset/translations/ru/LC_MESSAGES/messages.po +++ b/superset/translations/ru/LC_MESSAGES/messages.po @@ -1235,6 +1235,9 @@ msgstr "Не удалось создать диаграмму" msgid "A new dashboard will be created." msgstr "Новый дашборд будет создан" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -3937,6 +3940,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Расположение меток листьев" @@ -4889,6 +4897,12 @@ msgstr "Копировать с заголовком" msgid "Corner Radius" msgstr "Радиус скругления" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Корреляция" @@ -5532,6 +5546,12 @@ msgstr "Данные обновлены" msgid "Data type" msgstr "Тип данных" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "Датафрейм должен включать хотя бы один ряд" @@ -7197,6 +7217,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Введите часть имени объекта" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Введите имя оповещения" @@ -7212,9 +7235,15 @@ msgstr "Введите имя группы" msgid "Enter minimum and maximum values for the range filter" msgstr "Введите минимальное и максимальное значения для диапазона" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Введите имя отчета" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7491,6 +7520,12 @@ msgstr "Существующий датасет" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7679,6 +7714,9 @@ msgstr "Выражение SQL" msgid "Expression cannot be empty" msgstr "Выражение не может быть пустым" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8140,6 +8178,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10155,6 +10203,13 @@ msgstr "Синхронизировать цвет временного сдви msgid "Match type" msgstr "Тип данных" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Matrixify" msgstr "Матричное расположение" @@ -10899,6 +10954,12 @@ msgstr "Нет доступных фильтров" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -11862,6 +11923,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12585,6 +12652,12 @@ msgstr "Ранжирование" msgid "Ratio" msgstr "Отношение" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Сырые записи" @@ -13629,6 +13702,9 @@ msgstr "" msgid "Save query" msgstr "Сохранить запрос" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15265,6 +15341,10 @@ msgstr "Исходная категория" msgid "Source location" msgstr "Исходная категория" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Спарклайн" @@ -17085,6 +17165,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17094,6 +17177,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -19369,12 +19455,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Использует первые 25 значений, если измерение содержит больше." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Корректное SQL-выражение" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Проверить запрос" @@ -19388,6 +19474,9 @@ msgstr "Проверка подключения для %s" msgid "Validating..." msgstr "Проверка..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Значение" diff --git a/superset/translations/sk/LC_MESSAGES/messages.po b/superset/translations/sk/LC_MESSAGES/messages.po index 09056b0039d9..fac998c6d011 100644 --- a/superset/translations/sk/LC_MESSAGES/messages.po +++ b/superset/translations/sk/LC_MESSAGES/messages.po @@ -1240,6 +1240,9 @@ msgstr "Bude vytvorený nový graf." msgid "A new dashboard will be created." msgstr "Bude vytvorená nová nástenka." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4028,6 +4031,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Pozícia popisku podriadenej položky" @@ -5002,6 +5010,12 @@ msgstr "Kopírovať s hlavičkami" msgid "Corner Radius" msgstr "Polomer rohu" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Korelácie" @@ -5672,6 +5686,12 @@ msgstr "Data obnovena" msgid "Data type" msgstr "Datový typ" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "Datový rámec musí obsahovať alespoň jednu sérii" @@ -7383,6 +7403,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Zadejte část názvu objektu" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Zadejte názov upozornenie" @@ -7398,9 +7421,15 @@ msgstr "Zadajte názov skupiny" msgid "Enter minimum and maximum values for the range filter" msgstr "Zadejte minimální a maximální hodnoty pre filter rozsahu" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Zadejte názov reportu" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7689,6 +7718,12 @@ msgstr "Existujúcí sada dat" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7886,6 +7921,9 @@ msgstr "SQL výraz" msgid "Expression cannot be empty" msgstr "Výraz nesmí být prázdny" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8361,6 +8399,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10385,6 +10433,13 @@ msgstr "Sladit farbu časového posunu s pôvodný sérií" msgid "Match type" msgstr "Datový typ" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + msgid "Matrixify" msgstr "Matrixify" @@ -11138,6 +11193,12 @@ msgstr "Nesú k dispozici žiadne filtre." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12098,6 +12159,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12843,6 +12910,12 @@ msgstr "" msgid "Ratio" msgstr "Pomer" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Nezpracované záznamy" @@ -13907,6 +13980,9 @@ msgstr "" msgid "Save query" msgstr "Uložiť dotaz" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15631,6 +15707,10 @@ msgstr "Zdrojová kategória" msgid "Source location" msgstr "Zdrojová kategória" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Minigraf" @@ -17409,6 +17489,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17418,6 +17501,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -19723,13 +19809,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Použije prvých 25 hodnôt, ak dimenzia obsahuje viac." -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "Platný SQL výraz" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Overiť dotaz" @@ -19745,6 +19831,9 @@ msgstr "" msgid "Validating..." msgstr "" +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Hodnota" diff --git a/superset/translations/sl/LC_MESSAGES/messages.po b/superset/translations/sl/LC_MESSAGES/messages.po index 0410ce2e4848..6349512728b5 100644 --- a/superset/translations/sl/LC_MESSAGES/messages.po +++ b/superset/translations/sl/LC_MESSAGES/messages.po @@ -1284,6 +1284,9 @@ msgstr "Ustvarjen bo nov grafikon." msgid "A new dashboard will be created." msgstr "Ustvarjena bo nova nadzorna plošča." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4050,6 +4053,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Položaj podrejene oznake" @@ -5025,6 +5033,12 @@ msgstr "Kopiraj z glavami" msgid "Corner Radius" msgstr "Radij kota" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Korelacija" @@ -5690,6 +5704,12 @@ msgstr "Podatki osveženi" msgid "Data type" msgstr "Tip podatka" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame vsebuje vsaj eno serijo" @@ -7403,6 +7423,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Vnesite del imena predmeta" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Vnesite naslov opozorila" @@ -7420,9 +7443,15 @@ msgstr "Vnesite ime skupine" msgid "Enter minimum and maximum values for the range filter" msgstr "Vnesite najmanjšo in največjo vrednost za filter obsega" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Vnesite naslov poročila" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7711,6 +7740,12 @@ msgstr "Obstoječ podatkovni set" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7904,6 +7939,9 @@ msgstr "Izraz" msgid "Expression cannot be empty" msgstr "Izraz ne sme biti prazen" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8391,6 +8429,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10447,6 +10495,14 @@ msgstr "Uskladi barvo časovnega premika z izvorno serijo" msgid "Match type" msgstr "Vrsta ujemanja" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] msgid "Matrixify" @@ -11218,6 +11274,12 @@ msgstr "Ni razpoložljivih filtrov." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12178,6 +12240,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12926,6 +12994,12 @@ msgstr "Rangiranje" msgid "Ratio" msgstr "Razmerje" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Surovi podatki" @@ -14000,6 +14074,9 @@ msgstr "" msgid "Save query" msgstr "Shrani poizvedbo" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15706,6 +15783,10 @@ msgstr "Kategorija izvora" msgid "Source location" msgstr "Lokacija vira" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Hitri grafikon" @@ -17563,6 +17644,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17572,6 +17656,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -19911,12 +19998,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Uporabi prvih 25 vrednosti, če jih ima dimenzija več." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Veljaven izraz SQL" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Preveri poizvedbo" @@ -19934,6 +20021,9 @@ msgstr "Preverjanje povezljivosti za %s" msgid "Validating..." msgstr "Preverjanje ..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Vrednost" diff --git a/superset/translations/sr/LC_MESSAGES/messages.po b/superset/translations/sr/LC_MESSAGES/messages.po index 0867e7f3a695..7a19ae65e5c7 100644 --- a/superset/translations/sr/LC_MESSAGES/messages.po +++ b/superset/translations/sr/LC_MESSAGES/messages.po @@ -1416,6 +1416,9 @@ msgstr "Биће креиран нови графикон." msgid "A new dashboard will be created." msgstr "Биће креирана нова контролна табла." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4749,6 +4752,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "Child label position" msgstr "Позиција подређене ознаке" @@ -5917,6 +5925,12 @@ msgstr "Копирај са заглављима" msgid "Corner Radius" msgstr "Радијус угла" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "Correlation" msgstr "Корелација" @@ -6718,6 +6732,12 @@ msgstr "Подаци ажурирани" msgid "Data type" msgstr "Тип података" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + # de, es, fa, fr, it, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, uk, zh, # zh_TW] msgid "DataFrame include at least one series" @@ -8800,6 +8820,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Унесите део назива објекта" +msgid "Enter a value transform before validating against data." +msgstr "" + # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] msgid "Enter alert name" msgstr "Унесите назив упозорења" @@ -8819,10 +8842,16 @@ msgstr "Унесите назив групе" msgid "Enter minimum and maximum values for the range filter" msgstr "Унесите минималну и максималну вредност за филтер опсега" +msgid "Enter one to three existing partition keys." +msgstr "" + # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] msgid "Enter report name" msgstr "Унесите назив извештаја" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -9169,6 +9198,12 @@ msgstr "Постојећи скуп података" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -9396,6 +9431,9 @@ msgstr "Израз" msgid "Expression cannot be empty" msgstr "Израз не може бити празан" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -9946,6 +9984,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -12399,6 +12447,13 @@ msgstr "Усклади боју временског помака са ориг msgid "Match type" msgstr "Тип подударања" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + # lv, ru, sk, uk] msgid "Matrixify" msgstr "Matrixify" @@ -13308,6 +13363,12 @@ msgstr "Нема доступних филтера." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -14476,6 +14537,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "" "Partitions whose height to parent height proportions are below this value" @@ -15391,6 +15458,12 @@ msgstr "Рангирање" msgid "Ratio" msgstr "Однос" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "Raw records" msgstr "Сирови записи" @@ -16670,6 +16743,9 @@ msgstr "" msgid "Save query" msgstr "Сачувај упит" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -18671,6 +18747,10 @@ msgstr "Категорија извора" msgid "Source location" msgstr "Локација извора" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk] msgid "Sparkline" msgstr "Спарклајн" @@ -20805,6 +20885,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -20814,6 +20897,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -23470,13 +23556,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Користи првих 25 вредности ако димензија садржи више." -msgid "Valid" -msgstr "" - # uk] msgid "Valid SQL expression" msgstr "Важећи SQL израз" +msgid "Validate against data" +msgstr "" + # uk] msgid "Validate query" msgstr "Провери упит" @@ -23494,6 +23580,9 @@ msgstr "Провера повезивости за %s" msgid "Validating..." msgstr "Провера у току..." +msgid "Validation inconclusive" +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, tr, uk, zh, # zh_TW] msgid "Value" diff --git a/superset/translations/sr_Latn/LC_MESSAGES/messages.po b/superset/translations/sr_Latn/LC_MESSAGES/messages.po index 7eabccbaeebe..eea3233afdc3 100644 --- a/superset/translations/sr_Latn/LC_MESSAGES/messages.po +++ b/superset/translations/sr_Latn/LC_MESSAGES/messages.po @@ -1416,6 +1416,9 @@ msgstr "Biće kreiran novi grafikon." msgid "A new dashboard will be created." msgstr "Biće kreirana nova kontrolna tabla." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4752,6 +4755,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "Child label position" msgstr "Pozicija podređene oznake" @@ -5920,6 +5928,12 @@ msgstr "Kopiraj sa zaglavljima" msgid "Corner Radius" msgstr "Radijus ugla" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "Correlation" msgstr "Korelacija" @@ -6721,6 +6735,12 @@ msgstr "Podaci ažurirani" msgid "Data type" msgstr "Tip podataka" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + # de, es, fa, fr, it, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, uk, zh, # zh_TW] msgid "DataFrame include at least one series" @@ -8804,6 +8824,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Unesite deo naziva objekta" +msgid "Enter a value transform before validating against data." +msgstr "" + # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] msgid "Enter alert name" msgstr "Unesite naziv upozorenja" @@ -8823,10 +8846,16 @@ msgstr "Unesite naziv grupe" msgid "Enter minimum and maximum values for the range filter" msgstr "Unesite minimalnu i maksimalnu vrednost za filter opsega" +msgid "Enter one to three existing partition keys." +msgstr "" + # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] msgid "Enter report name" msgstr "Unesite naziv izveštaja" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -9173,6 +9202,12 @@ msgstr "Postojeći skup podataka" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -9400,6 +9435,9 @@ msgstr "Izraz" msgid "Expression cannot be empty" msgstr "Izraz ne može biti prazan" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -9950,6 +9988,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -12406,6 +12454,13 @@ msgstr "Uskladi boju vremenskog pomaka sa originalnom serijom" msgid "Match type" msgstr "Tip podudaranja" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + # lv, ru, sk, uk] msgid "Matrixify" msgstr "Matrixify" @@ -13315,6 +13370,12 @@ msgstr "Nema dostupnih filtera." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -14483,6 +14544,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "" "Partitions whose height to parent height proportions are below this value" @@ -15398,6 +15465,12 @@ msgstr "Rangiranje" msgid "Ratio" msgstr "Odnos" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] msgid "Raw records" msgstr "Sirovi zapisi" @@ -16677,6 +16750,9 @@ msgstr "" msgid "Save query" msgstr "Sačuvaj upit" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -18678,6 +18754,10 @@ msgstr "Kategorija izvora" msgid "Source location" msgstr "Lokacija izvora" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk] msgid "Sparkline" msgstr "Sparklajn" @@ -20813,6 +20893,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -20822,6 +20905,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -23487,13 +23573,13 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Koristi prvih 25 vrednosti ako dimenzija sadrži više." -msgid "Valid" -msgstr "" - # uk] msgid "Valid SQL expression" msgstr "Važeći SQL izraz" +msgid "Validate against data" +msgstr "" + # uk] msgid "Validate query" msgstr "Proveri upit" @@ -23511,6 +23597,9 @@ msgstr "Provera povezivosti za %s" msgid "Validating..." msgstr "Provera u toku..." +msgid "Validation inconclusive" +msgstr "" + # de, es, fa, fr, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, tr, uk, zh, # zh_TW] msgid "Value" diff --git a/superset/translations/ta/LC_MESSAGES/messages.po b/superset/translations/ta/LC_MESSAGES/messages.po index bb01775c90d2..f248ad5128d1 100644 --- a/superset/translations/ta/LC_MESSAGES/messages.po +++ b/superset/translations/ta/LC_MESSAGES/messages.po @@ -1206,6 +1206,9 @@ msgstr "ஒரு புதிய சார்ட் உருவாக்கப msgid "A new dashboard will be created." msgstr "ஒரு புதிய டேஷ்போர்டு உருவாக்கப்படும்." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -3947,6 +3950,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "துணைக் குறிச்சொல் (Child label) நிலை" @@ -4919,6 +4927,12 @@ msgstr "தலைப்புகளுடன் நகலெடு" msgid "Corner Radius" msgstr "மூலை ஆரம் (Corner Radius)" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "ஒட்டுறவு (Correlation)" @@ -5571,6 +5585,12 @@ msgstr "தரவு புதுப்பிக்கப்பட்டது" msgid "Data type" msgstr "தரவு வகை" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "டேட்டாஃபிரேமில் குறைந்தபட்சம் ஒரு வரிசை இருக்க வேண்டும்" @@ -7232,6 +7252,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "பொருளின் பெயரின் ஒரு பகுதியை உள்ளிடவும்" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "எச்சரிக்கை பெயரை உள்ளிடவும்" @@ -7249,9 +7272,15 @@ msgstr "" "வரம்பு வடிகட்டிக்கான (range filter) குறைந்தபட்ச மற்றும் அதிகபட்ச " "மதிப்புகளை உள்ளிடவும்" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "அறிக்கை பெயரை உள்ளிடவும்" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7530,6 +7559,12 @@ msgstr "ஏற்கனவே உள்ள தரவுத்தொகுப் msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7721,6 +7756,9 @@ msgstr "SQL கோவை (expression)" msgid "Expression cannot be empty" msgstr "கோவை (Expression) காலியாக இருக்கக்கூடாது" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8192,6 +8230,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10184,6 +10232,12 @@ msgstr "நேர மாற்ற நிறத்தை அசல் தொட msgid "Match type" msgstr "தரவு வகை" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" + msgid "Matrixify" msgstr "மேட்ரிசிஃபை (Matrixify)" @@ -10908,6 +10962,12 @@ msgstr "கிடைக்கக்கூடிய வடிகட்டிக msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -11826,6 +11886,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12550,6 +12616,12 @@ msgstr "தரவரிசை (Ranking)" msgid "Ratio" msgstr "விகிதம் (Ratio)" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "மூலப் பதிவுகள் (Raw records)" @@ -13577,6 +13649,9 @@ msgstr "" msgid "Save query" msgstr "வினவலைச் சேமி" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15220,6 +15295,10 @@ msgstr "மூல வகை" msgid "Source location" msgstr "மூல வகை" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "ஸ்பார்க்லைன்" @@ -16926,6 +17005,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -16935,6 +17017,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -19135,12 +19220,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "" -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "சரியான SQL வெளிப்பாடு" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "வினவலைச் சரிபார்க்கவும்" @@ -19154,6 +19239,9 @@ msgstr "%s க்கான இணைப்பைச் சரிபார்க msgid "Validating..." msgstr "சரிபார்க்கிறது..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "மதிப்பு" diff --git a/superset/translations/th/LC_MESSAGES/messages.po b/superset/translations/th/LC_MESSAGES/messages.po index b75bcaca2129..b26f60a8a7e0 100644 --- a/superset/translations/th/LC_MESSAGES/messages.po +++ b/superset/translations/th/LC_MESSAGES/messages.po @@ -1656,6 +1656,9 @@ msgstr "แผนภูมิใหม่จะถูกสร้างขึ้ msgid "A new dashboard will be created." msgstr "แดชบอร์ดใหม่จะถูกสร้างขึ้น" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -6125,6 +6128,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -7723,6 +7731,12 @@ msgstr "คัดลอกพร้อมส่วนหัว" msgid "Corner Radius" msgstr "รัศมีมุม" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -8809,6 +8823,12 @@ msgstr "รีเฟรชข้อมูลแล้ว" msgid "Data type" msgstr "ประเภทข้อมูล" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, it, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, uk, zh, # zh_TW] @@ -11599,6 +11619,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "ป้อนส่วนหนึ่งของชื่อออบเจ็กต์" +msgid "Enter a value transform before validating against data." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ca, cs, de, # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] #, fuzzy @@ -11626,12 +11649,18 @@ msgstr "ป้อนชื่อกลุ่ม" msgid "Enter minimum and maximum values for the range filter" msgstr "ป้อนค่าต่ำสุดและสูงสุดสำหรับตัวกรองช่วง" +msgid "Enter one to three existing partition keys." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ca, cs, de, # es, fa, fr, ja, lv, mi, ru, sk, sl, uk] #, fuzzy msgid "Enter report name" msgstr "ป้อนชื่อรายงาน" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -12104,6 +12133,12 @@ msgstr "ชุดข้อมูลที่มีอยู่" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -12412,6 +12447,9 @@ msgstr "นิพจน์ SQL" msgid "Expression cannot be empty" msgstr "นิพจน์ไม่สามารถว่างเปล่าได้" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -13149,6 +13187,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -16555,6 +16603,11 @@ msgstr "จับคู่สีการเลื่อนเวลากับ msgid "Match type" msgstr "ประเภทข้อมูล" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, fr, ja, # lv, ru, sk, uk] #, fuzzy @@ -17808,6 +17861,12 @@ msgstr "ไม่มีตัวกรองที่ใช้งานได้ msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -19364,6 +19423,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -20663,6 +20728,12 @@ msgstr "การจัดอันดับ" msgid "Ratio" msgstr "อัตราส่วน" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk, zh, zh_TW] #, fuzzy @@ -22372,6 +22443,9 @@ msgstr "" msgid "Save query" msgstr "บันทึกคำค้นหา" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -25158,6 +25232,10 @@ msgstr "หมวดหมู่ต้นทาง" msgid "Source location" msgstr "หมวดหมู่ต้นทาง" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, uk] #, fuzzy @@ -27886,6 +27964,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -27895,6 +27976,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -31309,15 +31393,15 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "ใช้ค่า 25 ค่าแรกหากมิติมีค่ามากกว่านั้น" -msgid "Valid" -msgstr "" - # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ja, lv, ru, # uk] #, fuzzy msgid "Valid SQL expression" msgstr "นิพจน์ SQL ที่ถูกต้อง" +msgid "Validate against data" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ja, lv, ru, # uk] #, fuzzy @@ -31342,6 +31426,9 @@ msgstr "กำลังตรวจสอบการเชื่อมต่อ msgid "Validating..." msgstr "กำลังตรวจสอบ..." +msgid "Validation inconclusive" +msgstr "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, ca, cs, # de, es, fa, fr, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, tr, uk, zh, # zh_TW] diff --git a/superset/translations/tr/LC_MESSAGES/messages.po b/superset/translations/tr/LC_MESSAGES/messages.po index 44c08016baef..33da5c1ab295 100644 --- a/superset/translations/tr/LC_MESSAGES/messages.po +++ b/superset/translations/tr/LC_MESSAGES/messages.po @@ -1205,6 +1205,9 @@ msgstr "Yeni bir grafik oluşturulacaktır." msgid "A new dashboard will be created." msgstr "Yeni bir pano oluşturulacak." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4019,6 +4022,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Çocuk etiketi pozisyonu" @@ -5000,6 +5008,12 @@ msgstr "Headers ile kopyalayın" msgid "Corner Radius" msgstr "Corner Radius" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Correlation" @@ -5683,6 +5697,12 @@ msgstr "Veri yenilendi" msgid "Data type" msgstr "Veri tipi" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "DataFrame en az bir seri içerir" @@ -7375,6 +7395,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Nesne adının bir kısmını girin" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Alarm adını girin" @@ -7390,9 +7413,15 @@ msgstr "Grup adını girin" msgid "Enter minimum and maximum values for the range filter" msgstr "aralığı filtre için minimum ve maksimum değerler girin" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Rapor adını girin" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7678,6 +7707,12 @@ msgstr "Mevcut veri kümesi" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7874,6 +7909,9 @@ msgstr "İfade" msgid "Expression cannot be empty" msgstr "İfade boş olamaz" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8364,6 +8402,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10440,6 +10488,11 @@ msgstr "Maç zamanı orijinal seri ile renk değiştiriyor" msgid "Match type" msgstr "Eşleşme türü" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" + msgid "Matrixify" msgstr "Matrixify" @@ -11209,6 +11262,12 @@ msgstr "Uygun filtre yok." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12190,6 +12249,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12942,6 +13007,12 @@ msgstr "Sıralama" msgid "Ratio" msgstr "Oran" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Raw kayıtları" @@ -14018,6 +14089,9 @@ msgstr "" msgid "Save query" msgstr "Sorguyu kaydet" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15848,6 +15922,10 @@ msgstr "Source kategori" msgid "Source location" msgstr "Kaynak konumu" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Spark" @@ -17729,6 +17807,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17738,6 +17819,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20138,12 +20222,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Boyutun daha fazla değeri varsa ilk 25 değeri kullanır." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Geçerli SQL ifadesi" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "Sorguyu gör" @@ -20159,6 +20243,9 @@ msgstr "%s " msgid "Validating..." msgstr "Yükleniyor…" +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Değer" diff --git a/superset/translations/uk/LC_MESSAGES/messages.po b/superset/translations/uk/LC_MESSAGES/messages.po index ef99c620b29b..5052cc3c9c5b 100644 --- a/superset/translations/uk/LC_MESSAGES/messages.po +++ b/superset/translations/uk/LC_MESSAGES/messages.po @@ -1276,6 +1276,9 @@ msgstr "Створиться нова діаграма." msgid "A new dashboard will be created." msgstr "Створиться новий звіт." +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4003,6 +4006,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "Позиція дочірньої мітки" @@ -4966,6 +4974,12 @@ msgstr "Скопіювати із заголовком" msgid "Corner Radius" msgstr "Заокруглення кутів" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "Кореляція" @@ -5626,6 +5640,12 @@ msgstr "Дані оновлено" msgid "Data type" msgstr "Тип даних" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "Таблиця даних має містити щонайменше один ряд даних" @@ -7323,6 +7343,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "Введіть частину назви об’єкта" +msgid "Enter a value transform before validating against data." +msgstr "" + msgid "Enter alert name" msgstr "Введіть назву оповіщення" @@ -7338,9 +7361,15 @@ msgstr "Введіть назву групи" msgid "Enter minimum and maximum values for the range filter" msgstr "Введіть мінімальне та максимальне значення для фільтра діапазону" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "Введіть назву доповіді" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7620,6 +7649,12 @@ msgstr "Наявний набір даних" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -7811,6 +7846,9 @@ msgstr "Вираз SQL" msgid "Expression cannot be empty" msgstr "Вираз не може бути порожній" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8284,6 +8322,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10316,6 +10364,14 @@ msgstr "Зіставити колір зсуву часу з основними msgid "Match type" msgstr "Тип даних" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + msgid "Matrixify" msgstr "Матриця" @@ -11065,6 +11121,12 @@ msgstr "Немає доступних фільтрів." msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12031,6 +12093,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -12770,6 +12838,12 @@ msgstr "Ранжування" msgid "Ratio" msgstr "Відношення сторін" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "Сирі записи" @@ -13828,6 +13902,9 @@ msgstr "" msgid "Save query" msgstr "Зберегти запит" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15551,6 +15628,10 @@ msgstr "Початкова категорія" msgid "Source location" msgstr "Початкова категорія" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "Нитка" @@ -17389,6 +17470,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17398,6 +17482,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -19701,12 +19788,12 @@ msgstr "" msgid "Uses the first 25 values if the dimension has more." msgstr "Використовує перші 25 значень, якщо вимір містить більше." -msgid "Valid" -msgstr "" - msgid "Valid SQL expression" msgstr "Правильний SQL" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "Перевірити запит" @@ -19720,6 +19807,9 @@ msgstr "Перевіряємо з’єднання для %s" msgid "Validating..." msgstr "Перевіряння..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "Значення" diff --git a/superset/translations/zh/LC_MESSAGES/messages.po b/superset/translations/zh/LC_MESSAGES/messages.po index 0736d182bc77..e14fb71f400b 100644 --- a/superset/translations/zh/LC_MESSAGES/messages.po +++ b/superset/translations/zh/LC_MESSAGES/messages.po @@ -1254,6 +1254,9 @@ msgstr "将会创建一个新图表" msgid "A new dashboard will be created." msgstr "将创建一个新看板。" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4040,6 +4043,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "子标签位置" @@ -5037,6 +5045,12 @@ msgstr "复制并包含标题" msgid "Corner Radius" msgstr "圆角半径" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "相关性" @@ -5735,6 +5749,12 @@ msgstr "已刷新数据" msgid "Data type" msgstr "数据类型" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "数据帧(DataFrame)至少包括一个序列" @@ -7419,6 +7439,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "输入对象名称的一部分" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "告警名称" @@ -7438,9 +7461,15 @@ msgstr "输入分组名称" msgid "Enter minimum and maximum values for the range filter" msgstr "输入范围过滤器的最小值和最大值" +msgid "Enter one to three existing partition keys." +msgstr "" + msgid "Enter report name" msgstr "输入报告名称" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -7740,6 +7769,12 @@ msgstr "现有数据集" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "退出编辑模式" @@ -7941,6 +7976,9 @@ msgstr "表达式" msgid "Expression cannot be empty" msgstr "表达式不能为空" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8458,6 +8496,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -10605,6 +10653,11 @@ msgstr "匹配时间偏移颜色与原始系列" msgid "Match type" msgstr "匹配类型" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] msgid "Matrixify" @@ -11405,6 +11458,12 @@ msgstr "没有可用的筛选器。" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -12395,6 +12454,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -13191,6 +13256,12 @@ msgstr "排名" msgid "Ratio" msgstr "比率" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "原始记录" @@ -14290,6 +14361,9 @@ msgstr "" msgid "Save query" msgstr "保存查询" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -15989,6 +16063,10 @@ msgstr "源分类" msgid "Source location" msgstr "源位置" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + msgid "Sparkline" msgstr "迷你趋势图" @@ -17812,6 +17890,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -17821,6 +17902,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -20162,13 +20246,13 @@ msgstr "使用圆圈来可视化数据在系统不同阶段的流动。将鼠标 msgid "Uses the first 25 values if the dimension has more." msgstr "如果维度包含超过25个值,则仅使用前25个。" -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "SQL表达式" +msgid "Validate against data" +msgstr "" + msgid "Validate query" msgstr "验证查询" @@ -20184,6 +20268,9 @@ msgstr "正在验证 %s 的连接性" msgid "Validating..." msgstr "验证中..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "值" diff --git a/superset/translations/zh_TW/LC_MESSAGES/messages.po b/superset/translations/zh_TW/LC_MESSAGES/messages.po index 669298cba22a..5d590ae403b9 100644 --- a/superset/translations/zh_TW/LC_MESSAGES/messages.po +++ b/superset/translations/zh_TW/LC_MESSAGES/messages.po @@ -1273,6 +1273,9 @@ msgstr "將會創建一個新圖表" msgid "A new dashboard will be created." msgstr "將會創建一個新看板" +msgid "A partition key must be a whole number within the 64-bit range." +msgstr "" + #, python-format msgid "A partition value transform cannot be longer than %(limit)d characters." msgstr "" @@ -4241,6 +4244,11 @@ msgstr "" msgid "Checking for affected charts and dashboards…" msgstr "" +msgid "" +"Checks up to 50 rows per selected partition. Sampling cannot verify every" +" row or future ingestion." +msgstr "" + msgid "Child label position" msgstr "子標籤位置" @@ -5311,6 +5319,12 @@ msgstr "子標題" msgid "Corner Radius" msgstr "内半徑" +msgid "Correct the mapping expression before validating data." +msgstr "" + +msgid "Correct the value transform before validating against data." +msgstr "" + msgid "Correlation" msgstr "相關性" @@ -6069,6 +6083,12 @@ msgstr "已刷新數據" msgid "Data type" msgstr "數據類型" +msgid "Data validation failed." +msgstr "" + +msgid "Data validation requires physical columns and a numeric partition key." +msgstr "" + msgid "DataFrame include at least one series" msgstr "數據帧(DataFrame)至少包括一個序列" @@ -7861,6 +7881,9 @@ msgstr "" msgid "Enter a part of the object name" msgstr "是否填充對象" +msgid "Enter a value transform before validating against data." +msgstr "" + #, fuzzy msgid "Enter alert name" msgstr "告警名稱" @@ -7880,10 +7903,16 @@ msgstr "輸入群組名稱" msgid "Enter minimum and maximum values for the range filter" msgstr "輸入範圍篩選器的最小值和最大值" +msgid "Enter one to three existing partition keys." +msgstr "" + #, fuzzy msgid "Enter report name" msgstr "報告名稱" +msgid "Enter stored epoch partition keys, not timestamps" +msgstr "" + msgid "" "Enter the email address of your account and we will send you a link to " "reset your password." @@ -8208,6 +8237,12 @@ msgstr "存在的數據集" msgid "Existing minimum: %s seconds. Editing replaces it with whole minutes." msgstr "" +msgid "Existing partition keys" +msgstr "" + +msgid "Existing partition keys (up to three, comma separated)" +msgstr "" + msgid "Exit edit mode" msgstr "" @@ -8424,6 +8459,9 @@ msgstr "SQL 表達式" msgid "Expression cannot be empty" msgstr "不能為空" +msgid "Expression evaluated — data not checked" +msgstr "" + #, python-format msgid "" "Extension '%(extension_id)s' has exceeded its persistent storage quota of" @@ -8972,6 +9010,16 @@ msgid "" "can prune partitions." msgstr "" +msgid "" +"Filters on this column are mapped onto the partition column, but are not " +"mirrored." +msgstr "" + +msgid "" +"Filters on this column are mapped onto the partition field, but with this" +" transform they are not mirrored." +msgstr "" + msgid "Filters on this column are mirrored onto the partition column." msgstr "" @@ -11274,6 +11322,11 @@ msgstr "將時間偏移顏色與原始系列匹配" msgid "Match type" msgstr "數據類型" +#, python-format +msgid "Matched %(rows)s sampled rows across %(partitions)s partition" +msgid_plural "Matched %(rows)s sampled rows across %(partitions)s partitions" +msgstr[0] "" + # Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: cs, de, fr, # ja, lv, ro, ru, sk, sr, sr_Latn, tr, uk] #, fuzzy @@ -12118,6 +12171,12 @@ msgstr "没有有效的過濾器" msgid "No charts or dashboards depend on the selected %s." msgstr "" +#, python-format +msgid "" +"No checkable rows, or the query or transform failed, for partition keys: " +"%(keys)s." +msgstr "" + msgid "No color mappings defined. Click \"+ Add more\" to get started." msgstr "" @@ -13160,6 +13219,12 @@ msgstr "" msgid "Partition filter mapping" msgstr "" +msgid "Partition keys must be whole numbers within the 64-bit range, up to three." +msgstr "" + +msgid "Partition mismatch found" +msgstr "" + msgid "" "Partitions whose height to parent height proportions are below this value" " are pruned" @@ -14001,6 +14066,12 @@ msgstr "排名" msgid "Ratio" msgstr "比率" +msgid "" +"Raw epoch conversion does not specify the partition bucket. Use the " +"expression that populates the partition key, including its hour or day " +"rounding and timezone. Filters will not be mirrored." +msgstr "" + msgid "Raw records" msgstr "原始紀錄" @@ -15152,6 +15223,9 @@ msgstr "" msgid "Save query" msgstr "保存查詢" +msgid "Save the dataset before validating against data." +msgstr "" + msgid "" "Save the dataset to preview this mapping. The preview runs against the " "saved columns, and this mapping uses one that has not been saved yet." @@ -17000,6 +17074,10 @@ msgstr "源分類" msgid "Source location" msgstr "源分類" +#, python-format +msgid "Source: %(source)s; stored key: %(stored)s; transform: %(actual)s" +msgstr "" + #, fuzzy msgid "Sparkline" msgstr "標記線" @@ -18931,6 +19009,9 @@ msgid "" "it reads :value both as a timestamp and as text." msgstr "" +msgid "The transform does not match the stored partition keys." +msgstr "" + #, python-format msgid "" "The transform gives a different partition value for a timestamp " @@ -18940,6 +19021,9 @@ msgid "" "so it reads :value the same way in either form." msgstr "" +msgid "The transform matched the sampled rows, not the entire dataset." +msgstr "" + #, python-format msgid "" "The transform returned %(returned)s for %(column)s, which is %(kind)s. " @@ -21427,13 +21511,13 @@ msgstr "使用圆圈來可視化數據在系统不同階段的流動。將鼠標 msgid "Uses the first 25 values if the dimension has more." msgstr "如果維度有更多值,則使用前 25 個值。" -msgid "Valid" -msgstr "" - #, fuzzy msgid "Valid SQL expression" msgstr "SQL 表達式" +msgid "Validate against data" +msgstr "" + #, fuzzy msgid "Validate query" msgstr "查看查詢語句" @@ -21452,6 +21536,9 @@ msgstr "正在驗證 %s 的連線" msgid "Validating..." msgstr "加载中..." +msgid "Validation inconclusive" +msgstr "" + msgid "Value" msgstr "值" diff --git a/tests/unit_tests/connectors/sqla/partition_validation_test.py b/tests/unit_tests/connectors/sqla/partition_validation_test.py new file mode 100644 index 000000000000..d578b85711e5 --- /dev/null +++ b/tests/unit_tests/connectors/sqla/partition_validation_test.py @@ -0,0 +1,393 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Partition validation compares row pairs, not membership in a set of keys.""" + +from datetime import datetime +from types import SimpleNamespace +from typing import Any +from unittest.mock import MagicMock, patch, PropertyMock + +import pandas as pd +import pytest +from flask import Flask + +from superset.connectors.sqla.partition_mapping import ( + epoch_mapping_refusal, + evaluate_transform, + preview_partition_mapping, + resolve_partition_mapping, +) +from superset.connectors.sqla.partition_validation import validate_partition_data +from superset.db_engine_specs.ascend import AscendEngineSpec +from superset.db_engine_specs.base import BaseEngineSpec +from superset.db_engine_specs.databricks import ( + DatabricksHiveEngineSpec, + DatabricksPythonConnectorEngineSpec, +) +from superset.db_engine_specs.hive import HiveEngineSpec +from superset.db_engine_specs.impala import ImpalaEngineSpec +from superset.db_engine_specs.spark import SparkEngineSpec +from superset.exceptions import SupersetSecurityException +from superset.models.core import Database +from superset.utils.core import QueryStatus +from tests.unit_tests.models.partition_mirroring_test import _table + + +@pytest.mark.parametrize( + "spec", + [ + HiveEngineSpec, + ImpalaEngineSpec, + SparkEngineSpec, + DatabricksHiveEngineSpec, + AscendEngineSpec, + ], +) +@pytest.mark.parametrize( + "transform", + [ + "unix_timestamp(:value)", + "(UNIX_TIMESTAMP(:value))", + " unix_timestamp( :value ) ", + ], +) +def test_former_default_is_declined_without_probing( + app: Flask, spec: type[BaseEngineSpec], transform: str +) -> None: + """Stored mappings cannot bypass the guard through a positive cached verdict.""" + table = _table(transform=transform) + with ( + app.app_context(), + patch.object( + Database, "db_engine_spec", new_callable=PropertyMock, return_value=spec + ), + patch("superset.connectors.sqla.partition_mapping.evaluate_transform") as probe, + patch( + "superset.connectors.sqla.models.known_mirror_verdict", return_value=True + ), + patch( + "superset.connectors.sqla.partition_mapping.feature_flag_manager.is_feature_enabled", + return_value=True, + ), + ): + assert spec.partition_value_transform_default is None + assert epoch_mapping_refusal(table, "event_time", "dt_epoch", transform) + # The chart path reaches `build_mirrored_predicates` only through this. + assert resolve_partition_mapping(table) is None + preview = preview_partition_mapping( + table, + mapped_column="event_time", + value_transform=transform, + sample_values=["2026-01-15 10:47:23"], + ) + assert preview["reason"] == "unbucketed_epoch" + probe.assert_not_called() + + +def test_floor_and_unrelated_databricks_spec_are_not_refused(app: Flask) -> None: + """The refusal is expression- and resolved-spec-specific.""" + table = _table() + with ( + app.app_context(), + patch.object( + Database, + "db_engine_spec", + new_callable=PropertyMock, + return_value=HiveEngineSpec, + ), + ): + assert ( + epoch_mapping_refusal( + table, + "event_time", + "dt_epoch", + "floor(unix_timestamp(:value)/3600)*3600", + ) + is None + ) + with ( + app.app_context(), + patch.object( + Database, + "db_engine_spec", + new_callable=PropertyMock, + return_value=DatabricksPythonConnectorEngineSpec, + ), + ): + assert ( + epoch_mapping_refusal( + table, "event_time", "dt_epoch", "unix_timestamp(:value)" + ) + is None + ) + + +def test_half_typed_transform_is_not_refused_and_does_not_raise(app: Flask) -> None: + """An unterminated quote tokenizes badly; the dataset must still load.""" + table = _table(transform="unix_timestamp(':value)") + with ( + app.app_context(), + patch.object( + Database, + "db_engine_spec", + new_callable=PropertyMock, + return_value=HiveEngineSpec, + ), + ): + assert ( + epoch_mapping_refusal( + table, "event_time", "dt_epoch", "unix_timestamp(':value)" + ) + is None + ) + + +@pytest.mark.parametrize( + "answers,status", + [ + ([3600, 7200], "matched"), + ([7200, 7200], "mismatch"), + ([None, 7200], "mismatch"), + (None, "inconclusive"), + ], +) +def test_data_validation_checks_corresponding_rows( + app: Flask, answers: list[Any] | None, status: str +) -> None: + """An answer existing in another partition is still wrong for this row.""" + table = _table(transform="floor(:value/3600)*3600") + table.id = 1 + context = MagicMock() + context.queries = [MagicMock()] + sample = SimpleNamespace( + status=QueryStatus.SUCCESS, + df=pd.DataFrame({"event_time": [4000, 8000], "dt_epoch": [3600, 7200]}), + ) + with ( + app.app_context(), + patch( + "superset.connectors.sqla.partition_validation.QueryContextFactory" + ) as factory, + patch.object(table, "query", return_value=sample), + patch( + "superset.connectors.sqla.partition_validation.evaluate_transform", + return_value=answers, + ) as probe, + ): + factory.return_value.create.return_value = context + result = validate_partition_data( + table, + mapped_column="event_time", + partition_column="dt_epoch", + value_transform="floor(:value/3600)*3600", + partition_values=["3600"], + ) + assert result["status"] == status + context.raise_for_access.assert_called_once() + request = factory.return_value.create.call_args.kwargs["queries"][0] + assert request["filters"] == [{"col": "dt_epoch", "op": "==", "val": 3600}] + assert request["columns"] == ["event_time", "dt_epoch"] + assert request["row_limit"] == 50 + assert "metrics" not in request + assert probe.call_args.kwargs["fresh"] is True + if status == "mismatch": + assert result["examples"][0]["stored_key"] == "3600" + if status == "inconclusive": + # The key at fault is named, not left to a generic message. + assert result["inconclusive_keys"] == ["3600"] + assert "3600" in str(result["message"]) + + +def test_validation_authorizes_before_reading_rows(app: Flask) -> None: + """An editor without data access cannot read sample pairs.""" + table = _table() + table.id = 1 + with ( + app.app_context(), + patch( + "superset.connectors.sqla.partition_validation.QueryContextFactory" + ) as factory, + patch.object(table, "query") as query, + ): + factory.return_value.create.return_value.raise_for_access.side_effect = ( + SupersetSecurityException(MagicMock()) + ) + with pytest.raises(SupersetSecurityException): + validate_partition_data( + table, + mapped_column="event_time", + partition_column="dt_epoch", + value_transform="unix_timestamp(:value)", + partition_values=["3600"], + ) + query.assert_not_called() + + +@pytest.mark.parametrize("values", [[], [None], [datetime(2026, 1, 15, 10, 47)]]) +def test_empty_null_or_failed_sample_cannot_pass(app: Flask, values: list[Any]) -> None: + """Empty coverage and failed evaluations are inconclusive, never matched.""" + table = _table() + table.id = 1 + context = MagicMock() + context.queries = [MagicMock()] + sample = SimpleNamespace( + status=QueryStatus.SUCCESS, + df=pd.DataFrame({"event_time": values, "dt_epoch": [3600] * len(values)}), + ) + with ( + app.app_context(), + patch( + "superset.connectors.sqla.partition_validation.QueryContextFactory" + ) as factory, + patch.object(table, "query", return_value=sample), + patch( + "superset.connectors.sqla.partition_validation.evaluate_transform", + return_value=None, + ), + ): + factory.return_value.create.return_value = context + assert ( + validate_partition_data( + table, + mapped_column="event_time", + partition_column="dt_epoch", + value_transform="unix_timestamp(:value)", + partition_values=["3600"], + )["status"] + == "inconclusive" + ) + + +def test_fresh_validation_does_not_read_or_write_probe_or_verdict_cache( + app: Flask, +) -> None: + """A previous successful probe is not evidence about these rows.""" + table = _table() + with ( + app.app_context(), + patch( + "superset.connectors.sqla.partition_mapping._probe", return_value=[3600] + ) as probe, + patch("superset.connectors.sqla.partition_mapping._cache_get") as cache_get, + patch("superset.connectors.sqla.partition_mapping._cache_set") as cache_set, + patch( + "superset.connectors.sqla.partition_mapping.record_mirror_verdict" + ) as verdict, + ): + assert evaluate_transform( + table.database, None, None, ":value", [4000, 4000], fresh=True + ) == [3600, 3600] + assert probe.call_args.args[4] == [4000] + cache_get.assert_not_called() + cache_set.assert_not_called() + verdict.assert_not_called() + + +def test_real_sampling_query_preserves_partition_filter_and_rls(app: Flask) -> None: + """Build and execute the sampling query, rather than mocking returned pairs.""" + import sqlite3 + + from superset.common.query_context import QueryContext + from superset.common.query_context_factory import QueryContextFactory + + table = _table(transform="floor(:value/3600)*3600") + table.id = 1 + table.columns[0].type = "BIGINT" + table.columns[0].is_dttm = False + connection = sqlite3.connect(":memory:") + connection.execute( + "CREATE TABLE web_events (event_time BIGINT, dt_epoch BIGINT, country TEXT)" + ) + connection.executemany( + "INSERT INTO web_events VALUES (?, ?, ?)", + [(4000, 3600, "allowed"), (4000, 7200, "hidden"), (8000, 7200, "allowed")], + ) + executed = [] + + def fetch(sql: str, *args: object, **kwargs: Any) -> pd.DataFrame: + executed.append(sql) + frame = pd.read_sql_query(sql, connection) + if mutator := kwargs.get("mutator"): + frame = mutator(frame) + return frame + + import sqlalchemy as sa + + with ( + app.app_context(), + patch.object(QueryContextFactory, "_convert_to_model", return_value=table), + patch.object(QueryContext, "raise_for_access"), + patch.object( + table, + "get_sqla_row_level_filters", + return_value=[sa.text("country = 'allowed'")], + ), + patch.object(Database, "get_df", side_effect=fetch), + ): + result = validate_partition_data( + table, + mapped_column="event_time", + partition_column="dt_epoch", + value_transform=":value - :value % 3600", + partition_values=["3600", "7200"], + ) + assert result["status"] == "matched" + assert result["checked_rows"] == 2 + reads = [sql for sql in executed if "FROM web_events" in sql] + assert len(reads) == 2 + assert all("LIMIT 50" in sql and "country = 'allowed'" in sql for sql in reads) + assert all("COUNT(" not in sql.upper() for sql in executed) + # A raw read stops at the limit; GROUP BY would aggregate the whole partition. + assert all("GROUP BY" not in sql.upper() for sql in reads) + connection.close() + + +def test_a_raw_instant_transform_is_refused_before_any_sample(app: Flask) -> None: + """ + Rows on the hour boundary match a raw-instant transform exactly, so sampled + rows alone could report as matched a transform the mirror declines. + """ + table = _table(transform="unix_timestamp(:value) * 1") + table.id = 1 + with ( + app.app_context(), + patch.object( + Database, + "db_engine_spec", + new_callable=PropertyMock, + return_value=HiveEngineSpec, + ), + patch( + "superset.connectors.sqla.partition_mapping._canary_answers", + return_value=[978343200, 978343200 + 2843], + ), + patch( + "superset.connectors.sqla.partition_validation.QueryContextFactory" + ) as factory, + patch.object(table, "query") as query, + ): + result = validate_partition_data( + table, + mapped_column="event_time", + partition_column="dt_epoch", + value_transform="unix_timestamp(:value) * 1", + partition_values=["3600"], + ) + assert result["status"] == "refused" + assert "Filters will not be mirrored" in str(result["message"]) + factory.assert_not_called() + query.assert_not_called() diff --git a/tests/unit_tests/datasets/partition_mapping_preview_test.py b/tests/unit_tests/datasets/partition_mapping_preview_test.py index b2a89e21a7fe..ae6b9f8e2d81 100644 --- a/tests/unit_tests/datasets/partition_mapping_preview_test.py +++ b/tests/unit_tests/datasets/partition_mapping_preview_test.py @@ -127,6 +127,7 @@ def test_preview_returns_the_emitted_predicate( assert response.status_code == 200 assert response.json["result"] == { + "data_validation": {"status": "not_checked"}, "valid": True, "sample_input": "event_time == '2026-01-15 00:00:00'", "emitted_predicate": "dt_epoch = 1768435200 OR dt_epoch IS NULL", @@ -152,6 +153,7 @@ def test_preview_mirrors_a_range_when_the_transform_is_monotonic( ) assert response.json["result"] == { + "data_validation": {"status": "not_checked"}, "valid": True, "sample_input": "event_time >= '2026-01-15 00:00:00'", "emitted_predicate": "dt_epoch >= 1768435200 OR dt_epoch IS NULL", @@ -183,6 +185,7 @@ def test_preview_shows_a_strict_bound_mirroring_non_strictly( ) assert response.json["result"] == { + "data_validation": {"status": "not_checked"}, "valid": True, "sample_input": "event_time < '2026-01-15 00:00:00'", "emitted_predicate": "dt_epoch <= 1768435200 OR dt_epoch IS NULL", @@ -265,6 +268,7 @@ def test_preview_mirrors_in_element_wise( ) assert response.json["result"] == { + "data_validation": {"status": "not_checked"}, "valid": True, "sample_input": "country IN ('US', 'CA')", "emitted_predicate": "region_key IN ('us', 'ca') OR region_key IS NULL", @@ -1483,6 +1487,7 @@ def test_preview_accepts_a_transform_that_reads_both_shapes_alike( ) assert response.json["result"] == { + "data_validation": {"status": "not_checked"}, "valid": True, "sample_input": "event_time >= '2026-01-15 10:47:23'", "emitted_predicate": "dt_epoch >= 1768474043 OR dt_epoch IS NULL", diff --git a/tests/unit_tests/datasets/partition_mapping_serialization_test.py b/tests/unit_tests/datasets/partition_mapping_serialization_test.py index ba08b7a4dd7c..0457aa2c79d1 100644 --- a/tests/unit_tests/datasets/partition_mapping_serialization_test.py +++ b/tests/unit_tests/datasets/partition_mapping_serialization_test.py @@ -109,6 +109,7 @@ def test_the_mapping_summary_survives_dashboard_payload_pruning(app: Flask) -> N data = _table().data_for_slices([]) assert data["partition_filter_mapping"] == { + "safety_refusal": None, "partition_column": "dt_epoch", "mapped_column": "event_time", "active": True, @@ -336,6 +337,7 @@ def test_the_mapping_summary_still_names_the_columns_when_inactive( summary = table.data["partition_filter_mapping"] assert summary == { + "safety_refusal": None, "partition_column": "dt_epoch", "mapped_column": "event_time", "active": False, diff --git a/tests/unit_tests/datasets/schema_tests.py b/tests/unit_tests/datasets/schema_tests.py index a2cf4b7962b5..bf803e2872c0 100644 --- a/tests/unit_tests/datasets/schema_tests.py +++ b/tests/unit_tests/datasets/schema_tests.py @@ -22,7 +22,10 @@ from marshmallow import ValidationError from pytest_mock import MockerFixture -from superset.datasets.schemas import validate_python_date_format +from superset.datasets.schemas import ( + validate_partition_key, + validate_python_date_format, +) # pylint: disable=too-few-public-methods @@ -449,3 +452,34 @@ def test_import_metric_schema_rejects_invalid_d3format() -> None: } ) assert "d3format" in exc.value.messages + + +@pytest.mark.parametrize( + "key", + ["0", "1768471200", "-9223372036854775808", "9223372036854775807"], +) +def test_validate_partition_key_accepts_64_bit_integers(key: str) -> None: + validate_partition_key(key) + + +@pytest.mark.parametrize( + "key", + [ + "1768471200\n", + " 1", + "1.0", + "", + "-", + "9223372036854775808", + "-9223372036854775809", + "123456789012345678901", + ], +) +def test_validate_partition_key_refuses_anything_else(key: str) -> None: + """A trailing newline passed a `$` anchor; out-of-range keys reached the engine.""" + with pytest.raises(ValidationError) as excinfo: + validate_partition_key(key) + # One message, not one per character of a lazy string. + assert excinfo.value.messages == [ + "A partition key must be a whole number within the 64-bit range." + ] diff --git a/tests/unit_tests/db_engine_specs/test_hive.py b/tests/unit_tests/db_engine_specs/test_hive.py index dc6f9b071e1b..d4f77f6fff5e 100644 --- a/tests/unit_tests/db_engine_specs/test_hive.py +++ b/tests/unit_tests/db_engine_specs/test_hive.py @@ -351,7 +351,7 @@ def test_spark_identifier_quote_uses_backticks() -> None: } -def test_the_partition_transform_default_is_hive_syntax() -> None: +def test_hive_does_not_guess_the_epoch_partition_bucket() -> None: """ `unix_timestamp(:value)` only belongs on engines where it parses. The one-argument form takes the bound being mirrored; the zero-argument form @@ -360,9 +360,9 @@ def test_the_partition_transform_default_is_hive_syntax() -> None: from superset.db_engine_specs.hive import HiveEngineSpec from superset.db_engine_specs.spark import SparkEngineSpec - assert HiveEngineSpec.partition_value_transform_default == "unix_timestamp(:value)" + assert HiveEngineSpec.partition_value_transform_default is None # Spark SQL inherits from Hive and shares the syntax. - assert SparkEngineSpec.partition_value_transform_default == "unix_timestamp(:value)" + assert SparkEngineSpec.partition_value_transform_default is None def test_the_partition_transform_default_is_not_inherited_by_presto() -> None: diff --git a/tests/unit_tests/db_engine_specs/test_impala.py b/tests/unit_tests/db_engine_specs/test_impala.py index f8603e2f5410..0ed72cbe5be3 100644 --- a/tests/unit_tests/db_engine_specs/test_impala.py +++ b/tests/unit_tests/db_engine_specs/test_impala.py @@ -286,12 +286,10 @@ def test_fetch_data_async_result_set(app_context: None, impala_errors: None) -> cursor.cancel_operation.assert_not_called() -def test_the_partition_transform_default_is_impala_syntax() -> None: +def test_impala_does_not_guess_the_epoch_partition_bucket() -> None: from superset.db_engine_specs.impala import ImpalaEngineSpec - assert ( - ImpalaEngineSpec.partition_value_transform_default == "unix_timestamp(:value)" - ) + assert ImpalaEngineSpec.partition_value_transform_default is None def test_engines_without_a_sensible_default_offer_none() -> None: diff --git a/tests/unit_tests/models/partition_mirroring_test.py b/tests/unit_tests/models/partition_mirroring_test.py index 8384cea96699..c5b827ea103e 100644 --- a/tests/unit_tests/models/partition_mirroring_test.py +++ b/tests/unit_tests/models/partition_mirroring_test.py @@ -24,6 +24,7 @@ from __future__ import annotations +from collections.abc import Callable from datetime import date, datetime from typing import Any from unittest.mock import MagicMock, patch @@ -43,7 +44,8 @@ from superset.utils import json from superset.utils.core import FilterOperator -PROBE = "superset.connectors.sqla.partition_mapping.evaluate_transform" +PARTITION_MAPPING = "superset.connectors.sqla.partition_mapping" +PROBE = f"{PARTITION_MAPPING}.evaluate_transform" @pytest.fixture(autouse=True) @@ -3278,3 +3280,352 @@ def test_an_array_partition_column_gets_no_mirror(app: Flask) -> None: # The filter itself is untouched: the query is still correct, it just reads # every partition. assert "dt_epoch = 5" in sql + + +# A comment the owner wrote is inert: it is removed before the probe compiles, +# so it costs neither rows nor pruning. +@pytest.mark.parametrize("comment", ["", " -- floor to the bucket"]) +@pytest.mark.parametrize("bucket", [3600, 86400]) +@pytest.mark.parametrize("operator", ["==", "IN", ">", ">=", "<", "<=", "range"]) +def test_epoch_bucket_queries_preserve_actual_rows( + app: Flask, bucket: int, operator: str, comment: str +) -> None: + """Execute generated SQL and compare rows against independently built keys.""" + import math + import sqlite3 + from datetime import timezone + + connection = sqlite3.connect(":memory:") + connection.create_function("floor", 1, math.floor) + connection.create_function( + "unix_timestamp", + 1, + lambda value: int( + datetime.fromisoformat(value).replace(tzinfo=timezone.utc).timestamp() + ), + ) + connection.execute( + "CREATE TABLE web_events (country TEXT, event_time TEXT, dt_epoch BIGINT)" + ) + origin = 1790812800 # 2026-10-01 UTC + seconds = [-1, 0, 1] + [ + origin + hour * 3600 + offset + for hour in range(48) + for offset in (0, 1, 1800, 3599) + ] + rows: list[tuple[str, str, int | None]] = [ + ( + str(index), + datetime.fromtimestamp(value, timezone.utc).strftime("%Y-%m-%d %H:%M:%S"), + value // bucket * bucket, + ) + for index, value in enumerate(seconds) + ] + rows.append(("null_partition", "2026-10-01 10:30:00", None)) + connection.executemany("INSERT INTO web_events VALUES (?, ?, ?)", rows) + table = _table( + transform=( + f"CAST(floor(unix_timestamp(:value) / {bucket}.0) * {bucket} AS BIGINT)" + f"{comment}" + ) + ) + if operator == "range": + arguments = { + "granularity": "event_time", + "from_dttm": datetime(2026, 10, 1, 10, 30), + "to_dttm": datetime(2026, 10, 2, 10, 30), + } + else: + arguments = { + "filter": [ + { + "col": "event_time", + "op": operator, + "val": ["2026-10-01 10:30:00", "2026-10-02 11:59:59"] + if operator == "IN" + else "2026-10-01 10:30:00", + } + ] + } + with ( + app.app_context(), + patch.object( + Database, + "get_df", + side_effect=lambda sql, **kwargs: pd.read_sql_query(sql, connection), + ), + ): + mirrored_sql = _query(table, **arguments) + table.partition_column = None + unmirrored_sql = _query(table, **arguments) + assert "dt_epoch" in mirrored_sql + assert sorted(connection.execute(mirrored_sql).fetchall()) == sorted( + connection.execute(unmirrored_sql).fetchall() + ) + connection.close() + + +@pytest.mark.parametrize("source_type", ["TIMESTAMP", "VARCHAR"]) +@pytest.mark.parametrize( + "transform", + [ + "unix_timestamp(:value)", + "CAST(unix_timestamp(:value) AS BIGINT)", + "unix_timestamp(:value) + 0", + "unix_timestamp(:value) * 1", + "floor(unix_timestamp(:value))", + ], +) +def test_a_raw_instant_transform_is_declined_onto_hourly_keys( + app: Flask, source_type: str, transform: str +) -> None: + """ + Any spelling of a raw-instant transform, on a timestamp or a text timestamp + column, would drop rows from hour-bucketed Hive keys; the canaries decline it. + """ + import math + import sqlite3 + from datetime import timezone + from unittest.mock import PropertyMock + + from superset.connectors.sqla.partition_mapping import ( + preview_partition_mapping, + ) + from superset.db_engine_specs.hive import HiveEngineSpec + + connection = sqlite3.connect(":memory:") + connection.create_function("floor", 1, math.floor) + connection.create_function( + "unix_timestamp", + 1, + lambda value: int( + datetime.fromisoformat(value).replace(tzinfo=timezone.utc).timestamp() + ), + ) + connection.execute( + "CREATE TABLE web_events (country TEXT, event_time TEXT, dt_epoch BIGINT)" + ) + origin = 1790812800 # 2026-10-01 UTC + connection.executemany( + "INSERT INTO web_events VALUES (?, ?, ?)", + [ + ( + str(offset), + datetime.fromtimestamp(origin + offset, timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ), + (origin + offset) // 3600 * 3600, + ) + for offset in (37800, 38000, 39599) + ], + ) + table = _table(transform=transform) + next(c for c in table.columns if c.column_name == "event_time").type = source_type + arguments = { + "filter": [{"col": "event_time", "op": "==", "val": "2026-10-01 10:33:20"}] + } + with ( + app.app_context(), + patch.object( + Database, + "get_df", + side_effect=lambda sql, **kwargs: pd.read_sql_query(sql, connection), + ), + ): + # Elsewhere a raw epoch column is an ordinary index, so it still mirrors. + assert "dt_epoch" in _query(table, **arguments) + with patch.object( + Database, + "db_engine_spec", + new_callable=PropertyMock, + return_value=HiveEngineSpec, + ): + mirrored_sql = _query(table, **arguments) + preview = preview_partition_mapping( + table, + mapped_column="event_time", + value_transform=transform, + sample_values=["2026-10-01 10:33:20"], + ) + table.partition_column = None + unmirrored_sql = _query(table, **arguments) + assert "dt_epoch" not in mirrored_sql + assert connection.execute(mirrored_sql).fetchall() == [("38000",)] + assert connection.execute(unmirrored_sql).fetchall() == [("38000",)] + assert preview["reason"] == "unbucketed_epoch" + connection.close() + + +@pytest.mark.parametrize( + "source_type, is_dttm, layout, transform, key_scale", + [ + # A text timestamp column left at the default, not flagged temporal. + ("VARCHAR", False, "%Y-%m-%d %H:%M:%S", "unix_timestamp(:value)", 1), + # Microsecond instants onto a microsecond hour key. + ( + "TIMESTAMP", + True, + "%Y-%m-%d %H:%M:%S", + "unix_timestamp(:value) * 1000000", + 1000000, + ), + # ISO text with a `T`, parsed by a transform that reads no other layout. + ("VARCHAR", True, "%Y-%m-%dT%H:%M:%S", "iso_seconds(:value)", 1), + ("VARCHAR", False, "%Y-%m-%dT%H:%M:%S", "iso_seconds(:value)", 1), + ], +) +def test_raw_instants_are_declined_in_every_unit_and_text_layout( + app: Flask, + source_type: str, + is_dttm: bool, + layout: str, + transform: str, + key_scale: int, +) -> None: + """ + Raw instants are declined whatever their unit, and on text columns whatever + their layout or temporal flag; each of these dropped rows from hourly keys. + """ + import sqlite3 + from datetime import timezone + from unittest.mock import PropertyMock + + from superset.connectors.sqla.partition_mapping import ( + preview_partition_mapping, + ) + from superset.db_engine_specs.hive import HiveEngineSpec + + def seconds_if(separator: str) -> Callable[[str], int | None]: + """Epoch seconds of text in one layout only, as Hive's parsers read it.""" + + def parse(value: str) -> int | None: + if not isinstance(value, str) or value[10:11] != separator: + return None + try: + parsed = datetime.fromisoformat(value) + except ValueError: + return None + return int(parsed.replace(tzinfo=timezone.utc).timestamp()) + + return parse + + connection = sqlite3.connect(":memory:") + connection.create_function("unix_timestamp", 1, seconds_if(" ")) + connection.create_function("iso_seconds", 1, seconds_if("T")) + connection.execute( + "CREATE TABLE web_events (country TEXT, event_time TEXT, dt_epoch BIGINT)" + ) + origin = 1790812800 # 2026-10-01 UTC + connection.executemany( + "INSERT INTO web_events VALUES (?, ?, ?)", + [ + ( + str(offset), + datetime.fromtimestamp(origin + offset, timezone.utc).strftime(layout), + (origin + offset) // 3600 * 3600 * key_scale, + ) + for offset in (37800, 38000, 39599) + ], + ) + table = _table(transform=transform) + event_time = next(c for c in table.columns if c.column_name == "event_time") + event_time.type = source_type + event_time.is_dttm = is_dttm + value = datetime(2026, 10, 1, 10, 33, 20).strftime(layout) + arguments = {"filter": [{"col": "event_time", "op": "==", "val": value}]} + probes: dict[str, Any] = {} + with ( + app.app_context(), + patch.object( + Database, + "get_df", + side_effect=lambda sql, **kwargs: pd.read_sql_query(sql, connection), + ), + patch(f"{PARTITION_MAPPING}._cache_get", side_effect=probes.get), + patch(f"{PARTITION_MAPPING}._cache_set", side_effect=probes.__setitem__), + ): + # Nothing probed yet, so the API has no refusal to report. + cold_summary = table.partition_filter_mapping_summary + assert cold_summary is not None + assert cold_summary["safety_refusal"] is None + # Elsewhere a raw epoch column is an ordinary index, so it still mirrors. + assert "dt_epoch" in _query(table, **arguments) + with patch.object( + Database, + "db_engine_spec", + new_callable=PropertyMock, + return_value=HiveEngineSpec, + ): + mirrored_sql = _query(table, **arguments) + preview = preview_partition_mapping( + table, + mapped_column="event_time", + value_transform=transform, + sample_values=[value], + ) + summary = table.partition_filter_mapping_summary + table.partition_column = None + unmirrored_sql = _query(table, **arguments) + assert "dt_epoch" not in mirrored_sql + assert connection.execute(mirrored_sql).fetchall() == [("38000",)] + assert connection.execute(unmirrored_sql).fetchall() == [("38000",)] + assert preview["reason"] == "unbucketed_epoch" + # The API reports the refusal the probe found, not only the editor. + assert summary is not None + assert summary["safety_refusal"]["reason"] == "unbucketed_epoch" + connection.close() + + +def test_a_text_layout_the_engine_rejects_does_not_hide_the_others() -> None: + """ + An engine raising on one text layout fails the batched canary probe; each + layout is then asked alone, so the one the transform parses still counts. + """ + from superset.connectors.sqla import partition_mapping + + canaries = ["a", "b", "c", "d"] + + def probe(database: Any, catalog: Any, schema: Any, transform: str, values: Any): + if values == canaries or values == ["a", "b"]: + return None + return [100, 100 + 2843] + + with patch.object(partition_mapping, "_probe", side_effect=probe): + answers = partition_mapping._probe_canaries(MagicMock(), "t(:value)", canaries) + assert answers == [None, None, 100, 2943] + assert partition_mapping._answers_raw_instants(answers) + + with patch.object(partition_mapping, "_probe", return_value=None): + assert ( + partition_mapping._probe_canaries(MagicMock(), "t(:value)", canaries) + is None + ) + + +@pytest.mark.parametrize( + "date_format, asked", + [ + ("%d/%m/%Y %H:%M:%S", True), + # Hive reads a backslash escape and ignores a doubled apostrophe, so + # the rendered literal would close early and the rest would run. + ("%Y\\' || reflect('java.lang.Thread', 'sleep', 1) || '", False), + ], +) +def test_a_date_format_reaches_the_probe_only_as_a_literal( + date_format: str, asked: bool +) -> None: + """ + The column's `python_date_format` is the dataset owner's text. Its canary + pair is asked only when it round-trips as a literal, as a filter value must. + """ + from superset.connectors.sqla import partition_mapping + + database = MagicMock() + database.backend = "hive" + source = MagicMock() + source.python_date_format = date_format + + canaries = partition_mapping._text_epoch_canaries(source, database) + + assert len(canaries) == (8 if asked else 6) + assert all(partition_mapping.value_round_trips(database, text) for text in canaries) diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 56f485e011e4..802bb0be7c11 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -24,7 +24,7 @@ import pytest import sqlglot from pytest_mock import MockerFixture -from sqlglot import Dialects, exp, parse_one +from sqlglot import Dialect, Dialects, exp, parse_one from superset.exceptions import QueryClauseValidationException, SupersetParseError from superset.jinja_context import JinjaTemplateProcessor @@ -53,6 +53,7 @@ SQLGLOT_DIALECTS, SQLScript, SQLStatement, + strip_comments, Table, tokenize_kql, transpile_to_dialect, @@ -7753,6 +7754,21 @@ def test_count_select_expressions(sql: str, engine: str, expected: int) -> None: assert SQLStatement(sql, engine).count_select_expressions() == expected +@pytest.mark.parametrize( + "sql, expected", + [ + ("SELECT (UNIX_TIMESTAMP(NULL)) -- note", True), + ("SELECT unix_timestamp( NULL )", True), + ("SELECT floor(unix_timestamp(NULL) / 3600) * 3600", False), + ("SELECT unix_timestamp(NULL), 1", False), + ], +) +def test_has_same_projection(sql: str, expected: bool) -> None: + """Check the `has_same_projection` method.""" + reference = SQLStatement("SELECT unix_timestamp(NULL)", "hive") + assert SQLStatement(sql, "hive").has_same_projection(reference) is expected + + @pytest.mark.parametrize( "sql, engine, expected", [ @@ -8038,3 +8054,47 @@ def test_get_select_literal_value(sql: str, engine: str, expected: str | None) - def test_has_comment(sql: str, engine: str, expected: bool) -> None: """Check the `has_comment` method.""" assert SQLStatement(sql, engine).has_comment() is expected + + +@pytest.mark.parametrize( + "sql, engine, expected", + [ + ( + "floor(:value / 3600) * 3600 -- hourly", + "hive", + "floor(:value / 3600) * 3600", + ), + ("a/* joined? */+b", "hive", "a +b"), + ( + "concat('a--b', '/* c */', :value)", + "hive", + "concat('a--b', '/* c */', :value)", + ), + ("lower(:value) # trailing", "mysql", "lower(:value)"), + ("-- leading\nlower(:value)", "postgresql", "lower(:value)"), + ("lower(:value)", "base", "lower(:value)"), + ("lower(:value) /* unterminated", "hive", None), + ("-- only a comment", "hive", ""), + (" ", "hive", ""), + ], +) +def test_strip_comments(sql: str, engine: str, expected: str | None) -> None: + """Comments go, literals and the placeholder stay exactly as written.""" + assert strip_comments(sql, engine) == expected + + +@pytest.mark.parametrize("relexed", [ValueError("no"), []]) +def test_strip_comments_refuses_what_does_not_relex_the_same( + mocker: MockerFixture, relexed: Exception | list[object] +) -> None: + """ + The stripped text is lexed again and must give back the same tokens; a + lexer that fails on it, or reads it differently, yields ``None`` rather + than a guess. + """ + tokens = Dialect.get_or_raise("hive").tokenize("lower(:value) -- c") + lexer = mocker.MagicMock() + lexer.tokenize.side_effect = [tokens, relexed] + mocker.patch("superset.sql.parse.Dialect.get_or_raise", return_value=lexer) + + assert strip_comments("lower(:value) -- c", "hive") is None