diff --git a/.github/workflows/revised-its.yml b/.github/workflows/revised-its.yml index c762a6016514..62aac48dc994 100644 --- a/.github/workflows/revised-its.yml +++ b/.github/workflows/revised-its.yml @@ -50,7 +50,7 @@ jobs: matrix: #jdk: [8, 11, 17] jdk: [8] - it: [HighAvailability, MultiStageQuery, Catalog, BatchIndex, MultiStageQueryWithMM, InputSource, InputFormat] + it: [HighAvailability, MultiStageQuery, Catalog, BatchIndex, MultiStageQueryWithMM, InputSource, InputFormat, Security] #indexer: [indexer, middleManager] indexer: [middleManager] uses: ./.github/workflows/reusable-revised-its.yml diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index c77d15888ed1..a87000ac07e0 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -34,7 +34,7 @@ env: MVN: mvn -B MAVEN_SKIP: -P skip-static-checks -Dweb.console.skip=true -Dmaven.javadoc.skip=true MAVEN_SKIP_TESTS: -P skip-tests - MAVEN_OPTS: -Xmx3000m + MAVEN_OPTS: -Xmx8g jobs: static-checks: @@ -144,6 +144,28 @@ jobs: --levels ERROR \ --scope JavaInspectionsScope + openrewrite: + runs-on: ubuntu-latest + steps: + - name: checkout branch + uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '8' + cache: 'maven' + + - name: maven install + run: | + echo 'Running Maven install...' && + ${MVN} clean install -q -ff -pl '!distribution' ${MAVEN_SKIP} ${MAVEN_SKIP_TESTS} -T1C && + ${MVN} install -q -ff -pl 'distribution' ${MAVEN_SKIP} ${MAVEN_SKIP_TESTS} + + - name: rewrite:dryRun + run: | + ${MVN} rewrite:dryRun ${MAVEN_SKIP} + web-checks: strategy: fail-fast: false diff --git a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java index 7b3e71aecec8..c04d4e31f959 100644 --- a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java +++ b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java @@ -213,7 +213,12 @@ public String getFormatString() // 40: regex filtering "SELECT string4, COUNT(*) FROM foo WHERE REGEXP_EXTRACT(string1, '^1') IS NOT NULL OR REGEXP_EXTRACT('Z' || string2, '^Z2') IS NOT NULL GROUP BY 1", // 41: complicated filtering - "SELECT string2, SUM(long1) FROM foo WHERE string1 = '1000' AND string5 LIKE '%1%' AND (string3 in ('1', '10', '20', '22', '32') AND long2 IN (1, 19, 21, 23, 25, 26, 46) AND double3 < 1010.0 AND double3 > 1000.0 AND (string4 = '1' OR REGEXP_EXTRACT(string1, '^1') IS NOT NULL OR REGEXP_EXTRACT('Z' || string2, '^Z2') IS NOT NULL)) GROUP BY 1 ORDER BY 2" + "SELECT string2, SUM(long1) FROM foo WHERE string1 = '1000' AND string5 LIKE '%1%' AND (string3 in ('1', '10', '20', '22', '32') AND long2 IN (1, 19, 21, 23, 25, 26, 46) AND double3 < 1010.0 AND double3 > 1000.0 AND (string4 = '1' OR REGEXP_EXTRACT(string1, '^1') IS NOT NULL OR REGEXP_EXTRACT('Z' || string2, '^Z2') IS NOT NULL)) GROUP BY 1 ORDER BY 2", + // 42: array_contains expr + "SELECT ARRAY_CONTAINS(\"multi-string3\", 100) FROM foo", + "SELECT ARRAY_CONTAINS(\"multi-string3\", ARRAY[1, 2, 10, 11, 20, 22, 30, 33, 40, 44, 50, 55, 100]) FROM foo", + "SELECT ARRAY_OVERLAP(\"multi-string3\", ARRAY[1, 100]) FROM foo", + "SELECT ARRAY_OVERLAP(\"multi-string3\", ARRAY[1, 2, 10, 11, 20, 22, 30, 33, 40, 44, 50, 55, 100]) FROM foo" ); @Param({"5000000"}) @@ -275,7 +280,11 @@ public String getFormatString() "38", "39", "40", - "41" + "41", + "42", + "43", + "44", + "45" }) private String query; @@ -369,8 +378,8 @@ public void setup() .writeValueAsString(jsonMapper.readValue((String) planResult[0], List.class)) ); } - catch (JsonProcessingException e) { - throw new RuntimeException(e); + catch (JsonProcessingException ignored) { + } try (final DruidPlanner planner = plannerFactory.createPlannerForTesting(engine, sql, ImmutableMap.of())) { @@ -384,6 +393,9 @@ public void setup() } log.info("Total result row count:" + rowCounter); } + catch (Throwable ignored) { + + } } @TearDown(Level.Trial) diff --git a/docs/api-reference/data-management-api.md b/docs/api-reference/data-management-api.md index 4adeaa8b2083..754bf62f725a 100644 --- a/docs/api-reference/data-management-api.md +++ b/docs/api-reference/data-management-api.md @@ -206,7 +206,8 @@ Marks the state of a group of segments as unused, using an array of segment IDs Pass the array of segment IDs or interval as a JSON object in the request body. For the interval, specify the start and end times as ISO 8601 strings to identify segments inclusive of the start time and exclusive of the end time. -Druid only updates the segments completely contained within the specified interval; partially overlapping segments are not affected. +Optionally, specify an array of segment versions with interval. Druid updates only the segments completely contained +within the specified interval that match the optional list of versions; partially overlapping segments are not affected. #### URL @@ -214,12 +215,13 @@ Druid only updates the segments completely contained within the specified interv #### Request body -The group of segments is sent as a JSON request payload that accepts one of the following properties: +The group of segments is sent as a JSON request payload that accepts the following properties: -|Property|Description|Example| -|----------|-------------|---------| -|`interval`|ISO 8601 segments interval.|`"2015-09-12T03:00:00.000Z/2015-09-12T05:00:00.000Z"`| -|`segmentIds`|Array of segment IDs.|`["segmentId1", "segmentId2"]`| +|Property|Description|Required|Example| +|----------|-------------|---------|---------| +|`interval`|ISO 8601 segments interval.|Yes, if `segmentIds` is not specified.|`"2015-09-12T03:00:00.000Z/2015-09-12T05:00:00.000Z"`| +|`segmentIds`|List of segment IDs.|Yes, if `interval` is not specified.|`["segmentId1", "segmentId2"]`| +|`versions`|List of segment versions. Must be provided with `interval`.|No.|`["2024-03-14T16:00:04.086Z", ""2024-03-12T16:00:04.086Z"]`| #### Responses @@ -306,7 +308,8 @@ Marks the state of a group of segments as used, using an array of segment IDs or Pass the array of segment IDs or interval as a JSON object in the request body. For the interval, specify the start and end times as ISO 8601 strings to identify segments inclusive of the start time and exclusive of the end time. -Druid only updates the segments completely contained within the specified interval; partially overlapping segments are not affected. +Optionally, specify an array of segment versions with interval. Druid updates only the segments completely contained +within the specified interval that match the optional list of versions; partially overlapping segments are not affected. #### URL @@ -314,12 +317,13 @@ Druid only updates the segments completely contained within the specified interv #### Request body -The group of segments is sent as a JSON request payload that accepts one of the following properties: +The group of segments is sent as a JSON request payload that accepts the following properties: -|Property|Description|Example| -|----------|-------------|---------| -|`interval`| ISO 8601 segments interval.|`"2015-09-12T03:00:00.000Z/2015-09-12T05:00:00.000Z"`| -|`segmentIds`|Array of segment IDs.|`["segmentId1", "segmentId2"]`| +|Property|Description|Required|Example| +|----------|-------------|---------|---------| +|`interval`|ISO 8601 segments interval.|Yes, if `segmentIds` is not specified.|`"2015-09-12T03:00:00.000Z/2015-09-12T05:00:00.000Z"`| +|`segmentIds`|List of segment IDs.|Yes, if `interval` is not specified.|`["segmentId1", "segmentId2"]`| +|`versions`|List of segment versions. Must be provided with `interval`.|No.|`["2024-03-14T16:00:04.086Z", ""2024-03-12T16:00:04.086Z"]`| #### Responses diff --git a/docs/development/extensions-core/lookups-cached-global.md b/docs/development/extensions-core/lookups-cached-global.md index 15e1469de4a8..5cfcbea01c24 100644 --- a/docs/development/extensions-core/lookups-cached-global.md +++ b/docs/development/extensions-core/lookups-cached-global.md @@ -211,8 +211,8 @@ The remapping values for each globally cached lookup can be specified by a JSON |Property|Description|Required|Default| |--------|-----------|--------|-------| |`pollPeriod`|Period between polling for updates|No|0 (only once)| -|`uri`|URI for the file of interest, specified as a file, hdfs, s3 or gs path|No|Use `uriPrefix`| -|`uriPrefix`|A URI that specifies a directory (or other searchable resource) in which to search for files|No|Use `uri`| +|`uri`|URI for the lookup file. Can be a file, HDFS, S3 or GCS path|Either `uri` or `uriPrefix` must be set|None| +|`uriPrefix`|A URI prefix that specifies a directory or other searchable resource where lookup files are located |Either `uri` or `uriPrefix` must be set|None| |`fileRegex`|Optional regex for matching the file name under `uriPrefix`. Only used if `uriPrefix` is used|No|`".*"`| |`namespaceParseSpec`|How to interpret the data at the URI|Yes|| |`maxHeapPercentage`|The maximum percentage of heap size that the lookup should consume. If the lookup grows beyond this size, warning messages will be logged in the respective service logs.|No|10% of JVM heap size| diff --git a/docs/multi-stage-query/concepts.md b/docs/multi-stage-query/concepts.md index 27b7d12c91c9..cae88a0f3750 100644 --- a/docs/multi-stage-query/concepts.md +++ b/docs/multi-stage-query/concepts.md @@ -200,8 +200,8 @@ To perform ingestion with rollup: 2. Set [`finalizeAggregations: false`](reference.md#context-parameters) in your context. This causes aggregation functions to write their internal state to the generated segments, instead of the finalized end result, and enables further aggregation at query time. -3. See [ARRAY types](../querying/arrays.md#sql-based-ingestion-with-rollup) for information about ingesting `ARRAY` columns -4. See [multi-value dimensions](../querying/multi-value-dimensions.md#sql-based-ingestion-with-rollup) for information to ingest multi-value VARCHAR columns +3. See [ARRAY types](../querying/arrays.md#sql-based-ingestion) for information about ingesting `ARRAY` columns +4. See [multi-value dimensions](../querying/multi-value-dimensions.md#sql-based-ingestion) for information to ingest multi-value VARCHAR columns When you do all of these things, Druid understands that you intend to do an ingestion with rollup, and it writes rollup-related metadata into the generated segments. Other applications can then use [`segmentMetadata` diff --git a/docs/multi-stage-query/reference.md b/docs/multi-stage-query/reference.md index 45dfa464416f..0b10e14b50f9 100644 --- a/docs/multi-stage-query/reference.md +++ b/docs/multi-stage-query/reference.md @@ -351,7 +351,7 @@ The following table lists the context parameters for the MSQ task engine: | `maxNumTasks` | SELECT, INSERT, REPLACE

The maximum total number of tasks to launch, including the controller task. The lowest possible value for this setting is 2: one controller and one worker. All tasks must be able to launch simultaneously. If they cannot, the query returns a `TaskStartTimeout` error code after approximately 10 minutes.

May also be provided as `numTasks`. If both are present, `maxNumTasks` takes priority. | 2 | | `taskAssignment` | SELECT, INSERT, REPLACE

Determines how many tasks to use. Possible values include: | `max` | | `finalizeAggregations` | SELECT, INSERT, REPLACE

Determines the type of aggregation to return. If true, Druid finalizes the results of complex aggregations that directly appear in query results. If false, Druid returns the aggregation's intermediate type rather than finalized type. This parameter is useful during ingestion, where it enables storing sketches directly in Druid tables. For more information about aggregations, see [SQL aggregation functions](../querying/sql-aggregations.md). | `true` | -| `arrayIngestMode` | INSERT, REPLACE

Controls how ARRAY type values are stored in Druid segments. When set to `array` (recommended for SQL compliance), Druid will store all ARRAY typed values in [ARRAY typed columns](../querying/arrays.md), and supports storing both VARCHAR and numeric typed arrays. When set to `mvd` (the default, for backwards compatibility), Druid only supports VARCHAR typed arrays, and will store them as [multi-value string columns](../querying/multi-value-dimensions.md). When set to `none`, Druid will throw an exception when trying to store any type of arrays. `none` is most useful when set in the system default query context with (`druid.query.default.context.arrayIngestMode=none`) to be used to help migrate operators from `mvd` mode to `array` mode and force query writers to make an explicit choice between ARRAY and multi-value VARCHAR typed columns. | `mvd` (for backwards compatibility, recommended to use `array` for SQL compliance)| +| `arrayIngestMode` | INSERT, REPLACE

Controls how ARRAY type values are stored in Druid segments. When set to `array` (recommended for SQL compliance), Druid will store all ARRAY typed values in [ARRAY typed columns](../querying/arrays.md), and supports storing both VARCHAR and numeric typed arrays. When set to `mvd` (the default, for backwards compatibility), Druid only supports VARCHAR typed arrays, and will store them as [multi-value string columns](../querying/multi-value-dimensions.md). See [`arrayIngestMode`] in the [Arrays](../querying/arrays.md) page for more details. | `mvd` (for backwards compatibility, recommended to use `array` for SQL compliance)| | `sqlJoinAlgorithm` | SELECT, INSERT, REPLACE

Algorithm to use for JOIN. Use `broadcast` (the default) for broadcast hash join or `sortMerge` for sort-merge join. Affects all JOIN operations in the query. This is a hint to the MSQ engine and the actual joins in the query may proceed in a different way than specified. See [Joins](#joins) for more details. | `broadcast` | | `rowsInMemory` | INSERT or REPLACE

Maximum number of rows to store in memory at once before flushing to disk during the segment generation process. Ignored for non-INSERT queries. In most cases, use the default value. You may need to override the default if you run into one of the [known issues](./known-issues.md) around memory usage. | 100,000 | | `segmentSortOrder` | INSERT or REPLACE

Normally, Druid sorts rows in individual segments using `__time` first, followed by the [CLUSTERED BY](#clustered-by) clause. When you set `segmentSortOrder`, Druid sorts rows in segments using this column list first, followed by the CLUSTERED BY order.

You provide the column list as comma-separated values or as a JSON array in string form. If your query includes `__time`, then this list must begin with `__time`. For example, consider an INSERT query that uses `CLUSTERED BY country` and has `segmentSortOrder` set to `__time,city`. Within each time chunk, Druid assigns rows to segments based on `country`, and then within each of those segments, Druid sorts those rows by `__time` first, then `city`, then `country`. | empty list | @@ -364,6 +364,7 @@ The following table lists the context parameters for the MSQ task engine: | `waitUntilSegmentsLoad` | INSERT, REPLACE

If set, the ingest query waits for the generated segment to be loaded before exiting, else the ingest query exits without waiting. The task and live reports contain the information about the status of loading segments if this flag is set. This will ensure that any future queries made after the ingestion exits will include results from the ingestion. The drawback is that the controller task will stall till the segments are loaded. | `false` | | `includeSegmentSource` | SELECT, INSERT, REPLACE

Controls the sources, which will be queried for results in addition to the segments present on deep storage. Can be `NONE` or `REALTIME`. If this value is `NONE`, only non-realtime (published and used) segments will be downloaded from deep storage. If this value is `REALTIME`, results will also be included from realtime tasks. | `NONE` | | `rowsPerPage` | SELECT

The number of rows per page to target. The actual number of rows per page may be somewhat higher or lower than this number. In most cases, use the default.
This property comes into effect only when `selectDestination` is set to `durableStorage` | 100000 | +| `skipTypeVerification` | INSERT or REPLACE

During query validation, Druid validates that [string arrays](../querying/arrays.md) and [multi-value dimensions](../querying/multi-value-dimensions.md) are not mixed in the same column. If you are intentionally migrating from one to the other, use this context parameter to disable type validation.

Provide the column list as comma-separated values or as a JSON array in string form.| empty list | | `failOnEmptyInsert` | INSERT or REPLACE

When set to false (the default), an INSERT query generating no output rows will be no-op, and a REPLACE query generating no output rows will delete all data that matches the OVERWRITE clause. When set to true, an ingest query generating no output rows will throw an `InsertCannotBeEmpty` fault. | `false` | ## Joins diff --git a/docs/querying/arrays.md b/docs/querying/arrays.md index dbeb3ec6e028..a7eebaa32afe 100644 --- a/docs/querying/arrays.md +++ b/docs/querying/arrays.md @@ -71,9 +71,46 @@ The following shows an example `dimensionsSpec` for native ingestion of the data ### SQL-based ingestion -Arrays can also be inserted with [SQL-based ingestion](../multi-stage-query/index.md) when you include a query context parameter [`"arrayIngestMode":"array"`](../multi-stage-query/reference.md#context-parameters). +#### `arrayIngestMode` + +Arrays can be inserted with [SQL-based ingestion](../multi-stage-query/index.md) when you include the query context +parameter `arrayIngestMode: array`. + +When `arrayIngestMode` is `array`, SQL ARRAY types are stored using Druid array columns. This is recommended for new +tables. + +When `arrayIngestMode` is `mvd`, SQL `VARCHAR ARRAY` are implicitly wrapped in [`ARRAY_TO_MV`](sql-functions.md#array_to_mv). +This causes them to be stored as [multi-value strings](multi-value-dimensions.md), using the same `STRING` column type +as regular scalar strings. SQL `BIGINT ARRAY` and `DOUBLE ARRAY` cannot be loaded under `arrayIngestMode: mvd`. This +is the default behavior when `arrayIngestMode` is not provided in your query context, although the default behavior +may change to `array` in a future release. + +When `arrayIngestMode` is `none`, Druid throws an exception when trying to store any type of arrays. This mode is most +useful when set in the system default query context with `druid.query.default.context.arrayIngestMode = none`, in cases +where the cluster administrator wants SQL query authors to explicitly provide one or the other in their query context. + +The following table summarizes the differences in SQL ARRAY handling between `arrayIngestMode: array` and +`arrayIngestMode: mvd`. + +| SQL type | Stored type when `arrayIngestMode: array` | Stored type when `arrayIngestMode: mvd` (default) | +|---|---|---| +|`VARCHAR ARRAY`|`ARRAY`|[multi-value `STRING`](multi-value-dimensions.md)| +|`BIGINT ARRAY`|`ARRAY`|not possible (validation error)| +|`DOUBLE ARRAY`|`ARRAY`|not possible (validation error)| + +In either mode, you can explicitly wrap string arrays in `ARRAY_TO_MV` to cause them to be stored as +[multi-value strings](multi-value-dimensions.md). + +When validating a SQL INSERT or REPLACE statement that contains arrays, Druid checks whether the statement would lead +to mixing string arrays and multi-value strings in the same column. If this condition is detected, the statement fails +validation unless the column is named under the `skipTypeVerification` context parameter. This parameter can be either +a comma-separated list of column names, or a JSON array in string form. This validation is done to prevent accidentally +mixing arrays and multi-value strings in the same column. + +#### Examples + +Set [`arrayIngestMode: array`](#arrayingestmode) in your query context to run the following examples. -For example, to insert the data used in this document: ```sql REPLACE INTO "array_example" OVERWRITE ALL WITH "ext" AS ( @@ -81,9 +118,14 @@ WITH "ext" AS ( FROM TABLE( EXTERN( '{"type":"inline","data":"{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row1\", \"arrayString\": [\"a\", \"b\"], \"arrayLong\":[1, null,3], \"arrayDouble\":[1.1, 2.2, null]}\n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row2\", \"arrayString\": [null, \"b\"], \"arrayLong\":null, \"arrayDouble\":[999, null, 5.5]}\n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row3\", \"arrayString\": [], \"arrayLong\":[1, 2, 3], \"arrayDouble\":[null, 2.2, 1.1]} \n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row4\", \"arrayString\": [\"a\", \"b\"], \"arrayLong\":[1, 2, 3], \"arrayDouble\":[]}\n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row5\", \"arrayString\": null, \"arrayLong\":[], \"arrayDouble\":null}"}', - '{"type":"json"}', - '[{"name":"timestamp", "type":"STRING"},{"name":"label", "type":"STRING"},{"name":"arrayString", "type":"ARRAY"},{"name":"arrayLong", "type":"ARRAY"},{"name":"arrayDouble", "type":"ARRAY"}]' + '{"type":"json"}' ) + ) EXTEND ( + "timestamp" VARCHAR, + "label" VARCHAR, + "arrayString" VARCHAR ARRAY, + "arrayLong" BIGINT ARRAY, + "arrayDouble" DOUBLE ARRAY ) ) SELECT @@ -96,8 +138,7 @@ FROM "ext" PARTITIONED BY DAY ``` -### SQL-based ingestion with rollup -These input arrays can also be grouped for rollup: +Arrays can also be used as `GROUP BY` keys for rollup: ```sql REPLACE INTO "array_example_rollup" OVERWRITE ALL @@ -106,9 +147,14 @@ WITH "ext" AS ( FROM TABLE( EXTERN( '{"type":"inline","data":"{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row1\", \"arrayString\": [\"a\", \"b\"], \"arrayLong\":[1, null,3], \"arrayDouble\":[1.1, 2.2, null]}\n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row2\", \"arrayString\": [null, \"b\"], \"arrayLong\":null, \"arrayDouble\":[999, null, 5.5]}\n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row3\", \"arrayString\": [], \"arrayLong\":[1, 2, 3], \"arrayDouble\":[null, 2.2, 1.1]} \n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row4\", \"arrayString\": [\"a\", \"b\"], \"arrayLong\":[1, 2, 3], \"arrayDouble\":[]}\n{\"timestamp\": \"2023-01-01T00:00:00\", \"label\": \"row5\", \"arrayString\": null, \"arrayLong\":[], \"arrayDouble\":null}"}', - '{"type":"json"}', - '[{"name":"timestamp", "type":"STRING"},{"name":"label", "type":"STRING"},{"name":"arrayString", "type":"ARRAY"},{"name":"arrayLong", "type":"ARRAY"},{"name":"arrayDouble", "type":"ARRAY"}]' + '{"type":"json"}' ) + ) EXTEND ( + "timestamp" VARCHAR, + "label" VARCHAR, + "arrayString" VARCHAR ARRAY, + "arrayLong" BIGINT ARRAY, + "arrayDouble" DOUBLE ARRAY ) ) SELECT diff --git a/docs/querying/multi-value-dimensions.md b/docs/querying/multi-value-dimensions.md index 2b33737a36fc..1ce3a618dac7 100644 --- a/docs/querying/multi-value-dimensions.md +++ b/docs/querying/multi-value-dimensions.md @@ -507,9 +507,9 @@ Avoid confusing string arrays with [multi-value dimensions](multi-value-dimensio Use care during ingestion to ensure you get the type you want. -To get arrays when performing an ingestion using JSON ingestion specs, such as [native batch](../ingestion/native-batch.md) or streaming ingestion such as with [Apache Kafka](../ingestion/kafka-ingestion.md), use dimension type `auto` or enable `useSchemaDiscovery`. When performing a [SQL-based ingestion](../multi-stage-query/index.md), write a query that generates arrays and set the context parameter `"arrayIngestMode": "array"`. Arrays may contain strings or numbers. +To get arrays when performing an ingestion using JSON ingestion specs, such as [native batch](../ingestion/native-batch.md) or streaming ingestion such as with [Apache Kafka](../ingestion/kafka-ingestion.md), use dimension type `auto` or enable `useSchemaDiscovery`. When performing a [SQL-based ingestion](../multi-stage-query/index.md), write a query that generates arrays and set the context parameter [`"arrayIngestMode": "array"`](arrays.md#arrayingestmode). Arrays may contain strings or numbers. -To get multi-value dimensions when performing an ingestion using JSON ingestion specs, use dimension type `string` and do not enable `useSchemaDiscovery`. When performing a [SQL-based ingestion](../multi-stage-query/index.md), wrap arrays in [`ARRAY_TO_MV`](multi-value-dimensions.md#sql-based-ingestion), which ensures you get multi-value dimensions in any `arrayIngestMode`. Multi-value dimensions can only contain strings. +To get multi-value dimensions when performing an ingestion using JSON ingestion specs, use dimension type `string` and do not enable `useSchemaDiscovery`. When performing a [SQL-based ingestion](../multi-stage-query/index.md), wrap arrays in [`ARRAY_TO_MV`](multi-value-dimensions.md#sql-based-ingestion), which ensures you get multi-value dimensions in any [`arrayIngestMode`](arrays.md#arrayingestmode). Multi-value dimensions can only contain strings. You can tell which type you have by checking the `INFORMATION_SCHEMA.COLUMNS` table, using a query like: diff --git a/docs/tutorials/tutorial-append-data.md b/docs/tutorials/tutorial-append-data.md new file mode 100644 index 000000000000..acdb05442516 --- /dev/null +++ b/docs/tutorials/tutorial-append-data.md @@ -0,0 +1,133 @@ +--- +id: tutorial-append-data +title: Append data +sidebar_label: Append data +description: Learn how to append data to a datasource without changing the existing data in Apache Druid. +--- + + + +This tutorial shows you how to use the Apache Druid SQL [INSERT](../multi-stage-query/reference.md#insert) function to append data to a [datasource](../design/storage.md) without changing the existing data. +The examples in the tutorial use the [multi-stage query (MSQ)](../multi-stage-query/index.md) task engine to executes SQL statements. + +## Prerequisites + +Before you follow the steps in this tutorial, download Druid as described in [Quickstart (local)](index.md) and have it running on your local machine. You don't need to load any data into the Druid cluster. + +You should be familiar with data querying in Druid. If you haven't already, go through the [Query data](../tutorials/tutorial-query.md) tutorial first. + +## Load sample data + +Load a sample dataset using [INSERT](../multi-stage-query/reference.md#insert) and [EXTERN](../multi-stage-query/reference.md#extern-function) functions. The EXTERN function lets you read external data or write to an external location. + +In the Druid [web console](../operations/web-console.md), go to the **Query** view and run the following query: + +```sql +INSERT INTO "append_tutorial" +SELECT + TIME_PARSE("timestamp") AS "__time", + "animal", + "number" +FROM TABLE( + EXTERN( + '{"type":"inline","data":"{\"timestamp\":\"2024-01-01T07:01:35Z\",\"animal\":\"octopus\", \"number\":115}\n{\"timestamp\":\"2024-01-01T05:01:35Z\",\"animal\":\"mongoose\", \"number\":737}\n{\"timestamp\":\"2024-01-01T06:01:35Z\",\"animal\":\"snake\", \"number\":1234}\n{\"timestamp\":\"2024-01-01T01:01:35Z\",\"animal\":\"lion\", \"number\":300}\n{\"timestamp\":\"2024-01-02T07:01:35Z\",\"animal\":\"seahorse\", \"number\":115}\n{\"timestamp\":\"2024-01-02T05:01:35Z\",\"animal\":\"skunk\", \"number\":737}\n{\"timestamp\":\"2024-01-02T06:01:35Z\",\"animal\":\"iguana\", \"number\":1234}\n{\"timestamp\":\"2024-01-02T01:01:35Z\",\"animal\":\"opossum\", \"number\":300}"}', + '{"type":"json"}' + ) + ) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT) +PARTITIONED BY DAY +``` + +The resulting `append_tutorial` datasource contains records for eight animals over two days. +To view the results, open a new tab and run the following query: + +```sql +SELECT * FROM "append_tutorial" +``` + +
+ View the results + +| `__time` | `animal` | `number`| +| -- | -- | -- | +| `2024-01-01T01:01:35.000Z`| `lion`| 300 | +| `2024-01-01T05:01:35.000Z`| `mongoose`| 737 | +| `2024-01-01T06:01:35.000Z`| `snake`| 1234 | +| `2024-01-01T07:01:35.000Z`| `octopus`| 115 | +| `2024-01-02T01:01:35.000Z`| `opossum`| 300 | +| `2024-01-02T05:01:35.000Z`| `skunk`| 737 | +| `2024-01-02T06:01:35.000Z`| `iguana`| 1234 | +| `2024-01-02T07:01:35.000Z`| `seahorse`| 115 | + +
+ +## Append data + +You can use the INSERT function to append data to the datasource without changing the existing data. +In a new tab, run the following query to ingest and append data to the `append_tutorial` datasource: + +```sql +INSERT INTO "append_tutorial" +SELECT + TIME_PARSE("timestamp") AS "__time", + "animal", + "number" +FROM TABLE( + EXTERN( + '{"type":"inline","data":"{\"timestamp\":\"2024-01-03T01:09:35Z\",\"animal\":\"zebra\", \"number\":233}\n{\"timestamp\":\"2024-01-04T07:01:35Z\",\"animal\":\"bear\", \"number\":577}\n{\"timestamp\":\"2024-01-04T05:01:35Z\",\"animal\":\"falcon\", \"number\":848}\n{\"timestamp\":\"2024-01-04T06:01:35Z\",\"animal\":\"giraffe\", \"number\":113}\n{\"timestamp\":\"2024-01-04T01:01:35Z\",\"animal\":\"rhino\", \"number\":473}"}', + '{"type":"json"}' + ) + ) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT) +PARTITIONED BY DAY +``` + +Druid adds rows for the subsequent days after `seahorse`. +When the task completes, open a new tab and run the following query to view the results: + +```sql +SELECT * FROM "append_tutorial" +``` + +
+ View the results + +| `__time` | `animal` | `number`| +| -- | -- | -- | +| `2024-01-01T01:01:35.000Z`| `lion`| 300 | +| `2024-01-01T05:01:35.000Z`| `mongoose`| 737 | +| `2024-01-01T06:01:35.000Z`| `snake`| 1234 | +| `2024-01-01T07:01:35.000Z`| `octopus`| 115 | +| `2024-01-02T01:01:35.000Z`| `opossum`| 300 | +| `2024-01-02T05:01:35.000Z`| `skunk`| 737 | +| `2024-01-02T06:01:35.000Z`| `iguana`| 1234 | +| `2024-01-02T07:01:35.000Z`| `seahorse`| 115 | +| `2024-01-03T01:09:35.000Z`| `zebra`| 233 | +| `2024-01-04T01:01:35.000Z`| `rhino`| 473 | +| `2024-01-04T05:01:35.000Z`| `falcon`| 848 | +| `2024-01-04T06:01:35.000Z`| `giraffe`| 113 | +| `2024-01-04T07:01:35.000Z`| `bear`| 577 | + +
+ +## Learn more + +See the following topics for more information: + +* [SQL-based ingestion reference](../multi-stage-query/reference.md) for a reference on MSQ architecture. +* [SQL-based ingestion query examples](../multi-stage-query/examples.md) for example queries using the MSQ task engine. \ No newline at end of file diff --git a/docs/tutorials/tutorial-update-data.md b/docs/tutorials/tutorial-update-data.md index aa85a2aca7ce..e761cd2a95d7 100644 --- a/docs/tutorials/tutorial-update-data.md +++ b/docs/tutorials/tutorial-update-data.md @@ -1,7 +1,8 @@ --- id: tutorial-update-data -title: Update existing data -sidebar_label: Update existing data +title: Update data +sidebar_label: Update data +description: Learn how to update data in Apache Druid. --- +Apache Druid stores data and indexes in [segment files](../design/segments.md) partitioned by time. +After Druid creates a segment, its contents can't be modified. +You can either replace data for the whole segment, or, in some cases, overshadow a portion of the segment data. -This tutorial shows you how to update data in a datasource by overwriting existing data and adding new data to the datasource. +In Druid, use time ranges to specify the data you want to update, as opposed to a primary key or dimensions often used in transactional databases. Data outside the specified replacement time range remains unaffected. +You can use this Druid functionality to perform data updates, inserts, and deletes, similar to UPSERT functionality for transactional databases. -## Prerequisites - -Before starting this tutorial, download and run Apache Druid on your local machine as described in -the [single-machine quickstart](index.md). - -You should also be familiar with the material in the following tutorials: -* [Load a file](../tutorials/tutorial-batch.md) -* [Query data](../tutorials/tutorial-query.md) -* [Rollup](../tutorials/tutorial-rollup.md) +This tutorial shows you how to use the Druid SQL [REPLACE](../multi-stage-query/reference.md#replace) function with the OVERWRITE clause to update existing data. -## Load initial data +The tutorial walks you through the following use cases: -Load an initial data set to which you will overwrite and append data. +* [Overwrite all data](#overwrite-all-data) +* [Overwrite records for a specific time range](#overwrite-records-for-a-specific-time-range) +* [Update a row using partial segment overshadowing](#update-a-row-using-partial-segment-overshadowing) -The ingestion spec is located at `quickstart/tutorial/updates-init-index.json`. This spec creates a datasource called `updates-tutorial` and ingests data from `quickstart/tutorial/updates-data.json`. +All examples use the [multi-stage query (MSQ)](../multi-stage-query/index.md) task engine to executes SQL statements. -Submit the ingestion task: - -```bash -bin/post-index-task --file quickstart/tutorial/updates-init-index.json --url http://localhost:8081 -``` +## Prerequisites -Start the SQL command-line client: -```bash -bin/dsql -``` +Before you follow the steps in this tutorial, download Druid as described in [Quickstart (local)](index.md) and have it running on your local machine. You don't need to load any data into the Druid cluster. -Run the following SQL query to retrieve data from `updates-tutorial`: - -```bash -dsql> SELECT * FROM "updates-tutorial"; -┌──────────────────────────┬──────────┬───────┬────────┐ -│ __time │ animal │ count │ number │ -├──────────────────────────┼──────────┼───────┼────────┤ -│ 2018-01-01T01:01:00.000Z │ tiger │ 1 │ 100 │ -│ 2018-01-01T03:01:00.000Z │ aardvark │ 1 │ 42 │ -│ 2018-01-01T03:01:00.000Z │ giraffe │ 1 │ 14124 │ -└──────────────────────────┴──────────┴───────┴────────┘ -Retrieved 3 rows in 1.42s. -``` +You should be familiar with data querying in Druid. If you haven't already, go through the [Query data](../tutorials/tutorial-query.md) tutorial first. -The datasource contains three rows of data with an `animal` dimension and a `number` metric. +## Load sample data -## Overwrite data +Load a sample dataset using [REPLACE](../multi-stage-query/reference.md#replace) and [EXTERN](../multi-stage-query/reference.md#extern-function) functions. +In Druid SQL, the REPLACE function can create a new [datasource](../design/storage.md) or update an existing datasource. -To overwrite the data, submit another task for the same interval but with different input data. +In the Druid [web console](../operations/web-console.md), go to the **Query** view and run the following query: -The `quickstart/tutorial/updates-overwrite-index.json` spec performs an overwrite on the `updates-tutorial` datasource. +```sql +REPLACE INTO "update_tutorial" OVERWRITE ALL +WITH "ext" AS ( + SELECT * + FROM TABLE( + EXTERN( + '{"type":"inline","data":"{\"timestamp\":\"2024-01-01T07:01:35Z\",\"animal\":\"octopus\", \"number\":115}\n{\"timestamp\":\"2024-01-01T05:01:35Z\",\"animal\":\"mongoose\", \"number\":737}\n{\"timestamp\":\"2024-01-01T06:01:35Z\",\"animal\":\"snake\", \"number\":1234}\n{\"timestamp\":\"2024-01-01T01:01:35Z\",\"animal\":\"lion\", \"number\":300}\n{\"timestamp\":\"2024-01-02T07:01:35Z\",\"animal\":\"seahorse\", \"number\":115}\n{\"timestamp\":\"2024-01-02T05:01:35Z\",\"animal\":\"skunk\", \"number\":737}\n{\"timestamp\":\"2024-01-02T06:01:35Z\",\"animal\":\"iguana\", \"number\":1234}\n{\"timestamp\":\"2024-01-02T01:01:35Z\",\"animal\":\"opossum\", \"number\":300}"}', + '{"type":"json"}' + ) + ) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT) +) +SELECT + TIME_PARSE("timestamp") AS "__time", + "animal", + "number" +FROM "ext" +PARTITIONED BY DAY -In the overwrite ingestion spec, notice the following: -* The `intervals` field remains the same: `"intervals" : ["2018-01-01/2018-01-03"]` -* New data is loaded from the local file, `quickstart/tutorial/updates-data2.json` -* `appendToExisting` is set to `false`, indicating an overwrite task +``` -Submit the ingestion task to overwrite the data: +In the resulting `update_tutorial` datasource, individual rows are uniquely identified by `__time`, `animal`, and `number`. +To view the results, open a new tab and run the following query: -```bash -bin/post-index-task --file quickstart/tutorial/updates-overwrite-index.json --url http://localhost:8081 +```sql +SELECT * FROM "update_tutorial" ``` -When Druid finishes loading the new segment from this overwrite task, run the SELECT query again. -In the new results, the `tiger` row now has the value `lion`, the `aardvark` row has a different number, and the `giraffe` row has been replaced with a `bear` row. - -```bash -dsql> SELECT * FROM "updates-tutorial"; -┌──────────────────────────┬──────────┬───────┬────────┐ -│ __time │ animal │ count │ number │ -├──────────────────────────┼──────────┼───────┼────────┤ -│ 2018-01-01T01:01:00.000Z │ lion │ 1 │ 100 │ -│ 2018-01-01T03:01:00.000Z │ aardvark │ 1 │ 9999 │ -│ 2018-01-01T04:01:00.000Z │ bear │ 1 │ 111 │ -└──────────────────────────┴──────────┴───────┴────────┘ -Retrieved 3 rows in 0.02s. +
+ View the results + +| `__time` | `animal` | `number`| +| -- | -- | -- | +| `2024-01-01T01:01:35.000Z`| `lion`| 300 | +| `2024-01-01T05:01:35.000Z`| `mongoose`| 737 | +| `2024-01-01T06:01:35.000Z`| `snake`| 1234 | +| `2024-01-01T07:01:35.000Z`| `octopus`| 115 | +| `2024-01-02T01:01:35.000Z`| `opossum`| 300 | +| `2024-01-02T05:01:35.000Z`| `skunk`| 737 | +| `2024-01-02T06:01:35.000Z`| `iguana`| 1234 | +| `2024-01-02T07:01:35.000Z`| `seahorse`| 115 | + +
+ +The results contain records for eight animals over two days. + +## Overwrite all data + +You can use the REPLACE function with OVERWRITE ALL to replace the entire datasource with new data while dropping the old data. + +In the web console, open a new tab and run the following query to overwrite timestamp data for the entire `update_tutorial` datasource: + +```sql +REPLACE INTO "update_tutorial" OVERWRITE ALL +WITH "ext" AS (SELECT * +FROM TABLE( + EXTERN( + '{"type":"inline","data":"{\"timestamp\":\"2024-01-02T07:01:35Z\",\"animal\":\"octopus\", \"number\":115}\n{\"timestamp\":\"2024-01-02T05:01:35Z\",\"animal\":\"mongoose\", \"number\":737}\n{\"timestamp\":\"2024-01-02T06:01:35Z\",\"animal\":\"snake\", \"number\":1234}\n{\"timestamp\":\"2024-01-02T01:01:35Z\",\"animal\":\"lion\", \"number\":300}\n{\"timestamp\":\"2024-01-03T07:01:35Z\",\"animal\":\"seahorse\", \"number\":115}\n{\"timestamp\":\"2024-01-03T05:01:35Z\",\"animal\":\"skunk\", \"number\":737}\n{\"timestamp\":\"2024-01-03T06:01:35Z\",\"animal\":\"iguana\", \"number\":1234}\n{\"timestamp\":\"2024-01-03T01:01:35Z\",\"animal\":\"opossum\", \"number\":300}"}', + '{"type":"json"}' + ) +) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT)) +SELECT + TIME_PARSE("timestamp") AS "__time", + "animal", + "number" +FROM "ext" +PARTITIONED BY DAY ``` -## Combine existing data with new data and overwrite - -Now append new data to the `updates-tutorial` datasource from `quickstart/tutorial/updates-data3.json` using the ingestion spec `quickstart/tutorial/updates-append-index.json`. - -The spec directs Druid to read from the existing `updates-tutorial` datasource as well as the `quickstart/tutorial/updates-data3.json` file. The task combines data from the two input sources, then overwrites the original data with the new combined data. - -Submit that task: - -```bash -bin/post-index-task --file quickstart/tutorial/updates-append-index.json --url http://localhost:8081 +
+ View the results + +| `__time` | `animal` | `number`| +| -- | -- | -- | +| `2024-01-02T01:01:35.000Z`| `lion`| 300 | +| `2024-01-02T05:01:35.000Z`| `mongoose`| 737 | +| `2024-01-02T06:01:35.000Z`| `snake`| 1234 | +| `2024-01-02T07:01:35.000Z`| `octopus`| 115 | +| `2024-01-03T01:01:35.000Z`| `opossum`| 300 | +| `2024-01-03T05:01:35.000Z`| `skunk`| 737 | +| `2024-01-03T06:01:35.000Z`| `iguana`| 1234 | +| `2024-01-03T07:01:35.000Z`| `seahorse`| 115 | + +
+ +Note that the values in the `__time` column have changed to one day later. + +## Overwrite records for a specific time range + +You can use the REPLACE function to overwrite a specific time range of a datasource. When you overwrite a specific time range, that time range must align with the granularity specified in the PARTITIONED BY clause. + +In the web console, open a new tab and run the following query to insert a new row and update specific rows. Note that the OVERWRITE WHERE clause tells the query to only update records for the date 2024-01-03. + +```sql +REPLACE INTO "update_tutorial" + OVERWRITE WHERE "__time" >= TIMESTAMP'2024-01-03 00:00:00' AND "__time" < TIMESTAMP'2024-01-04 00:00:00' +WITH "ext" AS (SELECT * +FROM TABLE( + EXTERN( + '{"type":"inline","data":"{\"timestamp\":\"2024-01-03T01:01:35Z\",\"animal\":\"tiger\", \"number\":300}\n{\"timestamp\":\"2024-01-03T07:01:35Z\",\"animal\":\"seahorse\", \"number\":500}\n{\"timestamp\":\"2024-01-03T05:01:35Z\",\"animal\":\"polecat\", \"number\":626}\n{\"timestamp\":\"2024-01-03T06:01:35Z\",\"animal\":\"iguana\", \"number\":300}\n{\"timestamp\":\"2024-01-03T01:01:35Z\",\"animal\":\"flamingo\", \"number\":999}"}', + '{"type":"json"}' + ) +) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT)) +SELECT + TIME_PARSE("timestamp") AS "__time", + "animal", + "number" +FROM "ext" +PARTITIONED BY DAY ``` -When Druid finishes loading the new segment from this overwrite task, it adds the new rows to the datasource. -Run the SELECT query again. Druid automatically rolls up the data at ingestion time, aggregating the data in the `lion` row: - -```bash -dsql> SELECT * FROM "updates-tutorial"; -┌──────────────────────────┬──────────┬───────┬────────┐ -│ __time │ animal │ count │ number │ -├──────────────────────────┼──────────┼───────┼────────┤ -│ 2018-01-01T01:01:00.000Z │ lion │ 2 │ 400 │ -│ 2018-01-01T03:01:00.000Z │ aardvark │ 1 │ 9999 │ -│ 2018-01-01T04:01:00.000Z │ bear │ 1 │ 111 │ -│ 2018-01-01T05:01:00.000Z │ mongoose │ 1 │ 737 │ -│ 2018-01-01T06:01:00.000Z │ snake │ 1 │ 1234 │ -│ 2018-01-01T07:01:00.000Z │ octopus │ 1 │ 115 │ -└──────────────────────────┴──────────┴───────┴────────┘ -Retrieved 6 rows in 0.02s. +
+ View the results + +| `__time` | `animal` | `number`| +| -- | -- | -- | +| `2024-01-02T01:01:35.000Z`| `lion`| 300 | +| `2024-01-02T05:01:35.000Z`| `mongoose`| 737 | +| `2024-01-02T06:01:35.000Z`| `snake`| 1234 | +| `2024-01-02T07:01:35.000Z`| `octopus`| 115 | +| `2024-01-03T01:01:35.000Z`| `flamingo`| 999 | +| `2024-01-03T01:01:35.000Z`| `tiger`| 300 | +| `2024-01-03T05:01:35.000Z`| `polecat`| 626 | +| `2024-01-03T06:01:35.000Z`| `iguana`| 300 | +| `2024-01-03T07:01:35.000Z`| `seahorse`| 500 | + +
+ +Note the changes in the resulting datasource: + +* There is now a new row called `flamingo`. +* The `opossum` row has the value `tiger`. +* The `skunk` row has the value `polecat`. +* The `iguana` and `seahorse` rows have different numbers. + +## Update a row using partial segment overshadowing + +In Druid, you can overlay older data with newer data for the entire segment or portions of the segment within a particular partition. +This capability is called [overshadowing](../ingestion/tasks.md#overshadowing-between-segments). + +You can use partial overshadowing to update a single row by adding a smaller time granularity segment on top of the existing data. +It's a less common variation on a more common approach where you replace the entire time chunk. + +The following example demonstrates how update data using partial overshadowing with mixed segment granularity. +Note the following important points about the example: + +* The query updates a single record for a specific `number` row. +* The original datasource uses DAY segment granularity. +* The new data segment is at HOUR granularity and represents a time range that's smaller than the existing data. +* The OVERWRITE WHERE and WHERE TIME_IN_INTERVAL clauses specify the destination where the update occurs and the source of the update, respectively. +* The query replaces everything within the specified interval. To update only a subset of data in that interval, you have to carry forward all records, changing only what you want to change. You can accomplish that by using the [CASE](../querying/sql-functions.md#case) function in the SELECT list. + +```sql +REPLACE INTO "update_tutorial" + OVERWRITE + WHERE "__time" >= TIMESTAMP'2024-01-03 05:00:00' AND "__time" < TIMESTAMP'2024-01-03 06:00:00' +SELECT + "__time", + "animal", + CAST(486 AS BIGINT) AS "number" +FROM "update_tutorial" +WHERE TIME_IN_INTERVAL("__time", '2024-01-03T05:01:35Z/PT1S') +PARTITIONED BY FLOOR(__time TO HOUR) ``` -## Append data +
+ View the results -Now you append data to the datasource without changing the existing data. -Use the ingestion spec located at `quickstart/tutorial/updates-append-index2.json`. +| `__time` | `animal` | `number`| +| -- | -- | -- | +| `2024-01-02T01:01:35.000Z`| `lion`| 300 | +| `2024-01-02T05:01:35.000Z`| `mongoose`| 737 | +| `2024-01-02T06:01:35.000Z`| `snake`| 1234 | +| `2024-01-02T07:01:35.000Z`| `octopus`| 115 | +| `2024-01-03T01:01:35.000Z`| `flamingo`| 999 | +| `2024-01-03T01:01:35.000Z`| `tiger`| 300 | +| `2024-01-03T05:01:35.000Z`| `polecat`| 486 | +| `2024-01-03T06:01:35.000Z`| `iguana`| 300 | +| `2024-01-03T07:01:35.000Z`| `seahorse`| 500 | -The spec directs Druid to ingest data from `quickstart/tutorial/updates-data4.json` and append it to the `updates-tutorial` datasource. The property `appendToExisting` is set to `true` in this spec. +
-Submit the task: +Note that the `number` for `polecat` has changed from 626 to 486. -```bash -bin/post-index-task --file quickstart/tutorial/updates-append-index2.json --url http://localhost:8081 -``` +When you perform partial segment overshadowing multiple times, you can create segment fragmentation that could affect query performance. Use [compaction](../data-management/compaction.md) to correct any fragmentation. -Druid adds two additional rows after `octopus`. When the task completes, query the data again to see them. -Druid doesn't roll up the new `bear` row with the existing `bear` row because it stored the new data in a separate segment. - -```bash -dsql> SELECT * FROM "updates-tutorial"; -┌──────────────────────────┬──────────┬───────┬────────┐ -│ __time │ animal │ count │ number │ -├──────────────────────────┼──────────┼───────┼────────┤ -│ 2018-01-01T01:01:00.000Z │ lion │ 2 │ 400 │ -│ 2018-01-01T03:01:00.000Z │ aardvark │ 1 │ 9999 │ -│ 2018-01-01T04:01:00.000Z │ bear │ 1 │ 111 │ -│ 2018-01-01T05:01:00.000Z │ mongoose │ 1 │ 737 │ -│ 2018-01-01T06:01:00.000Z │ snake │ 1 │ 1234 │ -│ 2018-01-01T07:01:00.000Z │ octopus │ 1 │ 115 │ -│ 2018-01-01T04:01:00.000Z │ bear │ 1 │ 222 │ -│ 2018-01-01T09:01:00.000Z │ falcon │ 1 │ 1241 │ -└──────────────────────────┴──────────┴───────┴────────┘ -Retrieved 8 rows in 0.02s. -``` +## Learn more -Run the following groupBy query to see that the `bear` rows group together at query time: - -```bash -dsql> SELECT __time, animal, SUM("count"), SUM("number") FROM "updates-tutorial" GROUP BY __time, animal; -┌──────────────────────────┬──────────┬────────┬────────┐ -│ __time │ animal │ EXPR$2 │ EXPR$3 │ -├──────────────────────────┼──────────┼────────┼────────┤ -│ 2018-01-01T01:01:00.000Z │ lion │ 2 │ 400 │ -│ 2018-01-01T03:01:00.000Z │ aardvark │ 1 │ 9999 │ -│ 2018-01-01T04:01:00.000Z │ bear │ 2 │ 333 │ -│ 2018-01-01T05:01:00.000Z │ mongoose │ 1 │ 737 │ -│ 2018-01-01T06:01:00.000Z │ snake │ 1 │ 1234 │ -│ 2018-01-01T07:01:00.000Z │ octopus │ 1 │ 115 │ -│ 2018-01-01T09:01:00.000Z │ falcon │ 1 │ 1241 │ -└──────────────────────────┴──────────┴────────┴────────┘ -Retrieved 7 rows in 0.23s. -``` +See the following topics for more information: + +* [Data updates](../data-management/update.md) for an overview of updating data in Druid. +* [Load files with SQL-based ingestion](../tutorials/tutorial-msq-extern.md) for generating a query that references externally hosted data. +* [Overwrite data with REPLACE](../multi-stage-query/concepts.md#overwrite-data-with-replace) for details on how the MSQ task engine executes SQL REPLACE queries. \ No newline at end of file diff --git a/extensions-contrib/compressed-bigdecimal/pom.xml b/extensions-contrib/compressed-bigdecimal/pom.xml index 5dfaf7c38e17..c70fb2251cc7 100644 --- a/extensions-contrib/compressed-bigdecimal/pom.xml +++ b/extensions-contrib/compressed-bigdecimal/pom.xml @@ -63,6 +63,36 @@ + + junit + junit + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + org.apache.druid druid-processing @@ -91,11 +121,6 @@ test-jar test - - junit - junit - test - org.hamcrest java-hamcrest diff --git a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMaxSqlAggregatorTest.java b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMaxSqlAggregatorTest.java index 06709040b645..1223135ab27d 100644 --- a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMaxSqlAggregatorTest.java +++ b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMaxSqlAggregatorTest.java @@ -19,11 +19,17 @@ package org.apache.druid.compressedbigdecimal; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + + public class CompressedBigDecimalMaxSqlAggregatorTest extends CompressedBigDecimalSqlAggregatorTestBase { private static final String FUNCTION_NAME = CompressedBigDecimalMaxSqlAggregator.NAME; @Override + @Test public void testCompressedBigDecimalAggWithNumberParse() { testCompressedBigDecimalAggWithNumberParseHelper( @@ -34,15 +40,19 @@ public void testCompressedBigDecimalAggWithNumberParse() } @Override + @Test public void testCompressedBigDecimalAggWithStrictNumberParse() { - testCompressedBigDecimalAggWithStrictNumberParseHelper( - FUNCTION_NAME, - CompressedBigDecimalMaxAggregatorFactory::new - ); + assertThrows(NumberFormatException.class, () -> { + testCompressedBigDecimalAggWithStrictNumberParseHelper( + FUNCTION_NAME, + CompressedBigDecimalMaxAggregatorFactory::new + ); + }); } @Override + @Test public void testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScale() { testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScaleHelper( @@ -53,6 +63,7 @@ public void testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScale() } @Override + @Test public void testCompressedBigDecimalAggDefaultScale() { testCompressedBigDecimalAggDefaultScaleHelper( @@ -63,6 +74,7 @@ public void testCompressedBigDecimalAggDefaultScale() } @Override + @Test public void testCompressedBigDecimalAggDefaultSizeAndScale() { testCompressedBigDecimalAggDefaultSizeAndScaleHelper( diff --git a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMinSqlAggregatorTest.java b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMinSqlAggregatorTest.java index 3a18dd49652b..e739b928f2e0 100644 --- a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMinSqlAggregatorTest.java +++ b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalMinSqlAggregatorTest.java @@ -19,11 +19,16 @@ package org.apache.druid.compressedbigdecimal; +import org.junit.jupiter.api.Test; + +import static org.junit.Assert.assertThrows; + public class CompressedBigDecimalMinSqlAggregatorTest extends CompressedBigDecimalSqlAggregatorTestBase { private static final String FUNCTION_NAME = CompressedBigDecimalMinSqlAggregator.NAME; @Override + @Test public void testCompressedBigDecimalAggWithNumberParse() { testCompressedBigDecimalAggWithNumberParseHelper( @@ -34,15 +39,19 @@ public void testCompressedBigDecimalAggWithNumberParse() } @Override + @Test public void testCompressedBigDecimalAggWithStrictNumberParse() { - testCompressedBigDecimalAggWithStrictNumberParseHelper( - FUNCTION_NAME, - CompressedBigDecimalMinAggregatorFactory::new - ); + assertThrows(NumberFormatException.class, () -> { + testCompressedBigDecimalAggWithStrictNumberParseHelper( + FUNCTION_NAME, + CompressedBigDecimalMinAggregatorFactory::new + ); + }); } @Override + @Test public void testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScale() { testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScaleHelper( @@ -53,6 +62,7 @@ public void testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScale() } @Override + @Test public void testCompressedBigDecimalAggDefaultScale() { testCompressedBigDecimalAggDefaultScaleHelper( @@ -63,6 +73,7 @@ public void testCompressedBigDecimalAggDefaultScale() } @Override + @Test public void testCompressedBigDecimalAggDefaultSizeAndScale() { testCompressedBigDecimalAggDefaultSizeAndScaleHelper( diff --git a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSqlAggregatorTestBase.java b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSqlAggregatorTestBase.java index 2acc0d88e8db..baa9c0f32e8c 100644 --- a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSqlAggregatorTestBase.java +++ b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSqlAggregatorTestBase.java @@ -48,9 +48,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -80,11 +79,11 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { QueryableIndex index = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -120,7 +119,8 @@ public void configureJsonMapper(ObjectMapper objectMapper) @Test public abstract void testCompressedBigDecimalAggWithNumberParse(); - @Test(expected = NumberFormatException.class) + // expected: NumberFormatException.class + @Test public abstract void testCompressedBigDecimalAggWithStrictNumberParse(); @Test diff --git a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSumSqlAggregatorTest.java b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSumSqlAggregatorTest.java index db248ef67f0e..8970f002ffe1 100644 --- a/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSumSqlAggregatorTest.java +++ b/extensions-contrib/compressed-bigdecimal/src/test/java/org/apache/druid/compressedbigdecimal/CompressedBigDecimalSumSqlAggregatorTest.java @@ -19,11 +19,16 @@ package org.apache.druid.compressedbigdecimal; +import org.junit.jupiter.api.Test; + +import static org.junit.Assert.assertThrows; + public class CompressedBigDecimalSumSqlAggregatorTest extends CompressedBigDecimalSqlAggregatorTestBase { private static final String FUNCTION_NAME = CompressedBigDecimalSumSqlAggregator.NAME; @Override + @Test public void testCompressedBigDecimalAggWithNumberParse() { testCompressedBigDecimalAggWithNumberParseHelper( @@ -34,15 +39,19 @@ public void testCompressedBigDecimalAggWithNumberParse() } @Override + @Test public void testCompressedBigDecimalAggWithStrictNumberParse() { - testCompressedBigDecimalAggWithStrictNumberParseHelper( - FUNCTION_NAME, - CompressedBigDecimalSumAggregatorFactory::new - ); + assertThrows(NumberFormatException.class, () -> { + testCompressedBigDecimalAggWithStrictNumberParseHelper( + FUNCTION_NAME, + CompressedBigDecimalSumAggregatorFactory::new + ); + }); } @Override + @Test public void testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScale() { testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScaleHelper( @@ -53,6 +62,7 @@ public void testCompressedBigDecimalAggDefaultNumberParseAndCustomSizeAndScale() } @Override + @Test public void testCompressedBigDecimalAggDefaultScale() { testCompressedBigDecimalAggDefaultScaleHelper( @@ -63,6 +73,7 @@ public void testCompressedBigDecimalAggDefaultScale() } @Override + @Test public void testCompressedBigDecimalAggDefaultSizeAndScale() { testCompressedBigDecimalAggDefaultSizeAndScaleHelper( diff --git a/extensions-contrib/materialized-view-maintenance/src/main/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisor.java b/extensions-contrib/materialized-view-maintenance/src/main/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisor.java index 333868780157..7e0eaf60d836 100644 --- a/extensions-contrib/materialized-view-maintenance/src/main/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisor.java +++ b/extensions-contrib/materialized-view-maintenance/src/main/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisor.java @@ -27,6 +27,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import org.apache.druid.error.DruidException; +import org.apache.druid.error.EntryAlreadyExists; import org.apache.druid.indexer.TaskStatus; import org.apache.druid.indexing.common.task.HadoopIndexTask; import org.apache.druid.indexing.overlord.DataSourceMetadata; @@ -47,7 +49,6 @@ import org.apache.druid.java.util.common.concurrent.Execs; import org.apache.druid.java.util.common.guava.Comparators; import org.apache.druid.java.util.emitter.EmittingLogger; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.MetadataSupervisorManager; import org.apache.druid.metadata.SqlSegmentsMetadataManager; import org.apache.druid.timeline.DataSegment; @@ -443,8 +444,15 @@ private void submitTasks( runningTasks.put(entry.getKey(), task); } } - catch (EntryExistsException e) { - log.error("task %s already exsits", task); + catch (DruidException e) { + if (EntryAlreadyExists.ERROR_CODE.equals(e.getErrorCode())) { + log.error("Task[%s] already exists", task.getId()); + } else { + throw e; + } + } + catch (RuntimeException e) { + throw e; } catch (Exception e) { throw new RuntimeException(e); diff --git a/extensions-contrib/materialized-view-maintenance/src/test/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisorTest.java b/extensions-contrib/materialized-view-maintenance/src/test/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisorTest.java index 1e74180ae69f..64070b11dc87 100644 --- a/extensions-contrib/materialized-view-maintenance/src/test/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisorTest.java +++ b/extensions-contrib/materialized-view-maintenance/src/test/java/org/apache/druid/indexing/materializedview/MaterializedViewSupervisorTest.java @@ -25,8 +25,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; +import junit.framework.AssertionFailedError; import org.apache.druid.data.input.impl.DimensionsSpec; import org.apache.druid.data.input.impl.StringDimensionSchema; +import org.apache.druid.error.EntryAlreadyExists; import org.apache.druid.indexer.HadoopIOConfig; import org.apache.druid.indexer.HadoopIngestionSpec; import org.apache.druid.indexer.HadoopTuningConfig; @@ -37,6 +39,7 @@ import org.apache.druid.indexing.overlord.TaskQueue; import org.apache.druid.indexing.overlord.TaskStorage; import org.apache.druid.indexing.overlord.supervisor.SupervisorStateManagerConfig; +import org.apache.druid.java.util.common.ISE; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.java.util.common.Pair; import org.apache.druid.metadata.IndexerSQLMetadataStorageCoordinator; @@ -58,11 +61,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -71,10 +75,9 @@ public class MaterializedViewSupervisorTest { @Rule - public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule(); + public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule + = new TestDerbyConnector.DerbyConnectorRule(); - @Rule - public final ExpectedException expectedException = ExpectedException.none(); private TaskStorage taskStorage; private TaskMaster taskMaster; private IndexerMetadataStorageCoordinator indexerMetadataStorageCoordinator; @@ -102,7 +105,7 @@ public void setUp() metadataSupervisorManager = EasyMock.createMock(MetadataSupervisorManager.class); sqlSegmentsMetadataManager = EasyMock.createMock(SqlSegmentsMetadataManager.class); taskQueue = EasyMock.createMock(TaskQueue.class); - taskQueue.start(); + objectMapper.registerSubtypes(new NamedType(HashBasedNumberedShardSpec.class, "hashed")); spec = new MaterializedViewSupervisorSpec( "base", @@ -113,7 +116,7 @@ public void setUp() null, null, null, - null, + Collections.singletonMap("maxTaskCount", 2), false, objectMapper, taskMaster, @@ -133,125 +136,81 @@ public void setUp() @Test public void testCheckSegments() throws IOException { - Set baseSegments = Sets.newHashSet( - new DataSegment( - "base", - Intervals.of("2015-01-01T00Z/2015-01-02T00Z"), - "2015-01-02", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ), - new DataSegment( - "base", - Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), - "2015-01-03", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ), - new DataSegment( - "base", - Intervals.of("2015-01-03T00Z/2015-01-04T00Z"), - "2015-01-04", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ) - ); - Set derivativeSegments = Sets.newHashSet( - new DataSegment( - derivativeDatasourceName, - Intervals.of("2015-01-01T00Z/2015-01-02T00Z"), - "2015-01-02", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ), - new DataSegment( - derivativeDatasourceName, - Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), - "3015-01-01", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ) + List baseSegments = createBaseSegments(); + Set derivativeSegments = Sets.newHashSet(createDerivativeSegments()); + + final Interval day1 = baseSegments.get(0).getInterval(); + final Interval day2 = new Interval(day1.getStart().plusDays(1), day1.getEnd().plusDays(1)); + + indexerMetadataStorageCoordinator.commitSegments(new HashSet<>(baseSegments)); + indexerMetadataStorageCoordinator.commitSegments(derivativeSegments); + EasyMock.expect(taskMaster.getTaskQueue()).andReturn(Optional.of(taskQueue)).anyTimes(); + EasyMock.expect(taskMaster.getTaskRunner()).andReturn(Optional.absent()).anyTimes(); + EasyMock.expect(taskStorage.getActiveTasks()).andReturn(ImmutableList.of()).anyTimes(); + + Pair, Map>> toBuildInterval + = supervisor.checkSegments(); + + Map> expectedSegments = ImmutableMap.of( + day1, Collections.singletonList(baseSegments.get(0)), + day2, Collections.singletonList(baseSegments.get(1)) ); + Assert.assertEquals(Collections.singleton(day1), toBuildInterval.lhs.keySet()); + Assert.assertEquals(expectedSegments, toBuildInterval.rhs); + } + + @Test + public void testSubmitTasksDoesNotFailIfTaskAlreadyExists() throws IOException + { + Set baseSegments = Sets.newHashSet(createBaseSegments()); + Set derivativeSegments = Sets.newHashSet(createDerivativeSegments()); + indexerMetadataStorageCoordinator.commitSegments(baseSegments); indexerMetadataStorageCoordinator.commitSegments(derivativeSegments); EasyMock.expect(taskMaster.getTaskQueue()).andReturn(Optional.of(taskQueue)).anyTimes(); EasyMock.expect(taskMaster.getTaskRunner()).andReturn(Optional.absent()).anyTimes(); EasyMock.expect(taskStorage.getActiveTasks()).andReturn(ImmutableList.of()).anyTimes(); - Pair, Map>> toBuildInterval = supervisor.checkSegments(); - Set expectedToBuildInterval = Sets.newHashSet(Intervals.of("2015-01-01T00Z/2015-01-02T00Z")); - Map> expectedSegments = new HashMap<>(); - expectedSegments.put( - Intervals.of("2015-01-01T00Z/2015-01-02T00Z"), - Collections.singletonList( - new DataSegment( - "base", - Intervals.of("2015-01-01T00Z/2015-01-02T00Z"), - "2015-01-02", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ) - ) - ); - expectedSegments.put( - Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), - Collections.singletonList( - new DataSegment( - "base", - Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), - "2015-01-03", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ) - ) + + EasyMock.expect(taskQueue.add(EasyMock.anyObject())) + .andThrow(EntryAlreadyExists.exception("Task ID already exists")); + + EasyMock.replay(taskMaster, taskStorage, taskQueue); + + supervisor.checkSegmentsAndSubmitTasks(); + + EasyMock.verify(taskMaster, taskStorage, taskQueue); + } + + @Test + public void testSubmitTasksFailsIfTaskCannotBeAdded() throws IOException + { + Set baseSegments = Sets.newHashSet(createBaseSegments()); + Set derivativeSegments = Sets.newHashSet(createDerivativeSegments()); + + indexerMetadataStorageCoordinator.commitSegments(baseSegments); + indexerMetadataStorageCoordinator.commitSegments(derivativeSegments); + EasyMock.expect(taskMaster.getTaskQueue()).andReturn(Optional.of(taskQueue)).anyTimes(); + EasyMock.expect(taskMaster.getTaskRunner()).andReturn(Optional.absent()).anyTimes(); + EasyMock.expect(taskStorage.getActiveTasks()).andReturn(ImmutableList.of()).anyTimes(); + + EasyMock.expect(taskQueue.add(EasyMock.anyObject())) + .andThrow(new ISE("Could not add task")); + + EasyMock.replay(taskMaster, taskStorage, taskQueue); + + ISE exception = Assert.assertThrows( + ISE.class, + () -> supervisor.checkSegmentsAndSubmitTasks() ); - Assert.assertEquals(expectedToBuildInterval, toBuildInterval.lhs.keySet()); - Assert.assertEquals(expectedSegments, toBuildInterval.rhs); + Assert.assertEquals("Could not add task", exception.getMessage()); + + EasyMock.verify(taskMaster, taskStorage, taskQueue); } @Test public void testCheckSegmentsAndSubmitTasks() throws IOException { - Set baseSegments = Sets.newHashSet( - new DataSegment( - "base", - Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), - "2015-01-03", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ) - ); + Set baseSegments = Collections.singleton(createBaseSegments().get(0)); indexerMetadataStorageCoordinator.commitSegments(baseSegments); EasyMock.expect(taskMaster.getTaskQueue()).andReturn(Optional.of(taskQueue)).anyTimes(); EasyMock.expect(taskMaster.getTaskRunner()).andReturn(Optional.absent()).anyTimes(); @@ -315,28 +274,12 @@ public void testCheckSegmentsAndSubmitTasks() throws IOException Assert.assertEquals(expectedRunningTasks, runningTasks); Assert.assertEquals(expectedRunningVersion, runningVersion); - } - /** - * Verifies that creating HadoopIndexTask compleates without raising exception. - */ @Test - public void testCreateTask() + public void testCreateTaskSucceeds() { - List baseSegments = Collections.singletonList( - new DataSegment( - "base", - Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), - "2015-01-03", - ImmutableMap.of(), - ImmutableList.of("dim1", "dim2"), - ImmutableList.of("m1"), - new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), - 9, - 1024 - ) - ); + List baseSegments = createBaseSegments().subList(0, 1); HadoopIndexTask task = spec.createTask( Intervals.of("2015-01-02T00Z/2015-01-03T00Z"), @@ -348,7 +291,7 @@ public void testCreateTask() } @Test - public void testSuspendedDoesntRun() + public void testSuspendedDoesNotRun() { MaterializedViewSupervisorSpec suspended = new MaterializedViewSupervisorSpec( "base", @@ -378,10 +321,7 @@ public void testSuspendedDoesntRun() // which will be true if truly suspended, since this is the first operation of the 'run' method otherwise IndexerSQLMetadataStorageCoordinator mock = EasyMock.createMock(IndexerSQLMetadataStorageCoordinator.class); EasyMock.expect(mock.retrieveDataSourceMetadata(suspended.getDataSourceName())) - .andAnswer(() -> { - Assert.fail(); - return null; - }) + .andThrow(new AssertionFailedError()) .anyTimes(); EasyMock.replay(mock); @@ -420,4 +360,36 @@ public void testResetOffsetsNotSupported() () -> supervisor.resetOffsets(null) ); } + + private List createBaseSegments() + { + return Arrays.asList( + createSegment("base", "2015-01-01T00Z/2015-01-02T00Z", "2015-01-02"), + createSegment("base", "2015-01-02T00Z/2015-01-03T00Z", "2015-01-03"), + createSegment("base", "2015-01-03T00Z/2015-01-04T00Z", "2015-01-04") + ); + } + + private List createDerivativeSegments() + { + return Arrays.asList( + createSegment(derivativeDatasourceName, "2015-01-01T00Z/2015-01-02T00Z", "2015-01-02"), + createSegment(derivativeDatasourceName, "2015-01-02T00Z/2015-01-03T00Z", "3015-01-01") + ); + } + + private DataSegment createSegment(String datasource, String interval, String version) + { + return new DataSegment( + datasource, + Intervals.of(interval), + version, + Collections.emptyMap(), + Arrays.asList("dim1", "dim2"), + Collections.singletonList("m2"), + new HashBasedNumberedShardSpec(0, 1, 0, 1, null, null, null), + 9, + 1024 + ); + } } diff --git a/extensions-contrib/tdigestsketch/pom.xml b/extensions-contrib/tdigestsketch/pom.xml index 824a9af4398b..ccfa7f6fb653 100644 --- a/extensions-contrib/tdigestsketch/pom.xml +++ b/extensions-contrib/tdigestsketch/pom.xml @@ -139,6 +139,31 @@ junit test + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + org.easymock easymock diff --git a/extensions-contrib/tdigestsketch/src/test/java/org/apache/druid/query/aggregation/tdigestsketch/sql/TDigestSketchSqlAggregatorTest.java b/extensions-contrib/tdigestsketch/src/test/java/org/apache/druid/query/aggregation/tdigestsketch/sql/TDigestSketchSqlAggregatorTest.java index de515814d239..2a3db93b81a8 100644 --- a/extensions-contrib/tdigestsketch/src/test/java/org/apache/druid/query/aggregation/tdigestsketch/sql/TDigestSketchSqlAggregatorTest.java +++ b/extensions-contrib/tdigestsketch/src/test/java/org/apache/druid/query/aggregation/tdigestsketch/sql/TDigestSketchSqlAggregatorTest.java @@ -53,9 +53,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.List; public class TDigestSketchSqlAggregatorTest extends BaseCalciteQueryTest @@ -72,13 +71,13 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { TDigestSketchModule.registerSerde(); final QueryableIndex index = IndexBuilder.create(CalciteTests.getJsonMapper()) - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/datasketches/pom.xml b/extensions-core/datasketches/pom.xml index 4f2180f0e3b8..bdd5b0391615 100644 --- a/extensions-core/datasketches/pom.xml +++ b/extensions-core/datasketches/pom.xml @@ -150,6 +150,31 @@ junit test + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + joda-time joda-time diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchSqlAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchSqlAggregatorTest.java index 538ca8171808..b0eae011e2c3 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchSqlAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchSqlAggregatorTest.java @@ -86,9 +86,8 @@ import org.joda.time.DateTimeZone; import org.joda.time.Period; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -257,12 +256,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { HllSketchModule.registerSerde(); final QueryableIndex index = IndexBuilder .create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/sql/DoublesSketchSqlAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/sql/DoublesSketchSqlAggregatorTest.java index c71890c036c0..fe8680de9848 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/sql/DoublesSketchSqlAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/quantiles/sql/DoublesSketchSqlAggregatorTest.java @@ -62,9 +62,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -84,13 +83,13 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { DoublesSketchModule.registerSerde(); final QueryableIndex index = IndexBuilder.create(CalciteTests.getJsonMapper()) - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/theta/sql/ThetaSketchSqlAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/theta/sql/ThetaSketchSqlAggregatorTest.java index 2650e15a04b8..19dc255ff2fe 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/theta/sql/ThetaSketchSqlAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/theta/sql/ThetaSketchSqlAggregatorTest.java @@ -70,9 +70,8 @@ import org.joda.time.DateTimeZone; import org.joda.time.Period; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -111,12 +110,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { SketchModule.registerSerde(); final QueryableIndex index = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/tuple/sql/ArrayOfDoublesSketchSqlAggregatorTest.java b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/tuple/sql/ArrayOfDoublesSketchSqlAggregatorTest.java index 134c2c76e4ff..0ed01f3f4117 100644 --- a/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/tuple/sql/ArrayOfDoublesSketchSqlAggregatorTest.java +++ b/extensions-core/datasketches/src/test/java/org/apache/druid/query/aggregation/datasketches/tuple/sql/ArrayOfDoublesSketchSqlAggregatorTest.java @@ -52,9 +52,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -111,12 +110,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { ArrayOfDoublesSketchModule.registerSerde(); final QueryableIndex index = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory( OffHeapMemorySegmentWriteOutMediumFactory.instance() ) diff --git a/extensions-core/druid-bloom-filter/pom.xml b/extensions-core/druid-bloom-filter/pom.xml index 6106ba74ca2c..fcb6905b195d 100644 --- a/extensions-core/druid-bloom-filter/pom.xml +++ b/extensions-core/druid-bloom-filter/pom.xml @@ -109,6 +109,36 @@ + + junit + junit + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + org.apache.druid druid-processing @@ -130,11 +160,6 @@ test test-jar - - junit - junit - test - org.easymock easymock diff --git a/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/aggregation/bloom/sql/BloomFilterSqlAggregatorTest.java b/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/aggregation/bloom/sql/BloomFilterSqlAggregatorTest.java index 8dcab824abf7..8d00b5b559a6 100644 --- a/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/aggregation/bloom/sql/BloomFilterSqlAggregatorTest.java +++ b/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/aggregation/bloom/sql/BloomFilterSqlAggregatorTest.java @@ -54,9 +54,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.List; public class BloomFilterSqlAggregatorTest extends BaseCalciteQueryTest @@ -77,11 +76,11 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { final QueryableIndex index = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/filter/sql/BloomDimFilterSqlTest.java b/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/filter/sql/BloomDimFilterSqlTest.java index 15152694ce4d..49632c79a81c 100644 --- a/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/filter/sql/BloomDimFilterSqlTest.java +++ b/extensions-core/druid-bloom-filter/src/test/java/org/apache/druid/query/filter/sql/BloomDimFilterSqlTest.java @@ -39,8 +39,8 @@ import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.http.SqlParameter; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.IOException; @@ -219,7 +219,7 @@ public void testBloomFilters() throws IOException ); } - @Ignore("this test is really slow and is intended to use for comparisons with testBloomFilterBigParameter") + @Disabled("this test is really slow and is intended to use for comparisons with testBloomFilterBigParameter") @Test public void testBloomFilterBigNoParam() throws IOException { @@ -247,7 +247,7 @@ public void testBloomFilterBigNoParam() throws IOException ); } - @Ignore("this test is for comparison with testBloomFilterBigNoParam") + @Disabled("this test is for comparison with testBloomFilterBigNoParam") @Test public void testBloomFilterBigParameter() throws IOException { diff --git a/extensions-core/druid-catalog/pom.xml b/extensions-core/druid-catalog/pom.xml index 5c4614573fdb..6e16ff62ccc4 100644 --- a/extensions-core/druid-catalog/pom.xml +++ b/extensions-core/druid-catalog/pom.xml @@ -148,13 +148,38 @@ - org.easymock - easymock + junit + junit test - junit - junit + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.easymock + easymock test diff --git a/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogInsertTest.java b/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogInsertTest.java index 22a0ca116ed4..d4a97e666ed4 100644 --- a/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogInsertTest.java +++ b/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogInsertTest.java @@ -28,12 +28,12 @@ import org.apache.druid.catalog.storage.CatalogTests; import org.apache.druid.catalog.sync.CachedMetadataCatalog; import org.apache.druid.catalog.sync.MetadataCatalog; -import org.apache.druid.metadata.TestDerbyConnector; +import org.apache.druid.metadata.TestDerbyConnector.DerbyConnectorRule5; import org.apache.druid.sql.calcite.CalciteCatalogInsertTest; import org.apache.druid.sql.calcite.planner.CatalogResolver; import org.apache.druid.sql.calcite.table.DatasourceTable; import org.apache.druid.sql.calcite.util.SqlTestFramework; -import org.junit.ClassRule; +import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.Assert.fail; @@ -42,9 +42,8 @@ */ public class CatalogInsertTest extends CalciteCatalogInsertTest { - @ClassRule - public static final TestDerbyConnector.DerbyConnectorRule DERBY_CONNECTION_RULE = - new TestDerbyConnector.DerbyConnectorRule(); + @RegisterExtension + public static final DerbyConnectorRule5 DERBY_CONNECTION_RULE = new DerbyConnectorRule5(); private static CatalogStorage storage; diff --git a/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogQueryTest.java b/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogQueryTest.java index 2ee9041fc929..b947c2e2c96d 100644 --- a/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogQueryTest.java +++ b/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogQueryTest.java @@ -27,14 +27,14 @@ import org.apache.druid.catalog.storage.CatalogTests; import org.apache.druid.catalog.sync.CachedMetadataCatalog; import org.apache.druid.catalog.sync.MetadataCatalog; -import org.apache.druid.metadata.TestDerbyConnector; +import org.apache.druid.metadata.TestDerbyConnector.DerbyConnectorRule5; import org.apache.druid.sql.calcite.BaseCalciteQueryTest; import org.apache.druid.sql.calcite.SqlSchema; import org.apache.druid.sql.calcite.planner.CatalogResolver; import org.apache.druid.sql.calcite.util.SqlTestFramework; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import java.util.Arrays; import java.util.Collections; @@ -43,8 +43,8 @@ public class CatalogQueryTest extends BaseCalciteQueryTest { - @Rule - public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule(); + @RegisterExtension + public static final DerbyConnectorRule5 DERBY_CONNECTION_RULE = new DerbyConnectorRule5(); private CatalogTests.DbFixture dbFixture; private CatalogStorage storage; @@ -70,7 +70,7 @@ public void testCatalogSchema() .run(); } - @After + @AfterEach public void catalogTearDown() { CatalogTests.tearDown(dbFixture); @@ -79,7 +79,7 @@ public void catalogTearDown() @Override public CatalogResolver createCatalogResolver() { - dbFixture = new CatalogTests.DbFixture(derbyConnectorRule); + dbFixture = new CatalogTests.DbFixture(DERBY_CONNECTION_RULE); storage = dbFixture.storage; MetadataCatalog catalog = new CachedMetadataCatalog( storage, diff --git a/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogReplaceTest.java b/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogReplaceTest.java index da86934403b5..34011fb2205f 100644 --- a/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogReplaceTest.java +++ b/extensions-core/druid-catalog/src/test/java/org/apache/druid/catalog/sql/CatalogReplaceTest.java @@ -28,12 +28,12 @@ import org.apache.druid.catalog.storage.CatalogTests; import org.apache.druid.catalog.sync.CachedMetadataCatalog; import org.apache.druid.catalog.sync.MetadataCatalog; -import org.apache.druid.metadata.TestDerbyConnector; +import org.apache.druid.metadata.TestDerbyConnector.DerbyConnectorRule5; import org.apache.druid.sql.calcite.CalciteCatalogReplaceTest; import org.apache.druid.sql.calcite.planner.CatalogResolver; import org.apache.druid.sql.calcite.table.DatasourceTable; import org.apache.druid.sql.calcite.util.SqlTestFramework; -import org.junit.ClassRule; +import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.Assert.fail; @@ -42,10 +42,8 @@ */ public class CatalogReplaceTest extends CalciteCatalogReplaceTest { - @ClassRule - public static final TestDerbyConnector.DerbyConnectorRule DERBY_CONNECTION_RULE = - new TestDerbyConnector.DerbyConnectorRule(); - + @RegisterExtension + public static final DerbyConnectorRule5 DERBY_CONNECTION_RULE = new DerbyConnectorRule5(); private static CatalogStorage storage; @Override diff --git a/extensions-core/hdfs-storage/pom.xml b/extensions-core/hdfs-storage/pom.xml index e46044455773..56f37a8e29c6 100644 --- a/extensions-core/hdfs-storage/pom.xml +++ b/extensions-core/hdfs-storage/pom.xml @@ -177,9 +177,21 @@ runtime - log4j - log4j - 1.2.17 + org.apache.logging.log4j + log4j-api + ${log4j.version} + test + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + test + + + org.apache.logging.log4j + log4j-1.2-api + ${log4j.version} test diff --git a/extensions-core/hdfs-storage/src/test/java/org/apache/druid/inputsource/hdfs/HdfsInputSourceTest.java b/extensions-core/hdfs-storage/src/test/java/org/apache/druid/inputsource/hdfs/HdfsInputSourceTest.java index 918f051d2243..10cff01c2b9f 100644 --- a/extensions-core/hdfs-storage/src/test/java/org/apache/druid/inputsource/hdfs/HdfsInputSourceTest.java +++ b/extensions-core/hdfs-storage/src/test/java/org/apache/druid/inputsource/hdfs/HdfsInputSourceTest.java @@ -53,10 +53,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; import java.io.BufferedWriter; import java.io.File; @@ -76,7 +74,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -@RunWith(Enclosed.class) public class HdfsInputSourceTest extends InitializedNullHandlingTest { private static final String PATH = "hdfs://localhost:7020/foo/bar"; diff --git a/extensions-core/histogram/pom.xml b/extensions-core/histogram/pom.xml index 787571ccef76..936b592614c5 100644 --- a/extensions-core/histogram/pom.xml +++ b/extensions-core/histogram/pom.xml @@ -93,6 +93,36 @@ + + junit + junit + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + org.apache.druid druid-processing @@ -114,11 +144,6 @@ test test-jar - - junit - junit - test - org.easymock easymock diff --git a/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/FixedBucketsHistogramQuantileSqlAggregatorTest.java b/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/FixedBucketsHistogramQuantileSqlAggregatorTest.java index 75a29ab4f2f5..bb6a7e82ba9d 100644 --- a/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/FixedBucketsHistogramQuantileSqlAggregatorTest.java +++ b/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/FixedBucketsHistogramQuantileSqlAggregatorTest.java @@ -56,9 +56,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.List; public class FixedBucketsHistogramQuantileSqlAggregatorTest extends BaseCalciteQueryTest @@ -75,12 +74,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { ApproximateHistogramDruidModule.registerSerde(); final QueryableIndex index = IndexBuilder.create(CalciteTests.getJsonMapper()) - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/QuantileSqlAggregatorTest.java b/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/QuantileSqlAggregatorTest.java index 3ee18f886f00..a14be3162e5b 100644 --- a/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/QuantileSqlAggregatorTest.java +++ b/extensions-core/histogram/src/test/java/org/apache/druid/query/aggregation/histogram/sql/QuantileSqlAggregatorTest.java @@ -55,9 +55,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.List; public class QuantileSqlAggregatorTest extends BaseCalciteQueryTest @@ -74,12 +73,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { ApproximateHistogramDruidModule.registerSerde(); final QueryableIndex index = IndexBuilder.create(CalciteTests.getJsonMapper()) - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/kinesis-indexing-service/src/test/java/org/apache/druid/indexing/kinesis/supervisor/KinesisSupervisorTest.java b/extensions-core/kinesis-indexing-service/src/test/java/org/apache/druid/indexing/kinesis/supervisor/KinesisSupervisorTest.java index a2f3e98deca0..a4ab6a30a808 100644 --- a/extensions-core/kinesis-indexing-service/src/test/java/org/apache/druid/indexing/kinesis/supervisor/KinesisSupervisorTest.java +++ b/extensions-core/kinesis-indexing-service/src/test/java/org/apache/druid/indexing/kinesis/supervisor/KinesisSupervisorTest.java @@ -79,7 +79,6 @@ import org.apache.druid.java.util.emitter.service.AlertEvent; import org.apache.druid.java.util.metrics.DruidMonitorSchedulerConfig; import org.apache.druid.java.util.metrics.StubServiceEmitter; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.CountAggregatorFactory; import org.apache.druid.segment.TestHelper; @@ -3744,7 +3743,7 @@ public void testGetCurrentTotalStats() @Test public void testDoNotKillCompatibleTasks() - throws InterruptedException, EntryExistsException + throws InterruptedException { // This supervisor always returns true for isTaskCurrent -> it should not kill its tasks int numReplicas = 2; @@ -3840,8 +3839,7 @@ public void testDoNotKillCompatibleTasks() } @Test - public void testKillIncompatibleTasks() - throws InterruptedException, EntryExistsException + public void testKillIncompatibleTasks() throws InterruptedException { // This supervisor always returns false for isTaskCurrent -> it should kill its tasks int numReplicas = 2; diff --git a/extensions-core/lookups-cached-global/src/main/java/org/apache/druid/query/lookup/NamespaceLookupIntrospectHandler.java b/extensions-core/lookups-cached-global/src/main/java/org/apache/druid/query/lookup/NamespaceLookupIntrospectHandler.java index 889467324da3..d95361a1bdf3 100644 --- a/extensions-core/lookups-cached-global/src/main/java/org/apache/druid/query/lookup/NamespaceLookupIntrospectHandler.java +++ b/extensions-core/lookups-cached-global/src/main/java/org/apache/druid/query/lookup/NamespaceLookupIntrospectHandler.java @@ -20,8 +20,8 @@ package org.apache.druid.query.lookup; import com.google.common.collect.ImmutableMap; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.java.util.common.ISE; +import org.apache.druid.server.http.ServletResourceUtils; import org.apache.druid.server.lookup.namespace.cache.CacheScheduler; import javax.ws.rs.GET; diff --git a/extensions-core/lookups-cached-global/src/test/java/org/apache/druid/query/lookup/namespace/JdbcExtractionNamespaceUrlCheckTest.java b/extensions-core/lookups-cached-global/src/test/java/org/apache/druid/query/lookup/namespace/JdbcExtractionNamespaceUrlCheckTest.java index 178abca9c497..d2ba30b49464 100644 --- a/extensions-core/lookups-cached-global/src/test/java/org/apache/druid/query/lookup/namespace/JdbcExtractionNamespaceUrlCheckTest.java +++ b/extensions-core/lookups-cached-global/src/test/java/org/apache/druid/query/lookup/namespace/JdbcExtractionNamespaceUrlCheckTest.java @@ -25,13 +25,10 @@ import org.joda.time.Period; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.util.Set; -@RunWith(Enclosed.class) public class JdbcExtractionNamespaceUrlCheckTest { private static final String TABLE_NAME = "abstractDbRenameTest"; diff --git a/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherTest.java b/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherTest.java index 1139d4c91fc8..b437d2a3d556 100644 --- a/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherTest.java +++ b/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherTest.java @@ -35,9 +35,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.skife.jdbi.v2.Handle; import java.io.IOException; @@ -45,7 +43,6 @@ import java.util.Collections; import java.util.Map; -@RunWith(Enclosed.class) public class JdbcDataFetcherTest extends InitializedNullHandlingTest { private static final String TABLE_NAME = "tableName"; diff --git a/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherUrlCheckTest.java b/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherUrlCheckTest.java index 8f7f2e9d6d6a..b38875b51892 100644 --- a/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherUrlCheckTest.java +++ b/extensions-core/lookups-cached-single/src/test/java/org/apache/druid/server/lookup/jdbc/JdbcDataFetcherUrlCheckTest.java @@ -24,13 +24,10 @@ import org.apache.druid.server.initialization.JdbcAccessSecurityConfig; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.util.Set; -@RunWith(Enclosed.class) public class JdbcDataFetcherUrlCheckTest { private static final String TABLE_NAME = "tableName"; diff --git a/extensions-core/multi-stage-query/pom.xml b/extensions-core/multi-stage-query/pom.xml index 163a90ae09b9..58e1976d556c 100644 --- a/extensions-core/multi-stage-query/pom.xml +++ b/extensions-core/multi-stage-query/pom.xml @@ -203,11 +203,31 @@ + + junit + junit + test + org.junit.jupiter junit-jupiter-api test + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + org.easymock easymock @@ -224,8 +244,13 @@ test - junit - junit + org.junit.jupiter + junit-jupiter + test + + + org.junit.vintage + junit-vintage-engine test diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerClient.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerClient.java index b3675f0e0476..afd1ece4dad1 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerClient.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerClient.java @@ -73,8 +73,12 @@ void postWorkerError( void postWorkerWarning( List MSQErrorReports ) throws IOException; + List getTaskList() throws IOException; + /** + * Close this client. Idempotent. + */ @Override void close(); } diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerImpl.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerImpl.java index 5da74f0a52a9..e9d71239940b 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerImpl.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/ControllerImpl.java @@ -783,18 +783,10 @@ public void updatePartialKeyStatisticsInformation( addToKernelManipulationQueue( queryKernel -> { final StageId stageId = queryKernel.getStageId(stageNumber); - - // We need a specially-decorated ObjectMapper to deserialize key statistics. - final StageDefinition stageDef = queryKernel.getStageDefinition(stageId); - final ObjectMapper mapper = MSQTasks.decorateObjectMapperForKeyCollectorSnapshot( - context.jsonMapper(), - stageDef.getShuffleSpec().clusterBy(), - stageDef.getShuffleSpec().doesAggregate() - ); - final PartialKeyStatisticsInformation partialKeyStatisticsInformation; + try { - partialKeyStatisticsInformation = mapper.convertValue( + partialKeyStatisticsInformation = context.jsonMapper().convertValue( partialKeyStatisticsInformationObject, PartialKeyStatisticsInformation.class ); @@ -1913,7 +1905,8 @@ private static QueryDefinition makeQueryDefinition( .processorFactory(new ExportResultsFrameProcessorFactory( queryId, exportStorageProvider, - resultFormat + resultFormat, + columnMappings )) ); return builder.build(); @@ -2135,9 +2128,13 @@ private static Pair, List> makeDimensio // deprecation and removal in future if (MultiStageQueryContext.getArrayIngestMode(query.context()) == ArrayIngestMode.MVD) { log.warn( - "'%s' is set to 'mvd' in the query's context. This ingests the string arrays as multi-value " - + "strings instead of arrays, and is preserved for legacy reasons when MVDs were the only way to ingest string " - + "arrays in Druid. It is incorrect behaviour and will likely be removed in the future releases of Druid", + "%s[mvd] is active for this task. This causes string arrays (VARCHAR ARRAY in SQL) to be ingested as " + + "multi-value strings rather than true arrays. This behavior may change in a future version of Druid. To be " + + "compatible with future behavior changes, we recommend setting %s to[array], which creates a clearer " + + "separation between multi-value strings and true arrays. In either[mvd] or[array] mode, you can write " + + "out multi-value string dimensions using ARRAY_TO_MV. " + + "See https://druid.apache.org/docs/latest/querying/arrays#arrayingestmode for more details.", + MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, MultiStageQueryContext.CTX_ARRAY_INGEST_MODE ); } diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/MSQTasks.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/MSQTasks.java index 24a3fad8dbf1..2dff4419bdbc 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/MSQTasks.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/MSQTasks.java @@ -19,10 +19,8 @@ package org.apache.druid.msq.exec; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Injector; import com.google.inject.Key; -import org.apache.druid.frame.key.ClusterBy; import org.apache.druid.java.util.common.ISE; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.msq.guice.MultiStageQuery; @@ -39,10 +37,6 @@ import org.apache.druid.msq.indexing.error.UnknownFault; import org.apache.druid.msq.indexing.error.WorkerFailedFault; import org.apache.druid.msq.indexing.error.WorkerRpcFailedFault; -import org.apache.druid.msq.statistics.KeyCollectorFactory; -import org.apache.druid.msq.statistics.KeyCollectorSnapshot; -import org.apache.druid.msq.statistics.KeyCollectorSnapshotDeserializerModule; -import org.apache.druid.msq.statistics.KeyCollectors; import org.apache.druid.segment.column.ColumnHolder; import org.apache.druid.server.DruidNode; import org.apache.druid.storage.NilStorageConnector; @@ -125,24 +119,6 @@ public static long primaryTimestampFromObjectForInsert(final Object timestamp) } } - /** - * Returns a decorated copy of an ObjectMapper that knows how to deserialize the appropriate kind of - * {@link KeyCollectorSnapshot}. - */ - static ObjectMapper decorateObjectMapperForKeyCollectorSnapshot( - final ObjectMapper mapper, - final ClusterBy clusterBy, - final boolean aggregate - ) - { - final KeyCollectorFactory keyCollectorFactory = - KeyCollectors.makeStandardFactory(clusterBy, aggregate); - - final ObjectMapper mapperCopy = mapper.copy(); - mapperCopy.registerModule(new KeyCollectorSnapshotDeserializerModule(keyCollectorFactory)); - return mapperCopy; - } - /** * Returns the host:port from a {@link DruidNode}. Convenience method to make it easier to construct * {@link MSQErrorReport} instances. diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/WorkerImpl.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/WorkerImpl.java index 3f2ef39b5bf7..e76169f70423 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/WorkerImpl.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/exec/WorkerImpl.java @@ -498,20 +498,16 @@ public Optional runTask(final Closer closer) throws Exception @Override public void stopGracefully() { - log.info("Stopping gracefully for taskId [%s]", task.getId()); - kernelManipulationQueue.add( - kernel -> { - // stopGracefully() is called when the containing process is terminated, or when the task is canceled. - throw new MSQException(CanceledFault.INSTANCE); - } - ); + // stopGracefully() is called when the containing process is terminated, or when the task is canceled. + log.info("Worker task[%s] canceled.", task.getId()); + doCancel(); } @Override public void controllerFailed() { - controllerAlive = false; - stopGracefully(); + log.info("Controller task[%s] for worker task[%s] failed. Canceling.", task.getControllerTaskId(), task.getId()); + doCancel(); } @Override @@ -909,6 +905,31 @@ private void cleanStageOutput(final StageId stageId, boolean removeDurableStorag } } + /** + * Called by {@link #stopGracefully()} (task canceled, or containing process shut down) and + * {@link #controllerFailed()}. + */ + private void doCancel() + { + // Set controllerAlive = false so we don't try to contact the controller after being canceled. If it canceled us, + // it doesn't need to know that we were canceled. If we were canceled by something else, the controller will + // detect this as part of its monitoring of workers. + controllerAlive = false; + + // Close controller client to cancel any currently in-flight calls to the controller. + if (controllerClient != null) { + controllerClient.close(); + } + + // Clear the main loop event queue, then throw a CanceledFault into the loop to exit it promptly. + kernelManipulationQueue.clear(); + kernelManipulationQueue.add( + kernel -> { + throw new MSQException(CanceledFault.INSTANCE); + } + ); + } + /** * Log (at DEBUG level) a string explaining the status of all work assigned to this worker. */ diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/MSQWorkerTaskLauncher.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/MSQWorkerTaskLauncher.java index a485831532f0..55ff6a3876d8 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/MSQWorkerTaskLauncher.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/MSQWorkerTaskLauncher.java @@ -357,21 +357,18 @@ public Map> getWorkerStats() final Map> workerStats = new TreeMap<>(); for (Map.Entry taskEntry : taskTrackers.entrySet()) { + final TaskTracker taskTracker = taskEntry.getValue(); + final TaskStatus taskStatus = taskTracker.statusRef.get(); - TaskTracker taskTracker = taskEntry.getValue(); + // taskStatus is null when TaskTrackers are first set up, and stay null until the first status call comes back. + final TaskState statusCode = taskStatus != null ? taskStatus.getStatusCode() : null; + + // getDuration() returns -1 for running tasks. It's not calculated on-the-fly here since + // taskTracker.startTimeMillis marks task submission time rather than the actual start. + final long duration = taskStatus != null ? taskStatus.getDuration() : -1; - TaskStatus taskStatus = taskTracker.statusRef.get(); workerStats.computeIfAbsent(taskTracker.workerNumber, k -> new ArrayList<>()) - .add(new WorkerStats( - taskEntry.getKey(), - taskStatus.getStatusCode(), - // getDuration() returns -1 for running tasks. - // It's not calculated on-the-fly here since - // taskTracker.startTimeMillis marks task - // submission time rather than the actual start. - taskStatus.getDuration(), - taskTracker.taskPendingTimeInMs() - )); + .add(new WorkerStats(taskEntry.getKey(), statusCode, duration, taskTracker.taskPendingTimeInMs())); } for (List workerStatsList : workerStats.values()) { diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessor.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessor.java index de65d3e9d7ad..52697578b07e 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessor.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessor.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.apache.druid.error.DruidException; import org.apache.druid.frame.Frame; import org.apache.druid.frame.channel.ReadableFrameChannel; @@ -35,13 +37,14 @@ import org.apache.druid.java.util.common.granularity.Granularities; import org.apache.druid.java.util.common.guava.Sequence; import org.apache.druid.msq.counters.ChannelCounters; -import org.apache.druid.msq.querykit.QueryKitUtils; import org.apache.druid.msq.util.SequenceUtils; import org.apache.druid.segment.BaseObjectColumnValueSelector; import org.apache.druid.segment.ColumnSelectorFactory; import org.apache.druid.segment.Cursor; import org.apache.druid.segment.VirtualColumns; import org.apache.druid.segment.column.RowSignature; +import org.apache.druid.sql.calcite.planner.ColumnMapping; +import org.apache.druid.sql.calcite.planner.ColumnMappings; import org.apache.druid.sql.http.ResultFormat; import org.apache.druid.storage.StorageConnector; @@ -60,6 +63,8 @@ public class ExportResultsFrameProcessor implements FrameProcessor private final ObjectMapper jsonMapper; private final ChannelCounters channelCounter; final String exportFilePath; + private final Object2IntMap outputColumnNameToFrameColumnNumberMap; + private final RowSignature exportRowSignature; public ExportResultsFrameProcessor( final ReadableFrameChannel inputChannel, @@ -68,7 +73,8 @@ public ExportResultsFrameProcessor( final StorageConnector storageConnector, final ObjectMapper jsonMapper, final ChannelCounters channelCounter, - final String exportFilePath + final String exportFilePath, + final ColumnMappings columnMappings ) { this.inputChannel = inputChannel; @@ -78,6 +84,30 @@ public ExportResultsFrameProcessor( this.jsonMapper = jsonMapper; this.channelCounter = channelCounter; this.exportFilePath = exportFilePath; + this.outputColumnNameToFrameColumnNumberMap = new Object2IntOpenHashMap<>(); + final RowSignature inputRowSignature = frameReader.signature(); + + if (columnMappings == null) { + // If the column mappings wasn't sent, fail the query to avoid inconsistency in file format. + throw DruidException.forPersona(DruidException.Persona.OPERATOR) + .ofCategory(DruidException.Category.RUNTIME_FAILURE) + .build("Received null columnMappings from controller. This might be due to an upgrade."); + } + for (final ColumnMapping columnMapping : columnMappings.getMappings()) { + this.outputColumnNameToFrameColumnNumberMap.put( + columnMapping.getOutputColumn(), + frameReader.signature().indexOf(columnMapping.getQueryColumn()) + ); + } + final RowSignature.Builder exportRowSignatureBuilder = RowSignature.builder(); + + for (String outputColumn : columnMappings.getOutputColumnNames()) { + exportRowSignatureBuilder.add( + outputColumn, + inputRowSignature.getColumnType(outputColumnNameToFrameColumnNumberMap.getInt(outputColumn)).orElse(null) + ); + } + this.exportRowSignature = exportRowSignatureBuilder.build(); } @Override @@ -109,8 +139,6 @@ public ReturnOrAwait runIncrementally(IntSet readableInputs) throws IOEx private void exportFrame(final Frame frame) throws IOException { - final RowSignature exportRowSignature = createRowSignatureForExport(frameReader.signature()); - final Sequence cursorSequence = new FrameStorageAdapter(frame, frameReader, Intervals.ETERNITY) .makeCursors(null, Intervals.ETERNITY, VirtualColumns.EMPTY, Granularities.ALL, false, null); @@ -135,7 +163,7 @@ private void exportFrame(final Frame frame) throws IOException //noinspection rawtypes @SuppressWarnings("rawtypes") final List selectors = - exportRowSignature + frameReader.signature() .getColumnNames() .stream() .map(columnSelectorFactory::makeColumnValueSelector) @@ -144,7 +172,9 @@ private void exportFrame(final Frame frame) throws IOException while (!cursor.isDone()) { formatter.writeRowStart(); for (int j = 0; j < exportRowSignature.size(); j++) { - formatter.writeRowField(exportRowSignature.getColumnName(j), selectors.get(j).getObject()); + String columnName = exportRowSignature.getColumnName(j); + BaseObjectColumnValueSelector selector = selectors.get(outputColumnNameToFrameColumnNumberMap.getInt(columnName)); + formatter.writeRowField(columnName, selector.getObject()); } channelCounter.incrementRowCount(); formatter.writeRowEnd(); @@ -162,16 +192,6 @@ private void exportFrame(final Frame frame) throws IOException } } - private static RowSignature createRowSignatureForExport(RowSignature inputRowSignature) - { - RowSignature.Builder exportRowSignatureBuilder = RowSignature.builder(); - inputRowSignature.getColumnNames() - .stream() - .filter(name -> !QueryKitUtils.PARTITION_BOOST_COLUMN.equals(name)) - .forEach(name -> exportRowSignatureBuilder.add(name, inputRowSignature.getColumnType(name).orElse(null))); - return exportRowSignatureBuilder.build(); - } - @Override public void cleanup() throws IOException { diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactory.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactory.java index c9f9b6a40a81..5fe9b52191ce 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactory.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactory.java @@ -20,6 +20,7 @@ package org.apache.druid.msq.querykit.results; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import org.apache.druid.error.DruidException; @@ -41,6 +42,7 @@ import org.apache.druid.msq.kernel.ProcessorsAndChannels; import org.apache.druid.msq.kernel.StageDefinition; import org.apache.druid.msq.querykit.BaseFrameProcessorFactory; +import org.apache.druid.sql.calcite.planner.ColumnMappings; import org.apache.druid.sql.http.ResultFormat; import org.apache.druid.storage.ExportStorageProvider; import org.apache.druid.utils.CollectionUtils; @@ -55,17 +57,20 @@ public class ExportResultsFrameProcessorFactory extends BaseFrameProcessorFactor private final String queryId; private final ExportStorageProvider exportStorageProvider; private final ResultFormat exportFormat; + private final ColumnMappings columnMappings; @JsonCreator public ExportResultsFrameProcessorFactory( @JsonProperty("queryId") String queryId, @JsonProperty("exportStorageProvider") ExportStorageProvider exportStorageProvider, - @JsonProperty("exportFormat") ResultFormat exportFormat + @JsonProperty("exportFormat") ResultFormat exportFormat, + @JsonProperty("columnMappings") @Nullable ColumnMappings columnMappings ) { this.queryId = queryId; this.exportStorageProvider = exportStorageProvider; this.exportFormat = exportFormat; + this.columnMappings = columnMappings; } @JsonProperty("queryId") @@ -87,6 +92,14 @@ public ExportStorageProvider getExportStorageProvider() return exportStorageProvider; } + @JsonProperty("columnMappings") + @JsonInclude(JsonInclude.Include.NON_NULL) + @Nullable + public ColumnMappings getColumnMappings() + { + return columnMappings; + } + @Override public ProcessorsAndChannels makeProcessors( StageDefinition stageDefinition, @@ -122,7 +135,8 @@ public ProcessorsAndChannels makeProcessors( exportStorageProvider.get(), frameContext.jsonMapper(), channelCounter, - getExportFilePath(queryId, workerNumber, readableInput.getStagePartition().getPartitionNumber(), exportFormat) + getExportFilePath(queryId, workerNumber, readableInput.getStagePartition().getPartitionNumber(), exportFormat), + columnMappings ) ); diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/sql/MSQTaskSqlEngine.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/sql/MSQTaskSqlEngine.java index 6f4f109ffa4b..5de089aed949 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/sql/MSQTaskSqlEngine.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/sql/MSQTaskSqlEngine.java @@ -29,20 +29,29 @@ import org.apache.calcite.rel.core.Sort; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.schema.Table; +import org.apache.calcite.sql.dialect.CalciteSqlDialect; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.util.Pair; import org.apache.druid.error.DruidException; import org.apache.druid.error.InvalidInput; import org.apache.druid.error.InvalidSqlInput; +import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.granularity.Granularities; import org.apache.druid.java.util.common.granularity.Granularity; import org.apache.druid.msq.querykit.QueryKitUtils; +import org.apache.druid.msq.util.ArrayIngestMode; +import org.apache.druid.msq.util.DimensionSchemaUtils; import org.apache.druid.msq.util.MultiStageQueryContext; import org.apache.druid.rpc.indexing.OverlordClient; import org.apache.druid.segment.column.ColumnHolder; +import org.apache.druid.segment.column.ColumnType; +import org.apache.druid.segment.column.ValueType; import org.apache.druid.sql.calcite.parser.DruidSqlIngest; import org.apache.druid.sql.calcite.parser.DruidSqlInsert; import org.apache.druid.sql.calcite.planner.Calcites; +import org.apache.druid.sql.calcite.planner.DruidTypeSystem; import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.run.EngineFeature; import org.apache.druid.sql.calcite.run.NativeSqlEngine; @@ -50,7 +59,9 @@ import org.apache.druid.sql.calcite.run.SqlEngine; import org.apache.druid.sql.calcite.run.SqlEngines; import org.apache.druid.sql.destination.IngestDestination; +import org.apache.druid.sql.destination.TableDestination; +import javax.annotation.Nullable; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -107,7 +118,7 @@ public RelDataType resultTypeForInsert(RelDataTypeFactory typeFactory, RelDataTy } @Override - public boolean featureAvailable(EngineFeature feature, PlannerContext plannerContext) + public boolean featureAvailable(EngineFeature feature) { switch (feature) { case ALLOW_BINDABLE_PLAN: @@ -118,6 +129,7 @@ public boolean featureAvailable(EngineFeature feature, PlannerContext plannerCon case GROUPING_SETS: case WINDOW_FUNCTIONS: case ALLOW_TOP_LEVEL_UNION_ALL: + case GROUPBY_IMPLICITLY_SORTS: return false; case UNNEST: case CAN_SELECT: @@ -162,7 +174,18 @@ public QueryMaker buildQueryMakerForInsert( final PlannerContext plannerContext ) { - validateInsert(relRoot.rel, relRoot.fields, plannerContext); + validateInsert( + relRoot.rel, + relRoot.fields, + destination instanceof TableDestination + ? plannerContext.getPlannerToolbox() + .rootSchema() + .getNamedSchema(plannerContext.getPlannerToolbox().druidSchemaName()) + .getSchema() + .getTable(((TableDestination) destination).getTableName()) + : null, + plannerContext + ); return new MSQTaskQueryMaker( destination, @@ -193,65 +216,78 @@ private static void validateSelect(final PlannerContext plannerContext) } } + /** + * Engine-specific validation that happens after the query is planned. + */ private static void validateInsert( final RelNode rootRel, final List> fieldMappings, + @Nullable Table targetTable, final PlannerContext plannerContext ) { + final int timeColumnIndex = getTimeColumnIndex(fieldMappings); + final Granularity segmentGranularity = getSegmentGranularity(plannerContext); + validateNoDuplicateAliases(fieldMappings); + validateTimeColumnType(rootRel, timeColumnIndex); + validateTimeColumnExistsIfNeeded(timeColumnIndex, segmentGranularity); + validateLimitAndOffset(rootRel, Granularities.ALL.equals(segmentGranularity)); + validateTypeChanges(rootRel, fieldMappings, targetTable, plannerContext); + } - // Find the __time field. - int timeFieldIndex = -1; + /** + * SQL allows multiple output columns with the same name. However, we don't allow this for INSERT or REPLACE + * queries, because we use these output names to generate columns in segments. They must be unique. + */ + private static void validateNoDuplicateAliases(final List> fieldMappings) + { + final Set aliasesSeen = new HashSet<>(); for (final Pair field : fieldMappings) { - if (field.right.equals(ColumnHolder.TIME_COLUMN_NAME)) { - timeFieldIndex = field.left; - - // Validate the __time field has the proper type. - final SqlTypeName timeType = rootRel.getRowType().getFieldList().get(field.left).getType().getSqlTypeName(); - if (timeType != SqlTypeName.TIMESTAMP) { - throw InvalidSqlInput.exception( - "Field [%s] was the wrong type [%s], expected TIMESTAMP", - ColumnHolder.TIME_COLUMN_NAME, - timeType - ); - } + if (!aliasesSeen.add(field.right)) { + throw InvalidSqlInput.exception("Duplicate field in SELECT: [%s]", field.right); } } + } - // Validate that if segmentGranularity is not ALL then there is also a __time field. - final Granularity segmentGranularity; - - try { - segmentGranularity = QueryKitUtils.getSegmentGranularityFromContext( - plannerContext.getJsonMapper(), - plannerContext.queryContextMap() - ); + /** + * Validate the time field {@link ColumnHolder#TIME_COLUMN_NAME} has type TIMESTAMP. + * + * @param rootRel root rel + * @param timeColumnIndex index of the time field + */ + private static void validateTimeColumnType(final RelNode rootRel, final int timeColumnIndex) + { + if (timeColumnIndex < 0) { + return; } - catch (Exception e) { - // This is a defensive check as the DruidSqlInsert.SQL_INSERT_SEGMENT_GRANULARITY in the query context is - // populated by Druid. If the user entered an incorrect granularity, that should have been flagged before reaching - // here - throw DruidException.forPersona(DruidException.Persona.DEVELOPER) - .ofCategory(DruidException.Category.DEFENSIVE) - .build( - e, - "[%s] is not a valid value for [%s]", - plannerContext.queryContext().get(DruidSqlInsert.SQL_INSERT_SEGMENT_GRANULARITY), - DruidSqlInsert.SQL_INSERT_SEGMENT_GRANULARITY - ); + // Validate the __time field has the proper type. + final SqlTypeName timeType = rootRel.getRowType().getFieldList().get(timeColumnIndex).getType().getSqlTypeName(); + if (timeType != SqlTypeName.TIMESTAMP) { + throw InvalidSqlInput.exception( + "Field[%s] was the wrong type[%s], expected TIMESTAMP", + ColumnHolder.TIME_COLUMN_NAME, + timeType + ); } + } + /** + * Validate that if segmentGranularity is not ALL, then there is also a {@link ColumnHolder#TIME_COLUMN_NAME} field. + * + * @param segmentGranularity granularity from {@link #getSegmentGranularity(PlannerContext)} + * @param timeColumnIndex index of the time field + */ + private static void validateTimeColumnExistsIfNeeded( + final int timeColumnIndex, + final Granularity segmentGranularity + ) + { final boolean hasSegmentGranularity = !Granularities.ALL.equals(segmentGranularity); - // Validate that the query does not have an inappropriate LIMIT or OFFSET. LIMIT prevents gathering result key - // statistics, which INSERT execution logic depends on. (In QueryKit, LIMIT disables statistics generation and - // funnels everything through a single partition.) - validateLimitAndOffset(rootRel, !hasSegmentGranularity); - - if (hasSegmentGranularity && timeFieldIndex < 0) { + if (hasSegmentGranularity && timeColumnIndex < 0) { throw InvalidInput.exception( "The granularity [%s] specified in the PARTITIONED BY clause of the INSERT query is different from ALL. " + "Therefore, the query must specify a time column (named __time).", @@ -261,29 +297,24 @@ private static void validateInsert( } /** - * SQL allows multiple output columns with the same name. However, we don't allow this for INSERT or REPLACE - * queries, because we use these output names to generate columns in segments. They must be unique. + * Validate that the query does not have an inappropriate LIMIT or OFFSET. LIMIT prevents gathering result key + * statistics, which INSERT execution logic depends on. (In QueryKit, LIMIT disables statistics generation and + * funnels everything through a single partition.) + * + * LIMIT is allowed when segment granularity is ALL, disallowed otherwise. OFFSET is never allowed. + * + * @param rootRel root rel + * @param limitOk whether LIMIT is ok (OFFSET is never ok) */ - private static void validateNoDuplicateAliases(final List> fieldMappings) - { - final Set aliasesSeen = new HashSet<>(); - - for (final Pair field : fieldMappings) { - if (!aliasesSeen.add(field.right)) { - throw InvalidSqlInput.exception("Duplicate field in SELECT: [%s]", field.right); - } - } - } - - private static void validateLimitAndOffset(final RelNode topRel, final boolean limitOk) + private static void validateLimitAndOffset(final RelNode rootRel, final boolean limitOk) { Sort sort = null; - if (topRel instanceof Sort) { - sort = (Sort) topRel; - } else if (topRel instanceof Project) { + if (rootRel instanceof Sort) { + sort = (Sort) rootRel; + } else if (rootRel instanceof Project) { // Look for Project after a Sort, then validate the sort. - final Project project = (Project) topRel; + final Project project = (Project) rootRel; if (project.isMapping()) { final RelNode projectInput = project.getInput(); if (projectInput instanceof Sort) { @@ -307,6 +338,132 @@ private static void validateLimitAndOffset(final RelNode topRel, final boolean l } } + /** + * Validate that the query does not include any type changes from string to array or vice versa. + * + * These type changes tend to cause problems due to mixing of multi-value strings and string arrays. In particular, + * many queries written in the "classic MVD" style (treating MVDs as if they were regular strings) will fail when + * MVDs and arrays are mixed. So, we detect them as invalid. + * + * @param rootRel root rel + * @param fieldMappings field mappings from {@link #validateInsert(RelNode, List, Table, PlannerContext)} + * @param targetTable table we are inserting (or replacing) into, if any + * @param plannerContext planner context + */ + private static void validateTypeChanges( + final RelNode rootRel, + final List> fieldMappings, + @Nullable final Table targetTable, + final PlannerContext plannerContext + ) + { + if (targetTable == null) { + return; + } + + final Set columnsExcludedFromTypeVerification = + MultiStageQueryContext.getColumnsExcludedFromTypeVerification(plannerContext.queryContext()); + final ArrayIngestMode arrayIngestMode = MultiStageQueryContext.getArrayIngestMode(plannerContext.queryContext()); + + for (Pair fieldMapping : fieldMappings) { + final int columnIndex = fieldMapping.left; + final String columnName = fieldMapping.right; + final RelDataTypeField oldSqlTypeField = + targetTable.getRowType(DruidTypeSystem.TYPE_FACTORY).getField(columnName, true, false); + + if (!columnsExcludedFromTypeVerification.contains(columnName) && oldSqlTypeField != null) { + final ColumnType oldDruidType = Calcites.getColumnTypeForRelDataType(oldSqlTypeField.getType()); + final RelDataType newSqlType = rootRel.getRowType().getFieldList().get(columnIndex).getType(); + final ColumnType newDruidType = + DimensionSchemaUtils.getDimensionType(Calcites.getColumnTypeForRelDataType(newSqlType), arrayIngestMode); + + if (newDruidType.isArray() && oldDruidType.is(ValueType.STRING) + || (newDruidType.is(ValueType.STRING) && oldDruidType.isArray())) { + final StringBuilder messageBuilder = new StringBuilder( + StringUtils.format( + "Cannot write into field[%s] using type[%s] and arrayIngestMode[%s], since the existing type is[%s]", + columnName, + newSqlType, + StringUtils.toLowerCase(arrayIngestMode.toString()), + oldSqlTypeField.getType() + ) + ); + + if (newDruidType.is(ValueType.STRING) + && newSqlType.getSqlTypeName() == SqlTypeName.ARRAY + && arrayIngestMode == ArrayIngestMode.MVD) { + // Tried to insert a SQL ARRAY, which got turned into a STRING by arrayIngestMode: mvd. + messageBuilder.append(". Try setting arrayIngestMode to[array] to retain the SQL type[") + .append(newSqlType) + .append("]"); + } else if (newDruidType.is(ValueType.ARRAY) + && oldDruidType.is(ValueType.STRING) + && arrayIngestMode == ArrayIngestMode.ARRAY) { + // Tried to insert a SQL ARRAY, which stayed an ARRAY, but wasn't compatible with existing STRING. + messageBuilder.append(". Try wrapping this field using ARRAY_TO_MV(...) AS ") + .append(CalciteSqlDialect.DEFAULT.quoteIdentifier(columnName)); + } else if (newDruidType.is(ValueType.STRING) && oldDruidType.is(ValueType.ARRAY)) { + // Tried to insert a SQL VARCHAR, but wasn't compatible with existing ARRAY. + messageBuilder.append(". Try"); + if (arrayIngestMode == ArrayIngestMode.MVD) { + messageBuilder.append(" setting arrayIngestMode to[array] and"); + } + messageBuilder.append(" adjusting your query to make this column an ARRAY instead of VARCHAR"); + } + + messageBuilder.append(". See https://druid.apache.org/docs/latest/querying/arrays#arrayingestmode " + + "for more details about this check and how to override it if needed."); + + throw InvalidSqlInput.exception(StringUtils.encodeForFormat(messageBuilder.toString())); + } + } + } + } + + /** + * Returns the index of {@link ColumnHolder#TIME_COLUMN_NAME} within a list of field mappings from + * {@link #validateInsert(RelNode, List, Table, PlannerContext)}. + * + * Returns -1 if the list does not contain a time column. + */ + private static int getTimeColumnIndex(final List> fieldMappings) + { + for (final Pair field : fieldMappings) { + if (field.right.equals(ColumnHolder.TIME_COLUMN_NAME)) { + return field.left; + } + } + + return -1; + } + + /** + * Retrieve the segment granularity for a query. + */ + private static Granularity getSegmentGranularity(final PlannerContext plannerContext) + { + try { + return QueryKitUtils.getSegmentGranularityFromContext( + plannerContext.getJsonMapper(), + plannerContext.queryContextMap() + ); + } + catch (Exception e) { + // This is a defensive check as the DruidSqlInsert.SQL_INSERT_SEGMENT_GRANULARITY in the query context is + // populated by Druid. If the user entered an incorrect granularity, that should have been flagged before reaching + // here. + throw DruidException.forPersona(DruidException.Persona.DEVELOPER) + .ofCategory(DruidException.Category.DEFENSIVE) + .build( + e, + "[%s] is not a valid value for [%s]", + plannerContext.queryContext().get(DruidSqlInsert.SQL_INSERT_SEGMENT_GRANULARITY), + DruidSqlInsert.SQL_INSERT_SEGMENT_GRANULARITY + ); + + } + } + private static RelDataType getMSQStructType(RelDataTypeFactory typeFactory) { return typeFactory.createStructType( diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DelegateOrMinKeyCollectorFactory.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DelegateOrMinKeyCollectorFactory.java index 043c5056257d..835e3927158c 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DelegateOrMinKeyCollectorFactory.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DelegateOrMinKeyCollectorFactory.java @@ -19,13 +19,8 @@ package org.apache.druid.msq.statistics; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; import org.apache.druid.frame.key.RowKey; -import java.io.IOException; import java.util.Comparator; import java.util.Optional; @@ -53,46 +48,6 @@ public DelegateOrMinKeyCollector newKeyCollector() return new DelegateOrMinKeyCollector<>(comparator, delegateFactory.newKeyCollector(), null); } - @Override - public JsonDeserializer> snapshotDeserializer() - { - final JsonDeserializer delegateDeserializer = delegateFactory.snapshotDeserializer(); - - return new JsonDeserializer>() - { - @Override - public DelegateOrMinKeyCollectorSnapshot deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException - { - TSnapshot delegateSnapshot = null; - RowKey minKey = null; - - if (!jp.isExpectedStartObjectToken()) { - ctxt.reportWrongTokenException(this, JsonToken.START_OBJECT, null); - } - - JsonToken token; - - while ((token = jp.nextToken()) != JsonToken.END_OBJECT) { - if (token != JsonToken.FIELD_NAME) { - ctxt.reportWrongTokenException(this, JsonToken.FIELD_NAME, null); - } - - final String fieldName = jp.getText(); - jp.nextToken(); - - if (DelegateOrMinKeyCollectorSnapshot.FIELD_SNAPSHOT.equals(fieldName)) { - delegateSnapshot = delegateDeserializer.deserialize(jp, ctxt); - } else if (DelegateOrMinKeyCollectorSnapshot.FIELD_MIN_KEY.equals(fieldName)) { - minKey = jp.readValueAs(RowKey.class); - } - } - - return new DelegateOrMinKeyCollectorSnapshot<>(delegateSnapshot, minKey); - } - }; - } - @Override public DelegateOrMinKeyCollectorSnapshot toSnapshot(final DelegateOrMinKeyCollector collector) { diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DistinctKeyCollectorFactory.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DistinctKeyCollectorFactory.java index 741b5096c77a..432fd76e8587 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DistinctKeyCollectorFactory.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/DistinctKeyCollectorFactory.java @@ -19,15 +19,11 @@ package org.apache.druid.msq.statistics; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; import it.unimi.dsi.fastutil.objects.Object2LongRBTreeMap; import org.apache.druid.collections.SerializablePair; import org.apache.druid.frame.key.ClusterBy; import org.apache.druid.frame.key.RowKey; -import java.io.IOException; import java.util.Comparator; import java.util.stream.Collectors; @@ -51,19 +47,6 @@ public DistinctKeyCollector newKeyCollector() return new DistinctKeyCollector(comparator); } - @Override - public JsonDeserializer snapshotDeserializer() - { - return new JsonDeserializer() - { - @Override - public DistinctKeySnapshot deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException - { - return jp.readValueAs(DistinctKeySnapshot.class); - } - }; - } - @Override public DistinctKeySnapshot toSnapshot(final DistinctKeyCollector collector) { diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorFactory.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorFactory.java index f7956b6dcd68..5ee61558370a 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorFactory.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorFactory.java @@ -19,8 +19,6 @@ package org.apache.druid.msq.statistics; -import com.fasterxml.jackson.databind.JsonDeserializer; - public interface KeyCollectorFactory, TSnapshot extends KeyCollectorSnapshot> { /** @@ -28,12 +26,6 @@ public interface KeyCollectorFactory */ TCollector newKeyCollector(); - /** - * Fetches the deserializer that can be used to deserialize the snapshots created by the KeyCollectors corresponding - * to this factory - */ - JsonDeserializer snapshotDeserializer(); - /** * Serializes a {@link KeyCollector} to a {@link KeyCollectorSnapshot} */ diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorSnapshotDeserializerModule.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorSnapshotDeserializerModule.java deleted file mode 100644 index b1aec6296d54..000000000000 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/KeyCollectorSnapshotDeserializerModule.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.msq.statistics; - -import com.fasterxml.jackson.databind.module.SimpleModule; - -/** - * A module for deserialization of {@link KeyCollectorSnapshot}. - */ -public class KeyCollectorSnapshotDeserializerModule extends SimpleModule -{ - public KeyCollectorSnapshotDeserializerModule(final KeyCollectorFactory keyCollectorFactory) - { - addDeserializer(KeyCollectorSnapshot.class, keyCollectorFactory.snapshotDeserializer()); - } -} - diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/QuantilesSketchKeyCollectorFactory.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/QuantilesSketchKeyCollectorFactory.java index 674dfe15acbb..4f6bd6dbcd13 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/QuantilesSketchKeyCollectorFactory.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/statistics/QuantilesSketchKeyCollectorFactory.java @@ -19,9 +19,6 @@ package org.apache.druid.msq.statistics; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; import com.google.common.annotations.VisibleForTesting; import org.apache.datasketches.common.ArrayOfItemsSerDe; import org.apache.datasketches.common.ByteArrayUtil; @@ -31,7 +28,6 @@ import org.apache.druid.frame.key.ClusterBy; import org.apache.druid.java.util.common.StringUtils; -import java.io.IOException; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Comparator; @@ -61,20 +57,6 @@ public QuantilesSketchKeyCollector newKeyCollector() return new QuantilesSketchKeyCollector(comparator, ItemsSketch.getInstance(byte[].class, SKETCH_INITIAL_K, comparator), 0); } - @Override - public JsonDeserializer snapshotDeserializer() - { - return new JsonDeserializer() - { - @Override - public QuantilesSketchKeyCollectorSnapshot deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException - { - return jp.readValueAs(QuantilesSketchKeyCollectorSnapshot.class); - } - }; - } - @Override public QuantilesSketchKeyCollectorSnapshot toSnapshot(QuantilesSketchKeyCollector collector) { diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/DimensionSchemaUtils.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/DimensionSchemaUtils.java index 748d411c97ce..07a518216168 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/DimensionSchemaUtils.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/DimensionSchemaUtils.java @@ -57,9 +57,19 @@ public static DimensionSchema createDimensionSchemaForExtern(final String column ); } + /** + * Create a dimension schema for a dimension column, given the type that it was assigned in the query, and the + * current values of {@link MultiStageQueryContext#CTX_USE_AUTO_SCHEMAS} and + * {@link MultiStageQueryContext#CTX_ARRAY_INGEST_MODE}. + * + * @param column column name + * @param queryType type of the column from the query + * @param useAutoType active value of {@link MultiStageQueryContext#CTX_USE_AUTO_SCHEMAS} + * @param arrayIngestMode active value of {@link MultiStageQueryContext#CTX_ARRAY_INGEST_MODE} + */ public static DimensionSchema createDimensionSchema( final String column, - @Nullable final ColumnType type, + @Nullable final ColumnType queryType, boolean useAutoType, ArrayIngestMode arrayIngestMode ) @@ -67,66 +77,92 @@ public static DimensionSchema createDimensionSchema( if (useAutoType) { // for complex types that are not COMPLEX, we still want to use the handler since 'auto' typing // only works for the 'standard' built-in types - if (type != null && type.is(ValueType.COMPLEX) && !ColumnType.NESTED_DATA.equals(type)) { - final ColumnCapabilities capabilities = ColumnCapabilitiesImpl.createDefault().setType(type); + if (queryType != null && queryType.is(ValueType.COMPLEX) && !ColumnType.NESTED_DATA.equals(queryType)) { + final ColumnCapabilities capabilities = ColumnCapabilitiesImpl.createDefault().setType(queryType); return DimensionHandlerUtils.getHandlerFromCapabilities(column, capabilities, null) .getDimensionSchema(capabilities); } - if (type != null && (type.isPrimitive() || type.isPrimitiveArray())) { - return new AutoTypeColumnSchema(column, type); + if (queryType != null && (queryType.isPrimitive() || queryType.isPrimitiveArray())) { + return new AutoTypeColumnSchema(column, queryType); } return new AutoTypeColumnSchema(column, null); } else { - // if schema information is not available, create a string dimension - if (type == null) { - return new StringDimensionSchema(column); - } else if (type.getType() == ValueType.STRING) { - return new StringDimensionSchema(column); - } else if (type.getType() == ValueType.LONG) { + // dimensionType may not be identical to queryType, depending on arrayIngestMode. + final ColumnType dimensionType = getDimensionType(queryType, arrayIngestMode); + + if (dimensionType.getType() == ValueType.STRING) { + return new StringDimensionSchema( + column, + queryType != null && queryType.isArray() + ? DimensionSchema.MultiValueHandling.ARRAY + : DimensionSchema.MultiValueHandling.SORTED_ARRAY, + null + ); + } else if (dimensionType.getType() == ValueType.LONG) { return new LongDimensionSchema(column); - } else if (type.getType() == ValueType.FLOAT) { + } else if (dimensionType.getType() == ValueType.FLOAT) { return new FloatDimensionSchema(column); - } else if (type.getType() == ValueType.DOUBLE) { + } else if (dimensionType.getType() == ValueType.DOUBLE) { return new DoubleDimensionSchema(column); - } else if (type.getType() == ValueType.ARRAY) { - ValueType elementType = type.getElementType().getType(); - if (elementType == ValueType.STRING) { - if (arrayIngestMode == ArrayIngestMode.NONE) { - throw InvalidInput.exception( - "String arrays can not be ingested when '%s' is set to '%s'. Set '%s' in query context " - + "to 'array' to ingest the string array as an array, or ingest it as an MVD by explicitly casting the " - + "array to an MVD with ARRAY_TO_MV function.", - MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, - StringUtils.toLowerCase(arrayIngestMode.name()), - MultiStageQueryContext.CTX_ARRAY_INGEST_MODE - ); - } else if (arrayIngestMode == ArrayIngestMode.MVD) { - return new StringDimensionSchema(column, DimensionSchema.MultiValueHandling.ARRAY, null); - } else { - // arrayIngestMode == ArrayIngestMode.ARRAY would be true - return new AutoTypeColumnSchema(column, type); - } - } else if (elementType.isNumeric()) { - // ValueType == LONG || ValueType == FLOAT || ValueType == DOUBLE - if (arrayIngestMode == ArrayIngestMode.ARRAY) { - return new AutoTypeColumnSchema(column, type); - } else { - throw InvalidInput.exception( - "Numeric arrays can only be ingested when '%s' is set to 'array' in the MSQ query's context. " - + "Current value of the parameter [%s]", - MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, - StringUtils.toLowerCase(arrayIngestMode.name()) - ); - } - } else { - throw new ISE("Cannot create dimension for type [%s]", type.toString()); - } + } else if (dimensionType.getType() == ValueType.ARRAY) { + return new AutoTypeColumnSchema(column, dimensionType); } else { - final ColumnCapabilities capabilities = ColumnCapabilitiesImpl.createDefault().setType(type); + final ColumnCapabilities capabilities = ColumnCapabilitiesImpl.createDefault().setType(dimensionType); return DimensionHandlerUtils.getHandlerFromCapabilities(column, capabilities, null) .getDimensionSchema(capabilities); } } } + + /** + * Based on a type from a query result, get the type of dimension we should write. + * + * @throws org.apache.druid.error.DruidException if there is some problem + */ + public static ColumnType getDimensionType( + @Nullable final ColumnType queryType, + final ArrayIngestMode arrayIngestMode + ) + { + if (queryType == null) { + // if schema information is not available, create a string dimension + return ColumnType.STRING; + } else if (queryType.getType() == ValueType.ARRAY) { + ValueType elementType = queryType.getElementType().getType(); + if (elementType == ValueType.STRING) { + if (arrayIngestMode == ArrayIngestMode.NONE) { + throw InvalidInput.exception( + "String arrays can not be ingested when '%s' is set to '%s'. Set '%s' in query context " + + "to 'array' to ingest the string array as an array, or ingest it as an MVD by explicitly casting the " + + "array to an MVD with the ARRAY_TO_MV function.", + MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, + StringUtils.toLowerCase(arrayIngestMode.name()), + MultiStageQueryContext.CTX_ARRAY_INGEST_MODE + ); + } else if (arrayIngestMode == ArrayIngestMode.MVD) { + return ColumnType.STRING; + } else { + assert arrayIngestMode == ArrayIngestMode.ARRAY; + return queryType; + } + } else if (elementType.isNumeric()) { + // ValueType == LONG || ValueType == FLOAT || ValueType == DOUBLE + if (arrayIngestMode == ArrayIngestMode.ARRAY) { + return queryType; + } else { + throw InvalidInput.exception( + "Numeric arrays can only be ingested when '%s' is set to 'array'. " + + "Current value of the parameter is[%s]", + MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, + StringUtils.toLowerCase(arrayIngestMode.name()) + ); + } + } else { + throw new ISE("Cannot create dimension for type[%s]", queryType.toString()); + } + } else { + return queryType; + } + } } diff --git a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java index b7340343c810..3cb49b7d05ea 100644 --- a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java +++ b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java @@ -43,7 +43,9 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -152,6 +154,7 @@ public class MultiStageQueryContext public static final String CTX_ARRAY_INGEST_MODE = "arrayIngestMode"; public static final ArrayIngestMode DEFAULT_ARRAY_INGEST_MODE = ArrayIngestMode.MVD; + public static final String CTX_SKIP_TYPE_VERIFICATION = "skipTypeVerification"; private static final Pattern LOOKS_LIKE_JSON_ARRAY = Pattern.compile("^\\s*\\[.*", Pattern.DOTALL); @@ -297,7 +300,7 @@ public static int getRowsInMemory(final QueryContext queryContext) public static List getSortOrder(final QueryContext queryContext) { - return MultiStageQueryContext.decodeSortOrder(queryContext.getString(CTX_SORT_ORDER)); + return decodeList(CTX_SORT_ORDER, queryContext.getString(CTX_SORT_ORDER)); } @Nullable @@ -316,37 +319,39 @@ public static ArrayIngestMode getArrayIngestMode(final QueryContext queryContext return queryContext.getEnum(CTX_ARRAY_INGEST_MODE, ArrayIngestMode.class, DEFAULT_ARRAY_INGEST_MODE); } + public static Set getColumnsExcludedFromTypeVerification(final QueryContext queryContext) + { + return new HashSet<>(decodeList(CTX_SKIP_TYPE_VERIFICATION, queryContext.getString(CTX_SKIP_TYPE_VERIFICATION))); + } + /** - * Decodes {@link #CTX_SORT_ORDER} from either a JSON or CSV string. + * Decodes a list from either a JSON or CSV string. */ - @Nullable @VisibleForTesting - static List decodeSortOrder(@Nullable final String sortOrderString) + static List decodeList(final String keyName, @Nullable final String listString) { - if (sortOrderString == null) { + if (listString == null) { return Collections.emptyList(); - } else if (LOOKS_LIKE_JSON_ARRAY.matcher(sortOrderString).matches()) { + } else if (LOOKS_LIKE_JSON_ARRAY.matcher(listString).matches()) { try { // Not caching this ObjectMapper in a static, because we expect to use it infrequently (once per INSERT // query that uses this feature) and there is no need to keep it around longer than that. - return new ObjectMapper().readValue(sortOrderString, new TypeReference>() - { - }); + return new ObjectMapper().readValue(listString, new TypeReference>() {}); } catch (JsonProcessingException e) { - throw QueryContexts.badValueException(CTX_SORT_ORDER, "CSV or JSON array", sortOrderString); + throw QueryContexts.badValueException(keyName, "CSV or JSON array", listString); } } else { final RFC4180Parser csvParser = new RFC4180ParserBuilder().withSeparator(',').build(); try { - return Arrays.stream(csvParser.parseLine(sortOrderString)) + return Arrays.stream(csvParser.parseLine(listString)) .filter(s -> s != null && !s.isEmpty()) .map(String::trim) .collect(Collectors.toList()); } catch (IOException e) { - throw QueryContexts.badValueException(CTX_SORT_ORDER, "CSV or JSON array", sortOrderString); + throw QueryContexts.badValueException(keyName, "CSV or JSON array", listString); } } } diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQArraysTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQArraysTest.java index 456c74c29bc7..d3f7e6a14732 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQArraysTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQArraysTest.java @@ -21,10 +21,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import org.apache.druid.common.config.NullHandling; import org.apache.druid.data.input.impl.InlineInputSource; import org.apache.druid.data.input.impl.JsonInputFormat; import org.apache.druid.data.input.impl.LocalInputSource; import org.apache.druid.data.input.impl.systemfield.SystemFields; +import org.apache.druid.error.DruidException; import org.apache.druid.java.util.common.ISE; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.msq.indexing.MSQSpec; @@ -47,11 +49,10 @@ import org.apache.druid.timeline.SegmentId; import org.apache.druid.utils.CompressionUtils; import org.hamcrest.CoreMatchers; -import org.junit.Before; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.io.File; import java.io.IOException; @@ -69,14 +70,12 @@ /** * Tests INSERT and SELECT behaviour of MSQ with arrays and MVDs */ -@RunWith(Parameterized.class) public class MSQArraysTest extends MSQTestBase { private String dataFileNameJsonString; private String dataFileSignatureJsonString; private DataSource dataFileExternalDataSource; - @Parameterized.Parameters(name = "{index}:with context {0}") public static Collection data() { Object[][] data = new Object[][]{ @@ -88,17 +87,11 @@ public static Collection data() return Arrays.asList(data); } - @Parameterized.Parameter(0) - public String contextName; - - @Parameterized.Parameter(1) - public Map context; - - @Before + @BeforeEach public void setup() throws IOException { // Read the file and make the name available to the tests - File dataFile = temporaryFolder.newFile(); + File dataFile = newTempFile("dataFile"); final InputStream resourceStream = NestedDataTestUtils.class.getClassLoader() .getResourceAsStream(NestedDataTestUtils.ARRAY_TYPES_DATA_FILE); final InputStream decompressing = CompressionUtils.decompress( @@ -111,14 +104,14 @@ public void setup() throws IOException dataFileNameJsonString = queryFramework().queryJsonMapper().writeValueAsString(dataFile); RowSignature dataFileSignature = RowSignature.builder() - .add("timestamp", ColumnType.STRING) - .add("arrayString", ColumnType.STRING_ARRAY) - .add("arrayStringNulls", ColumnType.STRING_ARRAY) - .add("arrayLong", ColumnType.LONG_ARRAY) - .add("arrayLongNulls", ColumnType.LONG_ARRAY) - .add("arrayDouble", ColumnType.DOUBLE_ARRAY) - .add("arrayDoubleNulls", ColumnType.DOUBLE_ARRAY) - .build(); + .add("timestamp", ColumnType.STRING) + .add("arrayString", ColumnType.STRING_ARRAY) + .add("arrayStringNulls", ColumnType.STRING_ARRAY) + .add("arrayLong", ColumnType.LONG_ARRAY) + .add("arrayLongNulls", ColumnType.LONG_ARRAY) + .add("arrayDouble", ColumnType.DOUBLE_ARRAY) + .add("arrayDoubleNulls", ColumnType.DOUBLE_ARRAY) + .build(); dataFileSignatureJsonString = queryFramework().queryJsonMapper().writeValueAsString(dataFileSignature); dataFileExternalDataSource = new ExternalDataSource( @@ -132,8 +125,9 @@ public void setup() throws IOException * Tests the behaviour of INSERT query when arrayIngestMode is set to none (default) and the user tries to ingest * string arrays */ - @Test - public void testInsertStringArrayWithArrayIngestModeNone() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertStringArrayWithArrayIngestModeNone(String contextName, Map context) { final Map adjustedContext = new HashMap<>(context); @@ -150,13 +144,184 @@ public void testInsertStringArrayWithArrayIngestModeNone() .verifyExecutionError(); } + /** + * Tests the behaviour of INSERT query when arrayIngestMode is set to none (default) and the user tries to ingest + * string arrays + */ + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceMvdWithStringArray(String contextName, Map context) + { + final Map adjustedContext = new HashMap<>(context); + adjustedContext.put(MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, "array"); + + testIngestQuery() + .setSql( + "REPLACE INTO foo OVERWRITE ALL\n" + + "SELECT MV_TO_ARRAY(dim3) AS dim3 FROM foo\n" + + "PARTITIONED BY ALL TIME" + ) + .setQueryContext(adjustedContext) + .setExpectedExecutionErrorMatcher(CoreMatchers.allOf( + CoreMatchers.instanceOf(DruidException.class), + ThrowableMessageMatcher.hasMessage(CoreMatchers.startsWith( + "Cannot write into field[dim3] using type[VARCHAR ARRAY] and arrayIngestMode[array], " + + "since the existing type is[VARCHAR]")) + )) + .verifyExecutionError(); + } + + /** + * Tests the behaviour of INSERT query when arrayIngestMode is set to none (default) and the user tries to ingest + * string arrays + */ + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceStringArrayWithMvdInArrayMode(String contextName, Map context) + { + final Map adjustedContext = new HashMap<>(context); + adjustedContext.put(MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, "array"); + + testIngestQuery() + .setSql( + "REPLACE INTO arrays OVERWRITE ALL\n" + + "SELECT ARRAY_TO_MV(arrayString) AS arrayString FROM arrays\n" + + "PARTITIONED BY ALL TIME" + ) + .setQueryContext(adjustedContext) + .setExpectedExecutionErrorMatcher(CoreMatchers.allOf( + CoreMatchers.instanceOf(DruidException.class), + ThrowableMessageMatcher.hasMessage(CoreMatchers.startsWith( + "Cannot write into field[arrayString] using type[VARCHAR] and arrayIngestMode[array], since the " + + "existing type is[VARCHAR ARRAY]. Try adjusting your query to make this column an ARRAY instead " + + "of VARCHAR.")) + )) + .verifyExecutionError(); + } + + /** + * Tests the behaviour of INSERT query when arrayIngestMode is set to none (default) and the user tries to ingest + * string arrays + */ + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceStringArrayWithMvdInMvdMode(String contextName, Map context) + { + final Map adjustedContext = new HashMap<>(context); + adjustedContext.put(MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, "mvd"); + + testIngestQuery() + .setSql( + "REPLACE INTO arrays OVERWRITE ALL\n" + + "SELECT ARRAY_TO_MV(arrayString) AS arrayString FROM arrays\n" + + "PARTITIONED BY ALL TIME" + ) + .setQueryContext(adjustedContext) + .setExpectedExecutionErrorMatcher(CoreMatchers.allOf( + CoreMatchers.instanceOf(DruidException.class), + ThrowableMessageMatcher.hasMessage(CoreMatchers.startsWith( + "Cannot write into field[arrayString] using type[VARCHAR] and arrayIngestMode[mvd], since the " + + "existing type is[VARCHAR ARRAY]. Try setting arrayIngestMode to[array] and adjusting your query to " + + "make this column an ARRAY instead of VARCHAR.")) + )) + .verifyExecutionError(); + } + + /** + * Tests the behaviour of INSERT query when arrayIngestMode is set to none (default) and the user tries to ingest + * string arrays + */ + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceMvdWithStringArraySkipValidation(String contextName, Map context) + { + final Map adjustedContext = new HashMap<>(context); + adjustedContext.put(MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, "array"); + adjustedContext.put(MultiStageQueryContext.CTX_SKIP_TYPE_VERIFICATION, "dim3"); + + RowSignature rowSignature = RowSignature.builder() + .add("__time", ColumnType.LONG) + .add("dim3", ColumnType.STRING_ARRAY) + .build(); + + testIngestQuery() + .setSql( + "REPLACE INTO foo OVERWRITE ALL\n" + + "SELECT MV_TO_ARRAY(dim3) AS dim3 FROM foo\n" + + "PARTITIONED BY ALL TIME" + ) + .setQueryContext(adjustedContext) + .setExpectedDataSource("foo") + .setExpectedRowSignature(rowSignature) + .setExpectedSegment(ImmutableSet.of(SegmentId.of("foo", Intervals.ETERNITY, "test", 0))) + .setExpectedResultRows( + NullHandling.sqlCompatible() + ? ImmutableList.of( + new Object[]{0L, null}, + new Object[]{0L, null}, + new Object[]{0L, new Object[]{"a", "b"}}, + new Object[]{0L, new Object[]{""}}, + new Object[]{0L, new Object[]{"b", "c"}}, + new Object[]{0L, new Object[]{"d"}} + ) + : ImmutableList.of( + new Object[]{0L, null}, + new Object[]{0L, null}, + new Object[]{0L, null}, + new Object[]{0L, new Object[]{"a", "b"}}, + new Object[]{0L, new Object[]{"b", "c"}}, + new Object[]{0L, new Object[]{"d"}} + ) + ) + .verifyResults(); + } + + /** + * Tests the behaviour of INSERT query when arrayIngestMode is set to none (default) and the user tries to ingest + * string arrays + */ + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceMvdWithMvd(String contextName, Map context) + { + final Map adjustedContext = new HashMap<>(context); + adjustedContext.put(MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, "array"); + + RowSignature rowSignature = RowSignature.builder() + .add("__time", ColumnType.LONG) + .add("dim3", ColumnType.STRING) + .build(); + + testIngestQuery() + .setSql( + "REPLACE INTO foo OVERWRITE ALL\n" + + "SELECT dim3 FROM foo\n" + + "PARTITIONED BY ALL TIME" + ) + .setQueryContext(adjustedContext) + .setExpectedDataSource("foo") + .setExpectedRowSignature(rowSignature) + .setExpectedSegment(ImmutableSet.of(SegmentId.of("foo", Intervals.ETERNITY, "test", 0))) + .setExpectedResultRows( + ImmutableList.of( + new Object[]{0L, null}, + new Object[]{0L, null}, + new Object[]{0L, NullHandling.sqlCompatible() ? "" : null}, + new Object[]{0L, ImmutableList.of("a", "b")}, + new Object[]{0L, ImmutableList.of("b", "c")}, + new Object[]{0L, "d"} + ) + ) + .verifyResults(); + } /** * Tests the behaviour of INSERT query when arrayIngestMode is set to mvd (default) and the only array type to be * ingested is string array */ - @Test - public void testInsertOnFoo1WithMultiValueToArrayGroupByWithDefaultContext() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithMultiValueToArrayGroupByWithDefaultContext(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -176,8 +341,9 @@ public void testInsertOnFoo1WithMultiValueToArrayGroupByWithDefaultContext() /** * Tests the INSERT query when 'auto' type is set */ - @Test - public void testInsertArraysAutoType() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertArraysAutoType(String contextName, Map context) { List expectedRows = Arrays.asList( new Object[]{1672531200000L, null, null, null}, @@ -229,8 +395,9 @@ public void testInsertArraysAutoType() * Tests the behaviour of INSERT query when arrayIngestMode is set to mvd and the user tries to ingest numeric array * types as well */ - @Test - public void testInsertArraysWithStringArraysAsMVDs() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertArraysWithStringArraysAsMVDs(String contextName, Map context) { final Map adjustedContext = new HashMap<>(context); adjustedContext.put(MultiStageQueryContext.CTX_ARRAY_INGEST_MODE, "mvd"); @@ -263,8 +430,9 @@ public void testInsertArraysWithStringArraysAsMVDs() * Tests the behaviour of INSERT query when arrayIngestMode is set to array and the user tries to ingest all * array types */ - @Test - public void testInsertArraysAsArrays() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertArraysAsArrays(String contextName, Map context) { final List expectedRows = Arrays.asList( new Object[]{ @@ -435,27 +603,30 @@ public void testInsertArraysAsArrays() .verifyResults(); } - @Test - public void testSelectOnArraysWithArrayIngestModeAsNone() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnArraysWithArrayIngestModeAsNone(String contextName, Map context) { - testSelectOnArrays("none"); + testSelectOnArrays(contextName, context, "none"); } - @Test - public void testSelectOnArraysWithArrayIngestModeAsMVD() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnArraysWithArrayIngestModeAsMVD(String contextName, Map context) { - testSelectOnArrays("mvd"); + testSelectOnArrays(contextName, context, "mvd"); } - @Test - public void testSelectOnArraysWithArrayIngestModeAsArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnArraysWithArrayIngestModeAsArray(String contextName, Map context) { - testSelectOnArrays("array"); + testSelectOnArrays(contextName, context, "array"); } // Tests the behaviour of the select with the given arrayIngestMode. The expectation should be the same, since the // arrayIngestMode should only determine how the array gets ingested at the end. - public void testSelectOnArrays(String arrayIngestMode) + public void testSelectOnArrays(String contextName, Map context, String arrayIngestMode) { final List expectedRows = Arrays.asList( new Object[]{ @@ -475,7 +646,7 @@ public void testSelectOnArrays(String arrayIngestMode) null, Arrays.asList(3.3d, 4.4d, 5.5d), Arrays.asList(999.0d, null, 5.5d), - }, + }, new Object[]{ 1672531200000L, Arrays.asList("b", "c"), @@ -583,7 +754,7 @@ public void testSelectOnArrays(String arrayIngestMode) Arrays.asList(2L, 3L), null, Arrays.asList(null, 1.1d), - } + } ); RowSignature rowSignatureWithoutTimeColumn = @@ -672,8 +843,9 @@ public void testSelectOnArrays(String arrayIngestMode) .verifyResults(); } - @Test - public void testScanWithOrderByOnStringArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testScanWithOrderByOnStringArray(String contextName, Map context) { final List expectedRows = Arrays.asList( new Object[]{Arrays.asList("d", "e")}, @@ -735,8 +907,9 @@ public void testScanWithOrderByOnStringArray() .verifyResults(); } - @Test - public void testScanWithOrderByOnLongArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testScanWithOrderByOnLongArray(String contextName, Map context) { final List expectedRows = Arrays.asList( new Object[]{null}, @@ -797,8 +970,9 @@ public void testScanWithOrderByOnLongArray() .verifyResults(); } - @Test - public void testScanWithOrderByOnDoubleArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testScanWithOrderByOnDoubleArray(String contextName, Map context) { final List expectedRows = Arrays.asList( new Object[]{null}, @@ -859,8 +1033,9 @@ public void testScanWithOrderByOnDoubleArray() .verifyResults(); } - @Test - public void testScanExternBooleanArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testScanExternBooleanArray(String contextName, Map context) { final List expectedRows = Collections.singletonList( new Object[]{Arrays.asList(1L, 0L, null)} @@ -906,8 +1081,9 @@ public void testScanExternBooleanArray() .verifyResults(); } - @Test - public void testScanExternArrayWithNonConvertibleType() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testScanExternArrayWithNonConvertibleType(String contextName, Map context) { final List expectedRows = Collections.singletonList( new Object[]{Arrays.asList(null, null)} diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQDataSketchesTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQDataSketchesTest.java index 1f856027de30..15fa79390c91 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQDataSketchesTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQDataSketchesTest.java @@ -36,7 +36,7 @@ import org.apache.druid.sql.calcite.planner.ColumnMapping; import org.apache.druid.sql.calcite.planner.ColumnMappings; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests of MSQ with functions from the "druid-datasketches" extension. diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQExportTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQExportTest.java index e6c3b5e2931a..4481d046c92c 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQExportTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQExportTest.java @@ -26,15 +26,14 @@ import org.apache.druid.msq.util.MultiStageQueryContext; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.segment.column.RowSignature; -import org.apache.druid.sql.calcite.export.TestExportStorageConnector; -import org.apache.druid.sql.http.ResultFormat; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.nio.charset.Charset; +import java.io.InputStreamReader; +import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,14 +45,13 @@ public class MSQExportTest extends MSQTestBase @Test public void testExport() throws IOException { - TestExportStorageConnector storageConnector = (TestExportStorageConnector) exportStorageConnectorProvider.get(); - RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) .add("dim1", ColumnType.STRING) .add("cnt", ColumnType.LONG).build(); - final String sql = StringUtils.format("insert into extern(%s()) as csv select cnt, dim1 from foo", TestExportStorageConnector.TYPE_NAME); + File exportDir = newTempFolder("export"); + final String sql = StringUtils.format("insert into extern(local(exportPath=>'%s')) as csv select cnt, dim1 as dim from foo", exportDir.getAbsolutePath()); testIngestQuery().setSql(sql) .setExpectedDataSource("foo1") @@ -63,23 +61,59 @@ public void testExport() throws IOException .setExpectedResultRows(ImmutableList.of()) .verifyResults(); - List objects = expectedFooFileContents(); + Assert.assertEquals( + 1, + Objects.requireNonNull(new File(exportDir.getAbsolutePath()).listFiles()).length + ); + File resultFile = new File(exportDir, "query-test-query-worker0-partition0.csv"); + List results = readResultsFromFile(resultFile); Assert.assertEquals( - convertResultsToString(objects), - new String(storageConnector.getByteArrayOutputStream().toByteArray(), Charset.defaultCharset()) + expectedFooFileContents(true), + results ); } @Test - public void testNumberOfRowsPerFile() throws IOException + public void testExport2() throws IOException + { + RowSignature rowSignature = RowSignature.builder() + .add("dim1", ColumnType.STRING) + .add("cnt", ColumnType.LONG).build(); + + File exportDir = newTempFolder("export"); + final String sql = StringUtils.format("insert into extern(local(exportPath=>'%s')) as csv select dim1 as table_dim, count(*) as table_count from foo where dim1 = 'abc' group by 1", exportDir.getAbsolutePath()); + + testIngestQuery().setSql(sql) + .setExpectedDataSource("foo1") + .setQueryContext(DEFAULT_MSQ_CONTEXT) + .setExpectedRowSignature(rowSignature) + .setExpectedSegment(ImmutableSet.of()) + .setExpectedResultRows(ImmutableList.of()) + .verifyResults(); + + Assert.assertEquals( + 1, + Objects.requireNonNull(new File(exportDir.getAbsolutePath()).listFiles()).length + ); + + File resultFile = new File(exportDir, "query-test-query-worker0-partition0.csv"); + List results = readResultsFromFile(resultFile); + Assert.assertEquals( + expectedFoo2FileContents(true), + results + ); + } + + @Test + public void testNumberOfRowsPerFile() { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) .add("dim1", ColumnType.STRING) .add("cnt", ColumnType.LONG).build(); - File exportDir = temporaryFolder.newFolder("export/"); + File exportDir = newTempFolder("export"); Map queryContext = new HashMap<>(DEFAULT_MSQ_CONTEXT); queryContext.put(MultiStageQueryContext.CTX_ROWS_PER_PAGE, 1); @@ -95,36 +129,48 @@ public void testNumberOfRowsPerFile() throws IOException .verifyResults(); Assert.assertEquals( - expectedFooFileContents().size(), + expectedFooFileContents(false).size(), Objects.requireNonNull(new File(exportDir.getAbsolutePath()).listFiles()).length ); } - private List expectedFooFileContents() + private List expectedFooFileContents(boolean withHeader) + { + ArrayList expectedResults = new ArrayList<>(); + if (withHeader) { + expectedResults.add("cnt,dim"); + } + expectedResults.addAll(ImmutableList.of( + "1,", + "1,10.1", + "1,2", + "1,1", + "1,def", + "1,abc" + ) + ); + return expectedResults; + } + + private List expectedFoo2FileContents(boolean withHeader) { - return new ArrayList<>(ImmutableList.of( - new Object[]{"1", null}, - new Object[]{"1", 10.1}, - new Object[]{"1", 2}, - new Object[]{"1", 1}, - new Object[]{"1", "def"}, - new Object[]{"1", "abc"} - )); + ArrayList expectedResults = new ArrayList<>(); + if (withHeader) { + expectedResults.add("table_dim,table_count"); + } + expectedResults.addAll(ImmutableList.of("abc,1")); + return expectedResults; } - private String convertResultsToString(List expectedRows) throws IOException + private List readResultsFromFile(File resultFile) throws IOException { - ByteArrayOutputStream expectedResult = new ByteArrayOutputStream(); - ResultFormat.Writer formatter = ResultFormat.CSV.createFormatter(expectedResult, objectMapper); - formatter.writeResponseStart(); - for (Object[] row : expectedRows) { - formatter.writeRowStart(); - for (Object object : row) { - formatter.writeRowField("", object); + List results = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(resultFile.toPath()), StringUtils.UTF8_STRING))) { + String line; + while (!(line = br.readLine()).isEmpty()) { + results.add(line); } - formatter.writeRowEnd(); + return results; } - formatter.writeResponseEnd(); - return new String(expectedResult.toByteArray(), Charset.defaultCharset()); } } diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQFaultsTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQFaultsTest.java index 974f617ff2dd..b3b1442074b7 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQFaultsTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQFaultsTest.java @@ -19,6 +19,7 @@ package org.apache.druid.msq.exec; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.apache.druid.error.DruidException; @@ -40,7 +41,6 @@ import org.apache.druid.msq.indexing.error.TooManyInputFilesFault; import org.apache.druid.msq.indexing.error.TooManyPartitionsFault; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.msq.test.MSQTestTaskActionClient; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.segment.column.RowSignature; @@ -49,13 +49,16 @@ import org.apache.druid.timeline.partition.DimensionRangeShardSpec; import org.apache.druid.timeline.partition.LinearShardSpec; import org.hamcrest.CoreMatchers; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -291,7 +294,7 @@ public void testInsertWithTooManySegments() throws IOException .add("__time", ColumnType.LONG) .build(); - File file = MSQTestFileUtils.generateTemporaryNdJsonFile(temporaryFolder, 30000, 1); + File file = createNdJsonFile(newTempFile("ndjson30k"), 30000, 1); String filePathAsJson = queryFramework().queryJsonMapper().writeValueAsString(file.getAbsolutePath()); testIngestQuery().setSql(" insert into foo1 SELECT\n" @@ -311,6 +314,27 @@ public void testInsertWithTooManySegments() throws IOException } + /** + * Helper method that populates a file with {@code numRows} rows and {@code numColumns} columns where the + * first column is a string 'timestamp' while the rest are string columns with junk value + */ + public static File createNdJsonFile(File file, final int numRows, final int numColumns) throws IOException + { + for (int currentRow = 0; currentRow < numRows; ++currentRow) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append("\"timestamp\":\"2016-06-27T00:00:11.080Z\""); + for (int currentColumn = 1; currentColumn < numColumns; ++currentColumn) { + sb.append(StringUtils.format(",\"column%s\":\"val%s\"", currentColumn, currentRow)); + } + sb.append("}"); + Files.write(file.toPath(), ImmutableList.of(sb.toString()), StandardCharsets.UTF_8, StandardOpenOption.APPEND); + } + return file; + } + + + @Test public void testInsertWithManyColumns() { @@ -399,7 +423,7 @@ public void testTooManyInputFiles() throws IOException final int numFiles = 20000; - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); String externalFiles = String.join(", ", Collections.nCopies(numFiles, toReadFileNameAsJson)); diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQInsertTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQInsertTest.java index ebee2e042a21..d22b2d7481ed 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQInsertTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQInsertTest.java @@ -38,7 +38,6 @@ import org.apache.druid.msq.kernel.WorkerAssignmentStrategy; import org.apache.druid.msq.test.CounterSnapshotMatcher; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.msq.util.MultiStageQueryContext; import org.apache.druid.query.QueryContexts; import org.apache.druid.query.aggregation.LongSumAggregatorFactory; @@ -48,10 +47,9 @@ import org.apache.druid.segment.column.ValueType; import org.apache.druid.timeline.SegmentId; import org.hamcrest.CoreMatchers; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import java.io.File; @@ -67,7 +65,6 @@ import java.util.TreeSet; -@RunWith(Parameterized.class) public class MSQInsertTest extends MSQTestBase { @@ -82,7 +79,6 @@ public class MSQInsertTest extends MSQTestBase .build(); private final HashFunction fn = Hashing.murmur3_128(); - @Parameterized.Parameters(name = "{index}:with context {0}") public static Collection data() { Object[][] data = new Object[][]{ @@ -94,15 +90,9 @@ public static Collection data() }; return Arrays.asList(data); } - - @Parameterized.Parameter(0) - public String contextName; - - @Parameterized.Parameter(1) - public Map context; - - @Test - public void testInsertOnFoo1() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1(String contextName, Map context) { List expectedRows = expectedFooRows(); int expectedCounterRows = expectedRows.size(); @@ -154,8 +144,9 @@ public void testInsertOnFoo1() } - @Test - public void testInsertWithExistingTimeColumn() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWithExistingTimeColumn(String contextName, Map context) throws IOException { List expectedRows = ImmutableList.of( new Object[] {1678897351000L, "A"}, @@ -168,9 +159,7 @@ public void testInsertWithExistingTimeColumn() throws IOException .add("flags", ColumnType.STRING) .build(); - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, - "/dataset-with-time-column.json" - ); + final File toRead = getResourceAsTemporaryFile("/dataset-with-time-column.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); testIngestQuery().setSql(" INSERT INTO foo1 SELECT\n" @@ -193,8 +182,9 @@ public void testInsertWithExistingTimeColumn() throws IOException } - @Test - public void testInsertWithUnnestInline() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWithUnnestInline(String contextName, Map context) { List expectedRows = ImmutableList.of( new Object[]{1692226800000L, 1L}, @@ -218,8 +208,9 @@ public void testInsertWithUnnestInline() } - @Test - public void testInsertWithUnnest() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWithUnnest(String contextName, Map context) { List expectedRows = ImmutableList.of( new Object[]{946684800000L, "a"}, @@ -248,8 +239,9 @@ public void testInsertWithUnnest() } - @Test - public void testInsertWithUnnestWithVirtualColumns() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWithUnnestWithVirtualColumns(String contextName, Map context) { List expectedRows = ImmutableList.of( new Object[]{946684800000L, 1.0f}, @@ -282,10 +274,11 @@ public void testInsertWithUnnestWithVirtualColumns() } - @Test - public void testInsertOnExternalDataSource() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnExternalDataSource(String contextName, Map context) throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -347,8 +340,9 @@ public void testInsertOnExternalDataSource() throws IOException } - @Test - public void testInsertOnFoo1WithGroupByLimitWithoutClusterBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithGroupByLimitWithoutClusterBy(String contextName, Map context) { List expectedRows = expectedFooRows(); int expectedCounterRows = expectedRows.size(); @@ -395,8 +389,9 @@ public void testInsertOnFoo1WithGroupByLimitWithoutClusterBy() } - @Test - public void testInsertOnFoo1WithTwoCountAggregatorsWithRollupContext() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithTwoCountAggregatorsWithRollupContext(String contextName, Map context) { final List expectedRows = expectedFooRows(); @@ -434,8 +429,9 @@ public void testInsertOnFoo1WithTwoCountAggregatorsWithRollupContext() .verifyResults(); } - @Test - public void testInsertOnFoo1WithGroupByLimitWithClusterBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithGroupByLimitWithClusterBy(String contextName, Map context) { List expectedRows = expectedFooRows(); int expectedCounterRows = expectedRows.size(); @@ -485,8 +481,10 @@ public void testInsertOnFoo1WithGroupByLimitWithClusterBy() .verifyResults(); } - @Test - public void testInsertOnFoo1WithTimeFunction() + + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithTimeFunction(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -504,8 +502,9 @@ public void testInsertOnFoo1WithTimeFunction() } - @Test - public void testInsertOnFoo1WithTimeAggregator() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithTimeAggregator(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -534,8 +533,9 @@ public void testInsertOnFoo1WithTimeAggregator() } - @Test - public void testInsertOnFoo1WithTimeAggregatorAndMultipleWorkers() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithTimeAggregatorAndMultipleWorkers(String contextName, Map context) { Map localContext = new HashMap<>(context); localContext.put(MultiStageQueryContext.CTX_TASK_ASSIGNMENT_STRATEGY, WorkerAssignmentStrategy.MAX.name()); @@ -568,8 +568,9 @@ public void testInsertOnFoo1WithTimeAggregatorAndMultipleWorkers() } - @Test - public void testInsertOnFoo1WithTimePostAggregator() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithTimePostAggregator(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -599,8 +600,9 @@ public void testInsertOnFoo1WithTimePostAggregator() } - @Test - public void testInsertOnFoo1WithTimeFunctionWithSequential() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithTimeFunctionWithSequential(String contextName, Map context) { List expectedRows = expectedFooRows(); int expectedCounterRows = expectedRows.size(); @@ -610,7 +612,7 @@ public void testInsertOnFoo1WithTimeFunctionWithSequential() .add("__time", ColumnType.LONG) .add("dim1", ColumnType.STRING) .add("cnt", ColumnType.LONG).build(); - Map context = ImmutableMap.builder() + Map newContext = ImmutableMap.builder() .putAll(DEFAULT_MSQ_CONTEXT) .put( MultiStageQueryContext.CTX_CLUSTER_STATISTICS_MERGE_MODE, @@ -620,10 +622,10 @@ public void testInsertOnFoo1WithTimeFunctionWithSequential() testIngestQuery().setSql( "insert into foo1 select floor(__time to day) as __time , dim1 , count(*) as cnt from foo where dim1 is not null group by 1, 2 PARTITIONED by day clustered by dim1") - .setQueryContext(context) + .setQueryContext(newContext) .setExpectedDataSource("foo1") .setExpectedRowSignature(rowSignature) - .setQueryContext(MSQInsertTest.this.context) + .setQueryContext(context) .setExpectedSegment(expectedFooSegments()) .setExpectedResultRows(expectedRows) .setExpectedCountersForStageWorkerChannel( @@ -660,8 +662,9 @@ public void testInsertOnFoo1WithTimeFunctionWithSequential() } - @Test - public void testInsertOnFoo1WithMultiValueDim() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithMultiValueDim(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -677,8 +680,9 @@ public void testInsertOnFoo1WithMultiValueDim() .verifyResults(); } - @Test - public void testInsertOnFoo1MultiValueDimWithLimitWithoutClusterBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1MultiValueDimWithLimitWithoutClusterBy(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -694,8 +698,9 @@ public void testInsertOnFoo1MultiValueDimWithLimitWithoutClusterBy() .verifyResults(); } - @Test - public void testInsertOnFoo1MultiValueDimWithLimitWithClusterBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1MultiValueDimWithLimitWithClusterBy(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -711,8 +716,9 @@ public void testInsertOnFoo1MultiValueDimWithLimitWithClusterBy() .verifyResults(); } - @Test - public void testInsertOnFoo1WithMultiValueDimGroupBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithMultiValueDimGroupBy(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -728,8 +734,9 @@ public void testInsertOnFoo1WithMultiValueDimGroupBy() .verifyResults(); } - @Test - public void testInsertOnFoo1WithMultiValueMeasureGroupBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithMultiValueMeasureGroupBy(String contextName, Map context) { testIngestQuery().setSql( "INSERT INTO foo1 SELECT count(dim3) FROM foo WHERE dim3 IS NOT NULL GROUP BY 1 PARTITIONED BY ALL TIME") @@ -742,9 +749,9 @@ public void testInsertOnFoo1WithMultiValueMeasureGroupBy() } - - @Test - public void testInsertOnFoo1WithAutoTypeArrayGroupBy() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithAutoTypeArrayGroupBy(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -777,8 +784,9 @@ public void testInsertOnFoo1WithAutoTypeArrayGroupBy() .verifyResults(); } - @Test - public void testInsertOnFoo1WithArrayIngestModeArrayGroupByInsertAsArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithArrayIngestModeArrayGroupByInsertAsArray(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -812,8 +820,9 @@ public void testInsertOnFoo1WithArrayIngestModeArrayGroupByInsertAsArray() .verifyResults(); } - @Test - public void testInsertOnFoo1WithArrayIngestModeArrayGroupByInsertAsMvd() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithArrayIngestModeArrayGroupByInsertAsMvd(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -847,8 +856,9 @@ public void testInsertOnFoo1WithArrayIngestModeArrayGroupByInsertAsMvd() .verifyResults(); } - @Test - public void testInsertOnFoo1WithMultiValueDimGroupByWithoutGroupByEnable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOnFoo1WithMultiValueDimGroupByWithoutGroupByEnable(String contextName, Map context) { Map localContext = ImmutableMap.builder() .putAll(context) @@ -868,8 +878,9 @@ public void testInsertOnFoo1WithMultiValueDimGroupByWithoutGroupByEnable() .verifyExecutionError(); } - @Test - public void testRollUpOnFoo1UpOnFoo1() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testRollUpOnFoo1UpOnFoo1(String contextName, Map context) { List expectedRows = expectedFooRows(); int expectedCounterRows = expectedRows.size(); @@ -925,8 +936,9 @@ public void testRollUpOnFoo1UpOnFoo1() } - @Test - public void testRollUpOnFoo1WithTimeFunction() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testRollUpOnFoo1WithTimeFunction(String contextName, Map context) { List expectedRows = expectedFooRows(); int expectedCounterRows = expectedRows.size(); @@ -982,8 +994,9 @@ public void testRollUpOnFoo1WithTimeFunction() } - @Test - public void testInsertWithClusteredByDescendingThrowsException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWithClusteredByDescendingThrowsException(String contextName, Map context) { // Add a DESC clustered by column, which should not be allowed testIngestQuery().setSql("INSERT INTO foo1 " @@ -999,8 +1012,9 @@ public void testInsertWithClusteredByDescendingThrowsException() .verifyPlanningErrors(); } - @Test - public void testRollUpOnFoo1WithTimeFunctionComplexCol() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testRollUpOnFoo1WithTimeFunctionComplexCol(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -1025,8 +1039,9 @@ public void testRollUpOnFoo1WithTimeFunctionComplexCol() } - @Test - public void testRollUpOnFoo1ComplexCol() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testRollUpOnFoo1ComplexCol(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -1048,10 +1063,11 @@ public void testRollUpOnFoo1ComplexCol() } - @Test - public void testRollUpOnExternalDataSource() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testRollUpOnExternalDataSource(String contextName, Map context) throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1115,10 +1131,11 @@ public void testRollUpOnExternalDataSource() throws IOException .verifyResults(); } - @Test() - public void testRollUpOnExternalDataSourceWithCompositeKey() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testRollUpOnExternalDataSourceWithCompositeKey(String contextName, Map context) throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1191,8 +1208,9 @@ public void testRollUpOnExternalDataSourceWithCompositeKey() throws IOException } - @Test - public void testInsertWrongTypeTimestamp() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWrongTypeTimestamp(String contextName, Map context) { final RowSignature rowSignature = RowSignature.builder() @@ -1215,13 +1233,14 @@ public void testInsertWrongTypeTimestamp() DruidException.Persona.USER, DruidException.Category.INVALID_INPUT, "invalidInput" - ).expectMessageIs("Field [__time] was the wrong type [VARCHAR], expected TIMESTAMP") + ).expectMessageIs("Field[__time] was the wrong type[VARCHAR], expected TIMESTAMP") ) .verifyPlanningErrors(); } - @Test - public void testIncorrectInsertQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testIncorrectInsertQuery(String contextName, Map context) { testIngestQuery() .setSql( @@ -1234,8 +1253,9 @@ public void testIncorrectInsertQuery() } - @Test - public void testInsertRestrictedColumns() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertRestrictedColumns(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -1262,8 +1282,9 @@ public void testInsertRestrictedColumns() .verifyResults(); } - @Test - public void testInsertDuplicateColumnNames() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertDuplicateColumnNames(String contextName, Map context) { testIngestQuery() .setSql(" insert into foo1 SELECT\n" @@ -1284,8 +1305,9 @@ public void testInsertDuplicateColumnNames() .verifyPlanningErrors(); } - @Test - public void testInsertQueryWithInvalidSubtaskCount() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertQueryWithInvalidSubtaskCount(String contextName, Map context) { Map localContext = new HashMap<>(context); localContext.put(MultiStageQueryContext.CTX_MAX_NUM_TASKS, 1); @@ -1306,10 +1328,11 @@ public void testInsertQueryWithInvalidSubtaskCount() .verifyExecutionError(); } - @Test - public void testInsertWithTooLargeRowShouldThrowException() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertWithTooLargeRowShouldThrowException(String contextName, Map context) throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); Mockito.doReturn(500).when(workerMemoryParameters).getLargeFrameSize(); @@ -1335,8 +1358,9 @@ public void testInsertWithTooLargeRowShouldThrowException() throws IOException .verifyExecutionError(); } - @Test - public void testInsertLimitWithPeriodGranularityThrowsException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertLimitWithPeriodGranularityThrowsException(String contextName, Map context) { testIngestQuery().setSql(" INSERT INTO foo " + "SELECT __time, m1 " @@ -1352,8 +1376,9 @@ public void testInsertLimitWithPeriodGranularityThrowsException() .verifyPlanningErrors(); } - @Test - public void testInsertOffsetThrowsException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testInsertOffsetThrowsException(String contextName, Map context) { testIngestQuery().setSql(" INSERT INTO foo " + "SELECT __time, m1 " @@ -1368,20 +1393,21 @@ public void testInsertOffsetThrowsException() .verifyPlanningErrors(); } - @Test - public void testCorrectNumberOfWorkersUsedAutoModeWithoutBytesLimit() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testCorrectNumberOfWorkersUsedAutoModeWithoutBytesLimit(String contextName, Map context) throws IOException { Map localContext = new HashMap<>(context); localContext.put(MultiStageQueryContext.CTX_TASK_ASSIGNMENT_STRATEGY, WorkerAssignmentStrategy.AUTO.name()); localContext.put(MultiStageQueryContext.CTX_MAX_NUM_TASKS, 4); - final File toRead1 = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/multipleFiles/wikipedia-sampled-1.json"); + final File toRead1 = getResourceAsTemporaryFile("/multipleFiles/wikipedia-sampled-1.json"); final String toReadFileNameAsJson1 = queryFramework().queryJsonMapper().writeValueAsString(toRead1.getAbsolutePath()); - final File toRead2 = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/multipleFiles/wikipedia-sampled-2.json"); + final File toRead2 = getResourceAsTemporaryFile("/multipleFiles/wikipedia-sampled-2.json"); final String toReadFileNameAsJson2 = queryFramework().queryJsonMapper().writeValueAsString(toRead2.getAbsolutePath()); - final File toRead3 = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/multipleFiles/wikipedia-sampled-3.json"); + final File toRead3 = getResourceAsTemporaryFile("/multipleFiles/wikipedia-sampled-3.json"); final String toReadFileNameAsJson3 = queryFramework().queryJsonMapper().writeValueAsString(toRead3.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1418,21 +1444,22 @@ public void testCorrectNumberOfWorkersUsedAutoModeWithoutBytesLimit() throws IOE } - @Test - public void testCorrectNumberOfWorkersUsedAutoModeWithBytesLimit() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testCorrectNumberOfWorkersUsedAutoModeWithBytesLimit(String contextName, Map context) throws IOException { Map localContext = new HashMap<>(context); localContext.put(MultiStageQueryContext.CTX_TASK_ASSIGNMENT_STRATEGY, WorkerAssignmentStrategy.AUTO.name()); localContext.put(MultiStageQueryContext.CTX_MAX_NUM_TASKS, 4); localContext.put(MultiStageQueryContext.CTX_MAX_INPUT_BYTES_PER_WORKER, 10); - final File toRead1 = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/multipleFiles/wikipedia-sampled-1.json"); + final File toRead1 = getResourceAsTemporaryFile("/multipleFiles/wikipedia-sampled-1.json"); final String toReadFileNameAsJson1 = queryFramework().queryJsonMapper().writeValueAsString(toRead1.getAbsolutePath()); - final File toRead2 = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/multipleFiles/wikipedia-sampled-2.json"); + final File toRead2 = getResourceAsTemporaryFile("/multipleFiles/wikipedia-sampled-2.json"); final String toReadFileNameAsJson2 = queryFramework().queryJsonMapper().writeValueAsString(toRead2.getAbsolutePath()); - final File toRead3 = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/multipleFiles/wikipedia-sampled-3.json"); + final File toRead3 = getResourceAsTemporaryFile("/multipleFiles/wikipedia-sampled-3.json"); final String toReadFileNameAsJson3 = queryFramework().queryJsonMapper().writeValueAsString(toRead3.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1468,9 +1495,11 @@ public void testCorrectNumberOfWorkersUsedAutoModeWithBytesLimit() throws IOExce .verifyResults(); } - @Test - public void testEmptyInsertQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyInsertQuery(String contextName, Map context) { + // Insert with a condition which results in 0 rows being inserted -- do nothing. testIngestQuery().setSql( "INSERT INTO foo1 " @@ -1484,9 +1513,11 @@ public void testEmptyInsertQuery() .verifyResults(); } - @Test - public void testEmptyInsertQueryWithAllGranularity() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyInsertQueryWithAllGranularity(String contextName, Map context) { + // Insert with a condition which results in 0 rows being inserted -- do nothing. testIngestQuery().setSql( "INSERT INTO foo1 " @@ -1500,9 +1531,11 @@ public void testEmptyInsertQueryWithAllGranularity() .verifyResults(); } - @Test - public void testEmptyInsertLimitQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyInsertLimitQuery(String contextName, Map context) { + // Insert with a condition which results in 0 rows being inserted -- do nothing. testIngestQuery().setSql( "INSERT INTO foo1 " diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQLoadedSegmentTests.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQLoadedSegmentTests.java index c787066937fa..ab06f851af06 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQLoadedSegmentTests.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQLoadedSegmentTests.java @@ -51,8 +51,8 @@ import org.apache.druid.timeline.partition.LinearShardSpec; import org.hamcrest.CoreMatchers; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.Map; @@ -87,7 +87,7 @@ public class MSQLoadedSegmentTests extends MSQTestBase 2 ); - @Before + @BeforeEach public void setUp() { loadedSegmentsMetadata.add(new ImmutableSegmentLoadInfo(LOADED_SEGMENT_1, ImmutableSet.of(DATA_SERVER_1))); diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQParseExceptionsTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQParseExceptionsTest.java index 7dd9674e06a6..e1d0520362b2 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQParseExceptionsTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQParseExceptionsTest.java @@ -32,7 +32,6 @@ import org.apache.druid.msq.indexing.error.InvalidNullByteFault; import org.apache.druid.msq.querykit.scan.ExternalColumnSelectorFactory; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.query.dimension.DefaultDimensionSpec; import org.apache.druid.query.groupby.GroupByQuery; import org.apache.druid.query.scan.ScanQuery; @@ -44,7 +43,7 @@ import org.apache.druid.sql.calcite.planner.ColumnMapping; import org.apache.druid.sql.calcite.planner.ColumnMappings; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; @@ -53,15 +52,11 @@ public class MSQParseExceptionsTest extends MSQTestBase { - + @Test public void testIngestWithNullByte() throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile( - temporaryFolder, - this, - "/unparseable-null-byte-string.csv" - ); + final File toRead = getResourceAsTemporaryFile("/unparseable-null-byte-string.csv"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -145,11 +140,7 @@ public void testIngestWithNullByteInSqlExpression() @Test public void testIngestWithSanitizedNullByte() throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile( - temporaryFolder, - this, - "/unparseable-null-byte-string.csv" - ); + final File toRead = getResourceAsTemporaryFile("/unparseable-null-byte-string.csv"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -243,11 +234,7 @@ public void testIngestWithSanitizedNullByte() throws IOException @Test public void testMultiValueStringWithIncorrectType() throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile( - temporaryFolder, - this, - "/unparseable-mv-string-array.json" - ); + final File toRead = getResourceAsTemporaryFile("/unparseable-mv-string-array.json"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java index ea7adc866ee0..77eeed053672 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQReplaceTest.java @@ -31,7 +31,6 @@ import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.msq.test.CounterSnapshotMatcher; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.msq.test.MSQTestTaskActionClient; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.segment.column.RowSignature; @@ -40,13 +39,13 @@ import org.apache.druid.timeline.partition.DimensionRangeShardSpec; import org.easymock.EasyMock; import org.joda.time.Interval; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import javax.annotation.Nonnull; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -58,7 +57,6 @@ import java.util.Set; import java.util.TreeSet; -@RunWith(Parameterized.class) public class MSQReplaceTest extends MSQTestBase { @@ -72,7 +70,6 @@ public class MSQReplaceTest extends MSQTestBase ) .build(); - @Parameterized.Parameters(name = "{index}:with context {0}") public static Collection data() { Object[][] data = new Object[][]{ @@ -84,15 +81,9 @@ public static Collection data() }; return Arrays.asList(data); } - - @Parameterized.Parameter(0) - public String contextName; - - @Parameterized.Parameter(1) - public Map context; - - @Test - public void testReplaceOnFooWithAll() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOnFooWithAll(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -172,8 +163,9 @@ public void testReplaceOnFooWithAll() .verifyResults(); } - @Test - public void testReplaceOnFooWithWhere() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOnFooWithWhere(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -221,14 +213,15 @@ public void testReplaceOnFooWithWhere() .verifyResults(); } - @Test - public void testReplaceOnFoo1WithAllExtern() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOnFoo1WithAllExtern(String contextName, Map context) throws IOException { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) .add("cnt", ColumnType.LONG).build(); - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); testIngestQuery().setSql(" REPLACE INTO foo1 OVERWRITE ALL SELECT " @@ -296,14 +289,15 @@ public void testReplaceOnFoo1WithAllExtern() throws IOException .verifyResults(); } - @Test - public void testReplaceOnFoo1WithWhereExtern() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOnFoo1WithWhereExtern(String contextName, Map context) throws IOException { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) .add("user", ColumnType.STRING).build(); - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); testIngestQuery().setSql( @@ -362,8 +356,9 @@ public void testReplaceOnFoo1WithWhereExtern() throws IOException .verifyResults(); } - @Test - public void testReplaceIncorrectSyntax() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceIncorrectSyntax(String contextName, Map context) { testIngestQuery() .setSql("REPLACE INTO foo1 OVERWRITE SELECT * FROM foo PARTITIONED BY ALL TIME") @@ -376,8 +371,9 @@ public void testReplaceIncorrectSyntax() .verifyPlanningErrors(); } - @Test - public void testReplaceSegmentEntireTable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceSegmentEntireTable(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -433,8 +429,9 @@ public void testReplaceSegmentEntireTable() .verifyResults(); } - @Test - public void testReplaceSegmentsRepartitionTable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceSegmentsRepartitionTable(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -516,8 +513,9 @@ public void testReplaceSegmentsRepartitionTable() .verifyResults(); } - @Test - public void testReplaceWithWhereClause() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceWithWhereClause(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -589,8 +587,9 @@ public void testReplaceWithWhereClause() .verifyResults(); } - @Test - public void testReplaceWhereClauseLargerThanData() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceWhereClauseLargerThanData(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -665,8 +664,9 @@ public void testReplaceWhereClauseLargerThanData() .verifyResults(); } - @Test - public void testReplaceLimitWithPeriodGranularityThrowsException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceLimitWithPeriodGranularityThrowsException(String contextName, Map context) { testIngestQuery().setSql(" REPLACE INTO foo " + "OVERWRITE ALL " @@ -676,13 +676,14 @@ public void testReplaceLimitWithPeriodGranularityThrowsException() + "PARTITIONED BY MONTH") .setQueryContext(context) .setExpectedValidationErrorMatcher(invalidSqlContains( - "INSERT and REPLACE queries cannot have a LIMIT unless PARTITIONED BY is \"ALL\"" + "INSERT and REPLACE queries cannot have a LIMIT unless PARTITIONED BY is \"ALL\"" )) .verifyPlanningErrors(); } - @Test - public void testReplaceOffsetThrowsException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOffsetThrowsException(String contextName, Map context) { testIngestQuery().setSql(" REPLACE INTO foo " + "OVERWRITE ALL " @@ -692,14 +693,15 @@ public void testReplaceOffsetThrowsException() + "OFFSET 10" + "PARTITIONED BY ALL TIME") .setExpectedValidationErrorMatcher(invalidSqlContains( - "INSERT and REPLACE queries cannot have an OFFSET" + "INSERT and REPLACE queries cannot have an OFFSET" )) .setQueryContext(context) .verifyPlanningErrors(); } - @Test - public void testReplaceTimeChunks() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceTimeChunks(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -745,8 +747,9 @@ public void testReplaceTimeChunks() .verifyResults(); } - @Test - public void testReplaceTimeChunksLargerThanData() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceTimeChunksLargerThanData(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -800,8 +803,9 @@ public void testReplaceTimeChunksLargerThanData() .verifyResults(); } - @Test - public void testReplaceAllOverEternitySegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceAllOverEternitySegment(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -852,8 +856,9 @@ public void testReplaceAllOverEternitySegment() .verifyResults(); } - @Test - public void testReplaceOnFoo1Range() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOnFoo1Range(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -875,8 +880,48 @@ public void testReplaceOnFoo1Range() } - @Test - public void testReplaceSegmentsInsertIntoNewTable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceOnFoo1RangeClusteredBySubset(String contextName, Map context) + { + RowSignature rowSignature = RowSignature.builder() + .add("__time", ColumnType.LONG) + .add("dim1", ColumnType.STRING) + .add("m1", ColumnType.FLOAT) + .add("cnt", ColumnType.LONG) + .build(); + + testIngestQuery().setSql( + "REPLACE INTO foo1\n" + + "OVERWRITE ALL\n" + + "SELECT dim1, m1, COUNT(*) AS cnt\n" + + "FROM foo\n" + + "GROUP BY dim1, m1\n" + + "PARTITIONED BY ALL\n" + + "CLUSTERED BY dim1" + ) + .setExpectedDataSource("foo1") + .setQueryContext(DEFAULT_MSQ_CONTEXT) + .setExpectedShardSpec(DimensionRangeShardSpec.class) + .setExpectedRowSignature(rowSignature) + .setQueryContext(context) + .setExpectedSegment(ImmutableSet.of(SegmentId.of("foo1", Intervals.ETERNITY, "test", 0))) + .setExpectedResultRows( + ImmutableList.of( + new Object[]{0L, NullHandling.sqlCompatible() ? "" : null, 1.0f, 1L}, + new Object[]{0L, "1", 4.0f, 1L}, + new Object[]{0L, "10.1", 2.0f, 1L}, + new Object[]{0L, "2", 3.0f, 1L}, + new Object[]{0L, "abc", 6.0f, 1L}, + new Object[]{0L, "def", 5.0f, 1L} + ) + ) + .verifyResults(); + } + + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceSegmentsInsertIntoNewTable(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -906,8 +951,9 @@ public void testReplaceSegmentsInsertIntoNewTable() .verifyResults(); } - @Test - public void testReplaceWithClusteredByDescendingThrowsException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceWithClusteredByDescendingThrowsException(String contextName, Map context) { // Add a DESC clustered by column, which should not be allowed testIngestQuery().setSql(" REPLACE INTO foobar " @@ -916,15 +962,16 @@ public void testReplaceWithClusteredByDescendingThrowsException() + "FROM foo " + "PARTITIONED BY ALL TIME " + "CLUSTERED BY m2, m1 DESC" - ) + ) .setExpectedValidationErrorMatcher( invalidSqlIs("Invalid CLUSTERED BY clause [`m1` DESC]: cannot sort in descending order.") ) .verifyPlanningErrors(); } - @Test - public void testReplaceUnnestSegmentEntireTable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceUnnestSegmentEntireTable(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -982,8 +1029,9 @@ public void testReplaceUnnestSegmentEntireTable() .verifyResults(); } - @Test - public void testReplaceUnnestWithVirtualColumnSegmentEntireTable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceUnnestWithVirtualColumnSegmentEntireTable(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -1045,8 +1093,9 @@ public void testReplaceUnnestWithVirtualColumnSegmentEntireTable() .verifyResults(); } - @Test - public void testReplaceUnnestSegmentWithTimeFilter() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceUnnestSegmentWithTimeFilter(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -1119,8 +1168,9 @@ public void testReplaceUnnestSegmentWithTimeFilter() .verifyResults(); } - @Test - public void testReplaceTombstonesOverPartiallyOverlappingSegments() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testReplaceTombstonesOverPartiallyOverlappingSegments(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -1185,8 +1235,9 @@ public void testReplaceTombstonesOverPartiallyOverlappingSegments() .verifyResults(); } - @Test - public void testEmptyReplaceAll() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceAll(String contextName, Map context) { // An empty replace all with no used segment should effectively be the same as an empty insert testIngestQuery().setSql( @@ -1203,8 +1254,9 @@ public void testEmptyReplaceAll() .verifyResults(); } - @Test - public void testEmptyReplaceInterval() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceInterval(String contextName, Map context) { // An empty replace interval with no used segment should effectively be the same as an empty insert testIngestQuery().setSql( @@ -1221,8 +1273,9 @@ public void testEmptyReplaceInterval() .verifyResults(); } - @Test - public void testEmptyReplaceAllOverExistingSegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceAllOverExistingSegment(String contextName, Map context) { Interval existingSegmentInterval = Intervals.of("2001-01-01T/2001-01-02T"); DataSegment existingDataSegment = DataSegment.builder() @@ -1252,8 +1305,9 @@ public void testEmptyReplaceAllOverExistingSegment() .verifyResults(); } - @Test - public void testEmptyReplaceIntervalOverPartiallyOverlappingSegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceIntervalOverPartiallyOverlappingSegment(String contextName, Map context) { // Create a data segment which lies partially outside the generated segment DataSegment existingDataSegment = DataSegment.builder() @@ -1287,8 +1341,9 @@ public void testEmptyReplaceIntervalOverPartiallyOverlappingSegment() .verifyResults(); } - @Test - public void testEmptyReplaceIntervalOverPartiallyOverlappingStart() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceIntervalOverPartiallyOverlappingStart(String contextName, Map context) { // Create a data segment whose start partially lies outside the query's replace interval DataSegment existingDataSegment = DataSegment.builder() @@ -1324,8 +1379,9 @@ public void testEmptyReplaceIntervalOverPartiallyOverlappingStart() .verifyResults(); } - @Test - public void testEmptyReplaceIntervalOverPartiallyOverlappingEnd() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceIntervalOverPartiallyOverlappingEnd(String contextName, Map context) { // Create a data segment whose end partially lies outside the query's replace interval DataSegment existingDataSegment = DataSegment.builder() @@ -1361,8 +1417,9 @@ public void testEmptyReplaceIntervalOverPartiallyOverlappingEnd() .verifyResults(); } - @Test - public void testEmptyReplaceAllOverEternitySegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceAllOverEternitySegment(String contextName, Map context) { // Create a data segment spanning eternity DataSegment existingDataSegment = DataSegment.builder() @@ -1394,8 +1451,9 @@ public void testEmptyReplaceAllOverEternitySegment() } - @Test - public void testEmptyReplaceAllWithAllGrainOverFiniteIntervalSegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceAllWithAllGrainOverFiniteIntervalSegment(String contextName, Map context) { // Create a finite-interval segment DataSegment existingDataSegment = DataSegment.builder() @@ -1425,8 +1483,9 @@ public void testEmptyReplaceAllWithAllGrainOverFiniteIntervalSegment() .verifyResults(); } - @Test - public void testEmptyReplaceAllWithAllGrainOverEternitySegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceAllWithAllGrainOverEternitySegment(String contextName, Map context) { // Create a segment spanning eternity DataSegment existingDataSegment = DataSegment.builder() @@ -1457,8 +1516,9 @@ public void testEmptyReplaceAllWithAllGrainOverEternitySegment() .verifyResults(); } - @Test - public void testEmptyReplaceAllWithAllGrainOverHalfEternitySegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceAllWithAllGrainOverHalfEternitySegment(String contextName, Map context) { // Create a segment spanning half-eternity DataSegment existingDataSegment = DataSegment.builder() @@ -1488,8 +1548,9 @@ public void testEmptyReplaceAllWithAllGrainOverHalfEternitySegment() .verifyResults(); } - @Test - public void testEmptyReplaceLimitQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceLimitQuery(String contextName, Map context) { // A limit query which results in 0 rows being inserted -- do nothing. testIngestQuery().setSql( @@ -1506,8 +1567,9 @@ public void testEmptyReplaceLimitQuery() .verifyResults(); } - @Test - public void testEmptyReplaceIntervalOverEternitySegment() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testEmptyReplaceIntervalOverEternitySegment(String contextName, Map context) { // Create a data segment spanning eternity DataSegment existingDataSegment = DataSegment.builder() diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQSelectTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQSelectTest.java index c0dfe0f77f39..745cc33040f3 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQSelectTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/exec/MSQSelectTest.java @@ -44,7 +44,6 @@ import org.apache.druid.msq.querykit.common.SortMergeJoinFrameProcessorFactory; import org.apache.druid.msq.test.CounterSnapshotMatcher; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.msq.util.MultiStageQueryContext; import org.apache.druid.query.InlineDataSource; import org.apache.druid.query.LookupDataSource; @@ -81,10 +80,9 @@ import org.apache.druid.sql.calcite.util.CalciteTests; import org.hamcrest.CoreMatchers; import org.junit.Assert; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -98,7 +96,6 @@ import java.util.List; import java.util.Map; -@RunWith(Parameterized.class) public class MSQSelectTest extends MSQTestBase { @@ -126,7 +123,6 @@ public class MSQSelectTest extends MSQTestBase ) .build(); - @Parameterized.Parameters(name = "{index}:with context {0}") public static Collection data() { Object[][] data = new Object[][]{ @@ -140,15 +136,9 @@ public static Collection data() return Arrays.asList(data); } - - @Parameterized.Parameter(0) - public String contextName; - - @Parameterized.Parameter(1) - public Map context; - - @Test - public void testCalculator() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testCalculator(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("EXPR$0", ColumnType.LONG) @@ -173,7 +163,7 @@ public void testCalculator() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -183,8 +173,9 @@ public void testCalculator() .setExpectedResultRows(ImmutableList.of(new Object[]{2})).verifyResults(); } - @Test - public void testSelectOnFoo() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnFoo(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -205,7 +196,7 @@ public void testSelectOnFoo() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -225,8 +216,8 @@ public void testSelectOnFoo() .setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with() - .rows(isPageSizeLimited() ? new long[]{2, 2, 2} : new long[]{6}) - .frames(isPageSizeLimited() ? new long[]{1, 1, 1} : new long[]{1}), + .rows(isPageSizeLimited(contextName) ? new long[]{2, 2, 2} : new long[]{6}) + .frames(isPageSizeLimited(contextName) ? new long[]{1, 1, 1} : new long[]{1}), 0, 0, "shuffle" ) .setExpectedResultRows(ImmutableList.of( @@ -239,8 +230,9 @@ public void testSelectOnFoo() )).verifyResults(); } - @Test - public void testSelectOnFoo2() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnFoo2(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("m1", ColumnType.LONG) @@ -265,7 +257,7 @@ public void testSelectOnFoo2() .build()) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -290,15 +282,16 @@ public void testSelectOnFoo2() .setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with() - .rows(isPageSizeLimited() ? new long[]{1L, 2L} : new long[]{3L}) - .frames(isPageSizeLimited() ? new long[]{1L, 1L} : new long[]{1L}), + .rows(isPageSizeLimited(contextName) ? new long[]{1L, 2L} : new long[]{3L}) + .frames(isPageSizeLimited(contextName) ? new long[]{1L, 1L} : new long[]{1L}), 0, 0, "shuffle" ) .verifyResults(); } - @Test - public void testSelectOnFooDuplicateColumnNames() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnFooDuplicateColumnNames(String contextName, Map context) { // Duplicate column names are OK in SELECT statements. @@ -335,7 +328,7 @@ public void testSelectOnFooDuplicateColumnNames() ) .columnMappings(expectedColumnMappings) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -354,8 +347,8 @@ public void testSelectOnFooDuplicateColumnNames() .setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with() - .rows(isPageSizeLimited() ? new long[]{2, 2, 2} : new long[]{6}) - .frames(isPageSizeLimited() ? new long[]{1, 1, 1} : new long[]{1}), + .rows(isPageSizeLimited(contextName) ? new long[]{2, 2, 2} : new long[]{6}) + .frames(isPageSizeLimited(contextName) ? new long[]{1, 1, 1} : new long[]{1}), 0, 0, "shuffle" ) .setExpectedResultRows(ImmutableList.of( @@ -368,8 +361,9 @@ public void testSelectOnFooDuplicateColumnNames() )).verifyResults(); } - @Test - public void testSelectOnFooWhereMatchesNoSegments() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnFooWhereMatchesNoSegments(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -398,7 +392,7 @@ public void testSelectOnFooWhereMatchesNoSegments() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -409,8 +403,9 @@ public void testSelectOnFooWhereMatchesNoSegments() .verifyResults(); } - @Test - public void testSelectOnFooWhereMatchesNoData() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnFooWhereMatchesNoData(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -432,7 +427,7 @@ public void testSelectOnFooWhereMatchesNoData() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -443,8 +438,9 @@ public void testSelectOnFooWhereMatchesNoData() .verifyResults(); } - @Test - public void testSelectAndOrderByOnFooWhereMatchesNoData() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectAndOrderByOnFooWhereMatchesNoData(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -467,7 +463,7 @@ public void testSelectAndOrderByOnFooWhereMatchesNoData() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -478,8 +474,9 @@ public void testSelectAndOrderByOnFooWhereMatchesNoData() .verifyResults(); } - @Test - public void testGroupByOnFoo() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByOnFoo(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -512,7 +509,7 @@ public void testGroupByOnFoo() ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -537,8 +534,9 @@ public void testGroupByOnFoo() .verifyResults(); } - @Test - public void testGroupByOrderByDimension() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByOrderByDimension(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("m1", ColumnType.FLOAT) @@ -579,7 +577,7 @@ public void testGroupByOrderByDimension() ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -613,8 +611,9 @@ public void testGroupByOrderByDimension() .verifyResults(); } - @Test - public void testSelectWithLimit() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectWithLimit(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -636,7 +635,7 @@ public void testSelectWithLimit() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -668,8 +667,9 @@ public void testSelectWithLimit() )).verifyResults(); } - @Test - public void testSelectWithGroupByLimit() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectWithGroupByLimit(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -705,7 +705,7 @@ public void testSelectWithGroupByLimit() ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -715,8 +715,9 @@ public void testSelectWithGroupByLimit() } - @Test - public void testSelectLookup() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectLookup(String contextName, Map context) { final RowSignature rowSignature = RowSignature.builder().add("EXPR$0", ColumnType.LONG).build(); @@ -735,7 +736,7 @@ public void testSelectLookup() .build()) .columnMappings(new ColumnMappings(ImmutableList.of(new ColumnMapping("a0", "EXPR$0")))) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -744,8 +745,9 @@ public void testSelectLookup() .verifyResults(); } - @Test - public void testJoinWithLookup() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testJoinWithLookup(String contextName, Map context) { final RowSignature rowSignature = RowSignature.builder() @@ -791,7 +793,7 @@ public void testJoinWithLookup() ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -809,8 +811,9 @@ public void testJoinWithLookup() .verifyResults(); } - @Test - public void testSubquery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSubquery(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -840,7 +843,7 @@ public void testSubquery() .query(query) .columnMappings(new ColumnMappings(ImmutableList.of(new ColumnMapping("a0", "cnt")))) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -866,19 +869,21 @@ public void testSubquery() .verifyResults(); } - @Test - public void testBroadcastJoin() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testBroadcastJoin(String contextName, Map context) { - testJoin(JoinAlgorithm.BROADCAST); + testJoin(contextName, context, JoinAlgorithm.BROADCAST); } - @Test - public void testSortMergeJoin() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSortMergeJoin(String contextName, Map context) { - testJoin(JoinAlgorithm.SORT_MERGE); + testJoin(contextName, context, JoinAlgorithm.SORT_MERGE); } - private void testJoin(final JoinAlgorithm joinAlgorithm) + private void testJoin(String contextName, Map context, final JoinAlgorithm joinAlgorithm) { final Map queryContext = ImmutableMap.builder() @@ -1011,7 +1016,7 @@ private void testJoin(final JoinAlgorithm joinAlgorithm) ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1025,8 +1030,9 @@ private void testJoin(final JoinAlgorithm joinAlgorithm) .verifyResults(); } - @Test - public void testGroupByOrderByAggregation() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByOrderByAggregation(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("m1", ColumnType.FLOAT) @@ -1069,7 +1075,7 @@ public void testGroupByOrderByAggregation() ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1104,8 +1110,9 @@ public void testGroupByOrderByAggregation() .verifyResults(); } - @Test - public void testGroupByOrderByAggregationWithLimit() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByOrderByAggregationWithLimit(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("m1", ColumnType.FLOAT) @@ -1148,7 +1155,7 @@ public void testGroupByOrderByAggregationWithLimit() ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1180,8 +1187,9 @@ public void testGroupByOrderByAggregationWithLimit() .verifyResults(); } - @Test - public void testGroupByOrderByAggregationWithLimitAndOffset() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByOrderByAggregationWithLimitAndOffset(String contextName, Map context) { RowSignature rowSignature = RowSignature.builder() .add("m1", ColumnType.FLOAT) @@ -1225,7 +1233,7 @@ public void testGroupByOrderByAggregationWithLimitAndOffset() ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1256,10 +1264,11 @@ public void testGroupByOrderByAggregationWithLimitAndOffset() .verifyResults(); } - @Test - public void testExternGroupBy() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testExternGroupBy(String contextName, Map context) throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1325,7 +1334,7 @@ public void testExternGroupBy() throws IOException ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1349,13 +1358,14 @@ public void testExternGroupBy() throws IOException } - @Test - public void testExternSelectWithMultipleWorkers() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testExternSelectWithMultipleWorkers(String contextName, Map context) throws IOException { Map multipleWorkerContext = new HashMap<>(context); multipleWorkerContext.put(MultiStageQueryContext.CTX_MAX_NUM_TASKS, 3); - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1438,7 +1448,7 @@ public void testExternSelectWithMultipleWorkers() throws IOException ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1456,8 +1466,8 @@ public void testExternSelectWithMultipleWorkers() throws IOException .setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with() - .rows(isPageSizeLimited() ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{5L}) - .frames(isPageSizeLimited() ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{1L}), + .rows(isPageSizeLimited(contextName) ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{5L}) + .frames(isPageSizeLimited(contextName) ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{1L}), 0, 0, "shuffle" ) .setExpectedCountersForStageWorkerChannel( @@ -1473,12 +1483,12 @@ public void testExternSelectWithMultipleWorkers() throws IOException .setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with() - .rows(isPageSizeLimited() ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{5L}) - .frames(isPageSizeLimited() ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{1L}), + .rows(isPageSizeLimited(contextName) ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{5L}) + .frames(isPageSizeLimited(contextName) ? new long[]{1L, 1L, 1L, 1L, 1L} : new long[]{1L}), 0, 1, "shuffle" ); // adding result stage counter checks - if (isPageSizeLimited()) { + if (isPageSizeLimited(contextName)) { selectTester.setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with().rows(2, 0, 2, 0, 2), @@ -1500,8 +1510,9 @@ public void testExternSelectWithMultipleWorkers() throws IOException selectTester.verifyResults(); } - @Test - public void testIncorrectSelectQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testIncorrectSelectQuery(String contextName, Map context) { testSelectQuery() .setSql("select a from ") @@ -1512,8 +1523,9 @@ public void testIncorrectSelectQuery() .verifyPlanningErrors(); } - @Test - public void testSelectOnInformationSchemaSource() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnInformationSchemaSource(String contextName, Map context) { testSelectQuery() .setSql("SELECT * FROM INFORMATION_SCHEMA.SCHEMATA") @@ -1524,8 +1536,9 @@ public void testSelectOnInformationSchemaSource() .verifyPlanningErrors(); } - @Test - public void testSelectOnSysSource() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnSysSource(String contextName, Map context) { testSelectQuery() .setSql("SELECT * FROM sys.segments") @@ -1536,8 +1549,9 @@ public void testSelectOnSysSource() .verifyPlanningErrors(); } - @Test - public void testSelectOnSysSourceWithJoin() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnSysSourceWithJoin(String contextName, Map context) { testSelectQuery() .setSql("select s.segment_id, s.num_rows, f.dim1 from sys.segments as s, foo as f") @@ -1548,8 +1562,9 @@ public void testSelectOnSysSourceWithJoin() .verifyPlanningErrors(); } - @Test - public void testSelectOnSysSourceContainingWith() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnSysSourceContainingWith(String contextName, Map context) { testSelectQuery() .setSql("with segment_source as (SELECT * FROM sys.segments) " @@ -1561,8 +1576,9 @@ public void testSelectOnSysSourceContainingWith() .verifyPlanningErrors(); } - @Test - public void testSelectOnUserDefinedSourceContainingWith() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectOnUserDefinedSourceContainingWith(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("m1", ColumnType.LONG) @@ -1590,7 +1606,7 @@ public void testSelectOnUserDefinedSourceContainingWith() ) .columnMappings(ColumnMappings.identity(resultSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -1615,15 +1631,16 @@ public void testSelectOnUserDefinedSourceContainingWith() .setExpectedCountersForStageWorkerChannel( CounterSnapshotMatcher .with() - .rows(isPageSizeLimited() ? new long[]{1, 2} : new long[]{3}) - .frames(isPageSizeLimited() ? new long[]{1, 1} : new long[]{1}), + .rows(isPageSizeLimited(contextName) ? new long[]{1, 2} : new long[]{3}) + .frames(isPageSizeLimited(contextName) ? new long[]{1, 1} : new long[]{1}), 0, 0, "shuffle" ) .verifyResults(); } - @Test - public void testScanWithMultiValueSelectQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testScanWithMultiValueSelectQuery(String contextName, Map context) { RowSignature expectedScanSignature = RowSignature.builder() .add("dim3", ColumnType.STRING) @@ -1660,7 +1677,7 @@ public void testScanWithMultiValueSelectQuery() ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -1676,8 +1693,9 @@ public void testScanWithMultiValueSelectQuery() )).verifyResults(); } - @Test - public void testHavingOnApproximateCountDistinct() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testHavingOnApproximateCountDistinct(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("dim2", ColumnType.STRING) @@ -1730,7 +1748,7 @@ public void testHavingOnApproximateCountDistinct() new ColumnMapping("a0", "col") ))) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -1745,8 +1763,9 @@ public void testHavingOnApproximateCountDistinct() .verifyResults(); } - @Test - public void testGroupByWithMultiValue() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByWithMultiValue(String contextName, Map context) { Map localContext = enableMultiValueUnnesting(context, true); RowSignature rowSignature = RowSignature.builder() @@ -1784,7 +1803,7 @@ public void testGroupByWithMultiValue() ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -1794,8 +1813,9 @@ public void testGroupByWithMultiValue() } - @Test - public void testGroupByWithMultiValueWithoutGroupByEnable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByWithMultiValueWithoutGroupByEnable(String contextName, Map context) { Map localContext = enableMultiValueUnnesting(context, false); @@ -1811,8 +1831,9 @@ public void testGroupByWithMultiValueWithoutGroupByEnable() .verifyExecutionError(); } - @Test - public void testGroupByWithMultiValueMvToArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByWithMultiValueMvToArray(String contextName, Map context) { Map localContext = enableMultiValueUnnesting(context, true); @@ -1857,7 +1878,7 @@ public void testGroupByWithMultiValueMvToArray() ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -1868,8 +1889,9 @@ public void testGroupByWithMultiValueMvToArray() .verifyResults(); } - @Test - public void testGroupByArrayWithMultiValueMvToArray() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByArrayWithMultiValueMvToArray(String contextName, Map context) { Map localContext = enableMultiValueUnnesting(context, true); @@ -1927,7 +1949,7 @@ public void testGroupByArrayWithMultiValueMvToArray() ) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -1936,10 +1958,11 @@ public void testGroupByArrayWithMultiValueMvToArray() .verifyResults(); } - @Test - public void testTimeColumnAggregationFromExtern() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testTimeColumnAggregationFromExtern(String contextName, Map context) throws IOException { - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); RowSignature rowSignature = RowSignature.builder() @@ -1976,8 +1999,9 @@ public void testTimeColumnAggregationFromExtern() throws IOException .verifyPlanningErrors(); } - @Test - public void testGroupByWithMultiValueMvToArrayWithoutGroupByEnable() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByWithMultiValueMvToArrayWithoutGroupByEnable(String contextName, Map context) { Map localContext = enableMultiValueUnnesting(context, false); @@ -1994,8 +2018,9 @@ public void testGroupByWithMultiValueMvToArrayWithoutGroupByEnable() .verifyExecutionError(); } - @Test - public void testGroupByWithComplexColumnThrowsUnsupportedException() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByWithComplexColumnThrowsUnsupportedException(String contextName, Map context) { testSelectQuery() .setSql("select unique_dim1 from foo2 group by unique_dim1") @@ -2008,8 +2033,9 @@ public void testGroupByWithComplexColumnThrowsUnsupportedException() .verifyExecutionError(); } - @Test - public void testGroupByMultiValueMeasureQuery() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByMultiValueMeasureQuery(String contextName, Map context) { final RowSignature rowSignature = RowSignature.builder() .add("__time", ColumnType.LONG) @@ -2046,7 +2072,7 @@ public void testGroupByMultiValueMeasureQuery() ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -2064,8 +2090,9 @@ public void testGroupByMultiValueMeasureQuery() .verifyResults(); } - @Test - public void testGroupByOnFooWithDurableStoragePathAssertions() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testGroupByOnFooWithDurableStoragePathAssertions(String contextName, Map context) throws IOException { RowSignature rowSignature = RowSignature.builder() .add("cnt", ColumnType.LONG) @@ -2100,7 +2127,7 @@ public void testGroupByOnFooWithDurableStoragePathAssertions() throws IOExceptio )) ) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -2118,14 +2145,15 @@ public void testGroupByOnFooWithDurableStoragePathAssertions() throws IOExceptio } } - @Test - public void testSelectRowsGetUntruncatedByDefault() throws IOException + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectRowsGetUntruncatedByDefault(String contextName, Map context) throws IOException { RowSignature dummyRowSignature = RowSignature.builder().add("timestamp", ColumnType.LONG).build(); final int numFiles = 200; - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadFileNameAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); String externalFiles = String.join(", ", Collections.nCopies(numFiles, toReadFileNameAsJson)); @@ -2179,7 +2207,7 @@ public void testSelectRowsGetUntruncatedByDefault() throws IOException ) )) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build()) @@ -2188,10 +2216,13 @@ public void testSelectRowsGetUntruncatedByDefault() throws IOException .verifyResults(); } - @Test - public void testJoinUsesDifferentAlgorithm() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testJoinUsesDifferentAlgorithm(String contextName, Map context) { + + // This test asserts that the join algorithnm used is a different one from that supplied. In sqlCompatible() mode // the query gets planned differently, therefore we do use the sortMerge processor. Instead of having separate // handling, a similar test has been described in CalciteJoinQueryMSQTest, therefore we don't want to repeat that @@ -2274,7 +2305,7 @@ public void testJoinUsesDifferentAlgorithm() new ColumnMapping("a0", "cnt") ) )) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .tuningConfig(MSQTuningConfig.defaultConfig()) @@ -2293,8 +2324,9 @@ public void testJoinUsesDifferentAlgorithm() .verifyResults(); } - @Test - public void testSelectUnnestOnInlineFoo() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectUnnestOnInlineFoo(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("EXPR$0", ColumnType.LONG) @@ -2333,7 +2365,7 @@ public void testSelectUnnestOnInlineFoo() .build()) .columnMappings(expectedColumnMappings) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -2349,8 +2381,9 @@ public void testSelectUnnestOnInlineFoo() } - @Test - public void testSelectUnnestOnFoo() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectUnnestOnFoo(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("j0.unnest", ColumnType.STRING) @@ -2387,7 +2420,7 @@ public void testSelectUnnestOnFoo() .build()) .columnMappings(expectedColumnMappings) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -2417,8 +2450,9 @@ public void testSelectUnnestOnFoo() .verifyResults(); } - @Test - public void testSelectUnnestOnQueryFoo() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testSelectUnnestOnQueryFoo(String contextName, Map context) { RowSignature resultSignature = RowSignature.builder() .add("j0.unnest", ColumnType.STRING) @@ -2475,7 +2509,7 @@ public void testSelectUnnestOnQueryFoo() .build()) .columnMappings(expectedColumnMappings) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -2494,8 +2528,9 @@ public void testSelectUnnestOnQueryFoo() .verifyResults(); } - @Test - public void testUnionAllUsingUnionDataSource() + @MethodSource("data") + @ParameterizedTest(name = "{index}:with context {0}") + public void testUnionAllUsingUnionDataSource(String contextName, Map context) { final RowSignature rowSignature = RowSignature.builder() @@ -2542,7 +2577,7 @@ public void testUnionAllUsingUnionDataSource() .build()) .columnMappings(ColumnMappings.identity(rowSignature)) .tuningConfig(MSQTuningConfig.defaultConfig()) - .destination(isDurableStorageDestination() + .destination(isDurableStorageDestination(contextName, context) ? DurableStorageMSQDestination.INSTANCE : TaskReportMSQDestination.INSTANCE) .build() @@ -2595,12 +2630,12 @@ private static Map enableMultiValueUnnesting(Map return localContext; } - public boolean isDurableStorageDestination() + private boolean isDurableStorageDestination(String contextName, Map context) { return QUERY_RESULTS_WITH_DURABLE_STORAGE.equals(contextName) || QUERY_RESULTS_WITH_DEFAULT_CONTEXT.equals(context); } - public boolean isPageSizeLimited() + public boolean isPageSizeLimited(String contextName) { return QUERY_RESULTS_WITH_DURABLE_STORAGE.equals(contextName); } diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/InsertLockPreemptedFaultTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/InsertLockPreemptedFaultTest.java index 807a86b77751..5da165dbc460 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/InsertLockPreemptedFaultTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/InsertLockPreemptedFaultTest.java @@ -24,7 +24,7 @@ import org.apache.druid.msq.test.MSQTestTaskActionClient; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.segment.column.RowSignature; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class InsertLockPreemptedFaultTest extends MSQTestBase { diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/MSQWarningsTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/MSQWarningsTest.java index f21d92ee90fd..6a262cd38c1b 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/MSQWarningsTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/error/MSQWarningsTest.java @@ -28,7 +28,6 @@ import org.apache.druid.msq.indexing.MSQSpec; import org.apache.druid.msq.indexing.MSQTuningConfig; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.msq.util.MultiStageQueryContext; import org.apache.druid.query.Query; import org.apache.druid.query.QueryContexts; @@ -45,8 +44,8 @@ import org.apache.druid.sql.calcite.planner.ColumnMappings; import org.apache.druid.sql.calcite.util.CalciteTests; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; @@ -64,10 +63,10 @@ public class MSQWarningsTest extends MSQTestBase private Query defaultQuery; private ColumnMappings defaultColumnMappings; - @Before + @BeforeEach public void setUp3() throws IOException { - File tempFile = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/unparseable.gz"); + File tempFile = getResourceAsTemporaryFile("/unparseable.gz"); // Rename the file and the file's extension from .tmp to .gz to prevent issues with 'parsing' the file toRead = new File(tempFile.getParentFile(), "unparseable.gz"); diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactoryTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactoryTest.java new file mode 100644 index 000000000000..90f191647702 --- /dev/null +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/results/ExportResultsFrameProcessorFactoryTest.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.apache.druid.msq.querykit.results; + +import com.fasterxml.jackson.databind.InjectableValues; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.druid.jackson.DefaultObjectMapper; +import org.apache.druid.storage.StorageConfig; +import org.apache.druid.storage.StorageConnectorModule; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +public class ExportResultsFrameProcessorFactoryTest +{ + @Test + public void testSerde() throws IOException + { + String exportFactoryString = "{\"type\":\"exportResults\",\"queryId\":\"query-9128ieio9wq\",\"exportStorageProvider\":{\"type\":\"local\",\"exportPath\":\"/path\"},\"exportFormat\":\"csv\",\"resultTypeReference\":{\"type\":\"java.lang.Object\"}}"; + + ObjectMapper objectMapper = new DefaultObjectMapper(); + objectMapper.registerModules(new StorageConnectorModule().getJacksonModules()); + objectMapper.setInjectableValues( + new InjectableValues.Std() + .addValue(StorageConfig.class, new StorageConfig("/")) + ); + + ExportResultsFrameProcessorFactory exportResultsFrameProcessorFactory = objectMapper.readValue( + exportFactoryString, + ExportResultsFrameProcessorFactory.class + ); + Assert.assertNull(exportResultsFrameProcessorFactory.getColumnMappings()); + } +} diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java index 3ffa42d64ccc..6d81da64aab0 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java @@ -39,7 +39,6 @@ import org.apache.druid.msq.sql.entity.ResultSetInformation; import org.apache.druid.msq.sql.entity.SqlStatementResult; import org.apache.druid.msq.test.MSQTestBase; -import org.apache.druid.msq.test.MSQTestFileUtils; import org.apache.druid.msq.test.MSQTestOverlordServiceClient; import org.apache.druid.msq.util.MultiStageQueryContext; import org.apache.druid.query.ExecutionMode; @@ -50,11 +49,12 @@ import org.apache.druid.sql.http.SqlQuery; import org.apache.druid.storage.NilStorageConnector; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -69,7 +69,7 @@ public class SqlMSQStatementResourcePostTest extends MSQTestBase private SqlStatementResource resource; - @Before + @BeforeEach public void init() { resource = new SqlStatementResource( @@ -445,7 +445,7 @@ public void testMultipleWorkersWithPageSizeLimiting() throws IOException context.put(MultiStageQueryContext.CTX_ROWS_PER_PAGE, 2); context.put(MultiStageQueryContext.CTX_MAX_NUM_TASKS, 3); - final File toRead = MSQTestFileUtils.getResourceAsTemporaryFile(temporaryFolder, this, "/wikipedia-sampled.json"); + final File toRead = getResourceAsTemporaryFile("/wikipedia-sampled.json"); final String toReadAsJson = queryFramework().queryJsonMapper().writeValueAsString(toRead.getAbsolutePath()); diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java index e3995a4a96c8..3d41b46825a4 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java @@ -92,8 +92,8 @@ import org.jboss.netty.handler.codec.http.HttpVersion; import org.joda.time.DateTime; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; @@ -699,7 +699,7 @@ private static AuthenticationResult makeAuthResultForUser(String user) ); } - @Before + @BeforeEach public void init() throws Exception { overlordClient = Mockito.mock(OverlordClient.class); @@ -708,7 +708,7 @@ public void init() throws Exception sqlStatementFactory, objectMapper, overlordClient, - new LocalFileStorageConnector(tmpFolder.newFolder("local")), + new LocalFileStorageConnector(newTempFolder("local")), authorizerMapper ); } diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/statistics/ClusterByStatisticsCollectorImplTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/statistics/ClusterByStatisticsCollectorImplTest.java index 4d9421c221f2..77502dddd38c 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/statistics/ClusterByStatisticsCollectorImplTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/statistics/ClusterByStatisticsCollectorImplTest.java @@ -138,7 +138,7 @@ public void test_clusterByX_unique() ); } - verifySnapshotSerialization(testName, collector, aggregate); + verifySnapshotSerialization(testName, collector); } ); } @@ -187,7 +187,7 @@ public void test_clusterByX_everyKeyAppearsTwice() ); } - verifySnapshotSerialization(testName, collector, aggregate); + verifySnapshotSerialization(testName, collector); } ); } @@ -245,7 +245,7 @@ public void test_clusterByX_everyKeyAppearsTwice_withAggregation() ); } - verifySnapshotSerialization(testName, collector, aggregate); + verifySnapshotSerialization(testName, collector); } ); } @@ -309,7 +309,7 @@ public void test_clusterByXYbucketByX_threeX_uniqueY() } } - verifySnapshotSerialization(testName, collector, aggregate); + verifySnapshotSerialization(testName, collector); } ); } @@ -380,7 +380,7 @@ public void test_clusterByXYbucketByX_maxX_uniqueY() } } - verifySnapshotSerialization(testName, collector, aggregate); + verifySnapshotSerialization(testName, collector); } ); } @@ -446,7 +446,7 @@ public void test_clusterByXYbucketByX_maxX_lowCardinalityY_withAggregation() } } - verifySnapshotSerialization(testName, collector, aggregate); + verifySnapshotSerialization(testName, collector); } ); } @@ -945,21 +945,11 @@ private static long trackedRows(final ClusterByStatisticsCollectorImpl collector private static void verifySnapshotSerialization( final String testName, - final ClusterByStatisticsCollector collector, - final boolean aggregate + final ClusterByStatisticsCollector collector ) { try { final ObjectMapper jsonMapper = TestHelper.makeJsonMapper(); - jsonMapper.registerModule( - new KeyCollectorSnapshotDeserializerModule( - KeyCollectors.makeStandardFactory( - collector.getClusterBy(), - aggregate - ) - ) - ); - final ClusterByStatisticsSnapshot snapshot = collector.snapshot(); final ClusterByStatisticsSnapshot snapshot2 = jsonMapper.readValue( jsonMapper.writeValueAsString(snapshot), diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteArraysQueryMSQTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteArraysQueryMSQTest.java index 7e7d20ebe655..4e036f59518e 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteArraysQueryMSQTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteArraysQueryMSQTest.java @@ -42,7 +42,7 @@ public void configureGuice(DruidInjectorBuilder builder) { super.configureGuice(builder); builder.addModules( - CalciteMSQTestsHelper.fetchModules(temporaryFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0]) + CalciteMSQTestsHelper.fetchModules(this::newTempFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0]) ); } diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java index 3146341faf94..fc7cfe5d9bea 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteMSQTestsHelper.java @@ -76,22 +76,23 @@ import org.apache.druid.server.SegmentManager; import org.apache.druid.server.coordination.DataSegmentAnnouncer; import org.apache.druid.server.coordination.NoopDataSegmentAnnouncer; -import org.apache.druid.sql.calcite.CalciteArraysQueryTest; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.SegmentId; import org.easymock.EasyMock; import org.joda.time.Interval; -import org.junit.rules.TemporaryFolder; import org.mockito.Mockito; import javax.annotation.Nullable; -import java.io.IOException; + +import java.io.File; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; +import static org.apache.druid.sql.calcite.util.CalciteTests.ARRAYS_DATASOURCE; import static org.apache.druid.sql.calcite.util.CalciteTests.DATASOURCE1; import static org.apache.druid.sql.calcite.util.CalciteTests.DATASOURCE2; import static org.apache.druid.sql.calcite.util.CalciteTests.DATASOURCE3; @@ -113,10 +114,12 @@ public class CalciteMSQTestsHelper { public static List fetchModules( - TemporaryFolder temporaryFolder, + Function tempFolderProducer, TestGroupByBuffers groupByBuffers ) { + File cacheManagerDir = tempFolderProducer.apply("test"); + File storageDir = tempFolderProducer.apply("localsegments"); Module customBindings = binder -> { @@ -152,29 +155,18 @@ public String getFormatString() ); ObjectMapper testMapper = MSQTestBase.setupObjectMapper(dummyInjector); IndexIO indexIO = new IndexIO(testMapper, ColumnConfig.DEFAULT); - SegmentCacheManager segmentCacheManager = null; - try { - segmentCacheManager = new SegmentCacheManagerFactory(testMapper).manufacturate(temporaryFolder.newFolder( - "test")); - } - catch (IOException e) { - e.printStackTrace(); - } + SegmentCacheManager segmentCacheManager = new SegmentCacheManagerFactory(testMapper) + .manufacturate(cacheManagerDir); LocalDataSegmentPusherConfig config = new LocalDataSegmentPusherConfig(); MSQTestSegmentManager segmentManager = new MSQTestSegmentManager(segmentCacheManager, indexIO); - try { - config.storageDirectory = temporaryFolder.newFolder("localsegments"); - } - catch (IOException e) { - throw new ISE(e, "Unable to create folder"); - } + config.storageDirectory = storageDir; binder.bind(DataSegmentPusher.class).toProvider(() -> new MSQTestDelegateDataSegmentPusher( new LocalDataSegmentPusher(config), segmentManager )); binder.bind(DataSegmentAnnouncer.class).toInstance(new NoopDataSegmentAnnouncer()); binder.bind(DataSegmentProvider.class) - .toInstance((segmentId, channelCounters, isReindex) -> getSupplierForSegment(segmentId)); + .toInstance((segmentId, channelCounters, isReindex) -> getSupplierForSegment(tempFolderProducer, segmentId)); binder.bind(DataServerQueryHandlerFactory.class).toInstance(getTestDataServerQueryHandlerFactory()); GroupByQueryConfig groupByQueryConfig = new GroupByQueryConfig(); @@ -206,116 +198,104 @@ private static DataServerQueryHandlerFactory getTestDataServerQueryHandlerFactor return mockFactory; } - private static Supplier> getSupplierForSegment(SegmentId segmentId) + private static Supplier> getSupplierForSegment(Function tempFolderProducer, SegmentId segmentId) { - final TemporaryFolder temporaryFolder = new TemporaryFolder(); - try { - temporaryFolder.create(); - } - catch (IOException e) { - e.printStackTrace(); - } final QueryableIndex index; - try { - switch (segmentId.getDataSource()) { - case DATASOURCE1: - IncrementalIndexSchema foo1Schema = new IncrementalIndexSchema.Builder() - .withMetrics( - new CountAggregatorFactory("cnt"), - new FloatSumAggregatorFactory("m1", "m1"), - new DoubleSumAggregatorFactory("m2", "m2"), - new HyperUniquesAggregatorFactory("unique_dim1", "dim1") - ) - .withRollup(false) - .build(); - index = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(foo1Schema) - .rows(ROWS1) - .buildMMappedIndex(); - break; - case DATASOURCE2: - final IncrementalIndexSchema indexSchemaDifferentDim3M1Types = new IncrementalIndexSchema.Builder() - .withDimensionsSpec( - new DimensionsSpec( - ImmutableList.of( - new StringDimensionSchema("dim1"), - new StringDimensionSchema("dim2"), - new LongDimensionSchema("dim3") - ) - ) - ) - .withMetrics( - new CountAggregatorFactory("cnt"), - new LongSumAggregatorFactory("m1", "m1"), - new DoubleSumAggregatorFactory("m2", "m2"), - new HyperUniquesAggregatorFactory("unique_dim1", "dim1") - ) - .withRollup(false) - .build(); - index = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(indexSchemaDifferentDim3M1Types) - .rows(ROWS2) - .buildMMappedIndex(); - break; - case DATASOURCE3: - case CalciteTests.BROADCAST_DATASOURCE: - index = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(INDEX_SCHEMA_NUMERIC_DIMS) - .rows(ROWS1_WITH_NUMERIC_DIMS) - .buildMMappedIndex(); - break; - case DATASOURCE5: - index = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(INDEX_SCHEMA_LOTS_O_COLUMNS) - .rows(ROWS_LOTS_OF_COLUMNS) - .buildMMappedIndex(); - break; - case CalciteArraysQueryTest.DATA_SOURCE_ARRAYS: - index = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema( - new IncrementalIndexSchema.Builder() - .withTimestampSpec(NestedDataTestUtils.AUTO_SCHEMA.getTimestampSpec()) - .withDimensionsSpec(NestedDataTestUtils.AUTO_SCHEMA.getDimensionsSpec()) - .withMetrics( - new CountAggregatorFactory("cnt") - ) - .withRollup(false) - .build() - ) - .inputSource( - ResourceInputSource.of( - NestedDataTestUtils.class.getClassLoader(), - NestedDataTestUtils.ARRAY_TYPES_DATA_FILE - ) - ) - .inputFormat(TestDataBuilder.DEFAULT_JSON_INPUT_FORMAT) - .inputTmpDir(temporaryFolder.newFolder()) - .buildMMappedIndex(); - break; - case CalciteTests.WIKIPEDIA_FIRST_LAST: - index = TestDataBuilder.makeWikipediaIndexWithAggregation(temporaryFolder.newFolder()); - break; - default: - throw new ISE("Cannot query segment %s in test runner", segmentId); + switch (segmentId.getDataSource()) { + case DATASOURCE1: + IncrementalIndexSchema foo1Schema = new IncrementalIndexSchema.Builder() + .withMetrics( + new CountAggregatorFactory("cnt"), + new FloatSumAggregatorFactory("m1", "m1"), + new DoubleSumAggregatorFactory("m2", "m2"), + new HyperUniquesAggregatorFactory("unique_dim1", "dim1") + ) + .withRollup(false) + .build(); + index = IndexBuilder + .create() + .tmpDir(tempFolderProducer.apply("tmpDir")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema(foo1Schema) + .rows(ROWS1) + .buildMMappedIndex(); + break; + case DATASOURCE2: + final IncrementalIndexSchema indexSchemaDifferentDim3M1Types = new IncrementalIndexSchema.Builder() + .withDimensionsSpec( + new DimensionsSpec( + ImmutableList.of( + new StringDimensionSchema("dim1"), + new StringDimensionSchema("dim2"), + new LongDimensionSchema("dim3") + ) + ) + ) + .withMetrics( + new CountAggregatorFactory("cnt"), + new LongSumAggregatorFactory("m1", "m1"), + new DoubleSumAggregatorFactory("m2", "m2"), + new HyperUniquesAggregatorFactory("unique_dim1", "dim1") + ) + .withRollup(false) + .build(); + index = IndexBuilder + .create() + .tmpDir(tempFolderProducer.apply("tmpDir")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema(indexSchemaDifferentDim3M1Types) + .rows(ROWS2) + .buildMMappedIndex(); + break; + case DATASOURCE3: + case CalciteTests.BROADCAST_DATASOURCE: + index = IndexBuilder + .create() + .tmpDir(tempFolderProducer.apply("tmpDir")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema(INDEX_SCHEMA_NUMERIC_DIMS) + .rows(ROWS1_WITH_NUMERIC_DIMS) + .buildMMappedIndex(); + break; + case DATASOURCE5: + index = IndexBuilder + .create() + .tmpDir(tempFolderProducer.apply("tmpDir")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema(INDEX_SCHEMA_LOTS_O_COLUMNS) + .rows(ROWS_LOTS_OF_COLUMNS) + .buildMMappedIndex(); + break; + case ARRAYS_DATASOURCE: + index = IndexBuilder.create() + .tmpDir(tempFolderProducer.apply("tmpDir")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema( + new IncrementalIndexSchema.Builder() + .withTimestampSpec(NestedDataTestUtils.AUTO_SCHEMA.getTimestampSpec()) + .withDimensionsSpec(NestedDataTestUtils.AUTO_SCHEMA.getDimensionsSpec()) + .withMetrics( + new CountAggregatorFactory("cnt") + ) + .withRollup(false) + .build() + ) + .inputSource( + ResourceInputSource.of( + NestedDataTestUtils.class.getClassLoader(), + NestedDataTestUtils.ARRAY_TYPES_DATA_FILE + ) + ) + .inputFormat(TestDataBuilder.DEFAULT_JSON_INPUT_FORMAT) + .inputTmpDir(tempFolderProducer.apply("tmpDir")) + .buildMMappedIndex(); + break; + case CalciteTests.WIKIPEDIA_FIRST_LAST: + index = TestDataBuilder.makeWikipediaIndexWithAggregation(tempFolderProducer.apply("tmpDir")); + break; + default: + throw new ISE("Cannot query segment %s in test runner", segmentId); - } - } - catch (IOException e) { - throw new ISE(e, "Unable to load index for segment %s", segmentId); } Segment segment = new Segment() { diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectJoinQueryMSQTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectJoinQueryMSQTest.java index 147836536c43..fa72ca1d7530 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectJoinQueryMSQTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectJoinQueryMSQTest.java @@ -36,14 +36,11 @@ import org.apache.druid.sql.calcite.run.EngineFeature; import org.apache.druid.sql.calcite.run.QueryMaker; import org.apache.druid.sql.calcite.run.SqlEngine; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; /** * Runs {@link CalciteJoinQueryTest} but with MSQ engine. */ -@RunWith(Enclosed.class) -public abstract class CalciteSelectJoinQueryMSQTest +public class CalciteSelectJoinQueryMSQTest { /** * Run all tests with {@link JoinAlgorithm#BROADCAST}. @@ -87,19 +84,23 @@ public abstract static class Base extends CalciteJoinQueryTest { private final JoinAlgorithm joinAlgorithm; - protected Base(final JoinAlgorithm joinAlgorithm) { - super(joinAlgorithm == JoinAlgorithm.SORT_MERGE); this.joinAlgorithm = joinAlgorithm; } + @Override + public boolean isSortBasedJoin() + { + return joinAlgorithm == JoinAlgorithm.SORT_MERGE; + } + @Override public void configureGuice(DruidInjectorBuilder builder) { super.configureGuice(builder); builder.addModules( - CalciteMSQTestsHelper.fetchModules(temporaryFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0]) + CalciteMSQTestsHelper.fetchModules(this::newTempFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0]) ); } @@ -122,10 +123,9 @@ public SqlEngine createEngine( return new MSQTaskSqlEngine(indexingServiceClient, queryJsonMapper) { @Override - public boolean featureAvailable(EngineFeature feature, PlannerContext plannerContext) + public boolean featureAvailable(EngineFeature feature) { - plannerContext.queryContextMap().put(PlannerContext.CTX_SQL_JOIN_ALGORITHM, joinAlgorithm.toString()); - return super.featureAvailable(feature, plannerContext); + return super.featureAvailable(feature); } @Override diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java index ac954734e1ac..b2d9ab1e9eb3 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteSelectQueryMSQTest.java @@ -35,8 +35,11 @@ import org.apache.druid.sql.calcite.QueryTestBuilder; import org.apache.druid.sql.calcite.run.SqlEngine; import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; /** * Runs {@link CalciteQueryTest} but with MSQ engine @@ -47,7 +50,7 @@ public class CalciteSelectQueryMSQTest extends CalciteQueryTest public void configureGuice(DruidInjectorBuilder builder) { super.configureGuice(builder); - builder.addModules(CalciteMSQTestsHelper.fetchModules(temporaryFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0])); + builder.addModules(CalciteMSQTestsHelper.fetchModules(this::newTempFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0])); } @@ -86,77 +89,88 @@ protected QueryTestBuilder testBuilder() .verifyNativeQueries(new VerifyMSQSupportedNativeQueriesPredicate()); } - @Ignore + @Disabled @Override + @Test public void testCannotInsertWithNativeEngine() { } - @Ignore + @Disabled @Override + @Test public void testCannotReplaceWithNativeEngine() { } - @Ignore + @Disabled @Override + @Test public void testRequireTimeConditionSimpleQueryNegative() { } - @Ignore + @Disabled @Override + @Test public void testRequireTimeConditionSubQueryNegative() { } - @Ignore + @Disabled @Override + @Test public void testRequireTimeConditionSemiJoinNegative() { } - @Ignore + @Disabled @Override + @Test public void testExactCountDistinctWithFilter() { } - @Ignore + @Disabled @Override + @Test public void testUnplannableScanOrderByNonTime() { } - @Ignore + @Disabled @Override + @Test public void testUnplannableJoinQueriesInNonSQLCompatibleMode() { } - @Ignore + @Disabled @Override + @Test public void testQueryWithMoreThanMaxNumericInFilter() { } - @Ignore + @Disabled @Override + @Test public void testUnSupportedNullsFirst() { } - @Ignore + @Disabled @Override + @Test public void testUnSupportedNullsLast() { } @@ -180,7 +194,8 @@ public void testArrayAggQueryOnComplexDatatypes() } } - @Test(timeout = 40000) + @Test + @Timeout(value = 40000, unit = TimeUnit.MILLISECONDS) public void testJoinMultipleTablesWithWhereCondition() { testBuilder() @@ -217,6 +232,7 @@ public void testJoinMultipleTablesWithWhereCondition() } @Override + @Test public void testFilterParseLongNullable() { // this isn't really correct in default value mode, the result should be ImmutableList.of(new Object[]{0L}) diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java index 9f1d7cc242d2..babd3251a467 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java @@ -43,8 +43,8 @@ import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.run.SqlEngine; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; /** * Runs {@link CalciteUnionQueryTest} but with MSQ engine @@ -56,7 +56,7 @@ public void configureGuice(DruidInjectorBuilder builder) { super.configureGuice(builder); builder.addModules( - CalciteMSQTestsHelper.fetchModules(temporaryFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0]) + CalciteMSQTestsHelper.fetchModules(this::newTempFolder, TestGroupByBuffers.createDefault()).toArray(new Module[0]) ); } @@ -113,7 +113,7 @@ public void testUnionIsUnplannable() } - @Ignore("Ignored till MSQ can plan UNION ALL with any operand") + @Disabled("Ignored till MSQ can plan UNION ALL with any operand") @Test public void testUnionOnSubqueries() { diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java index 4fbaba52e83e..40dc06a76526 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestBase.java @@ -192,13 +192,10 @@ import org.apache.druid.timeline.partition.TombstoneShardSpec; import org.easymock.EasyMock; import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; import org.joda.time.Interval; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.mockito.Mockito; import javax.annotation.Nonnull; @@ -218,6 +215,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -226,6 +224,7 @@ import static org.apache.druid.sql.calcite.util.CalciteTests.DATASOURCE2; import static org.apache.druid.sql.calcite.util.TestDataBuilder.ROWS1; import static org.apache.druid.sql.calcite.util.TestDataBuilder.ROWS2; +import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; @@ -321,8 +320,6 @@ public class MSQTestBase extends BaseCalciteQueryTest private MSQTestSegmentManager segmentManager; private SegmentCacheManager segmentCacheManager; - @Rule - public TemporaryFolder tmpFolder = new TemporaryFolder(); private TestGroupByBuffers groupByBuffers; protected final WorkerMemoryParameters workerMemoryParameters = Mockito.spy( @@ -368,7 +365,7 @@ public List getJacksonModules() }); } - @After + @AfterEach public void tearDown2() { groupByBuffers.close(); @@ -391,7 +388,7 @@ public void tearDown2() // is created in the main injector, but it depends on the SegmentCacheManagerFactory // which depends on the object mapper that the injector will provide, once it // is built, but has not yet been build while we build the SQL engine. - @Before + @BeforeEach public void setUp2() throws Exception { groupByBuffers = TestGroupByBuffers.createDefault(); @@ -408,12 +405,7 @@ public void setUp2() throws Exception ObjectMapper secondMapper = setupObjectMapper(secondInjector); indexIO = new IndexIO(secondMapper, ColumnConfig.DEFAULT); - try { - segmentCacheManager = new SegmentCacheManagerFactory(secondMapper).manufacturate(tmpFolder.newFolder("test")); - } - catch (IOException exception) { - throw new ISE(exception, "Unable to create segmentCacheManager"); - } + segmentCacheManager = new SegmentCacheManagerFactory(secondMapper).manufacturate(newTempFolder("cacheManager")); MSQSqlModule sqlModule = new MSQSqlModule(); @@ -445,18 +437,13 @@ public String getFormatString() binder.bind(QueryProcessingPool.class) .toInstance(new ForwardingQueryProcessingPool(Execs.singleThreaded("Test-runner-processing-pool"))); binder.bind(DataSegmentProvider.class) - .toInstance((segmentId, channelCounters, isReindex) -> getSupplierForSegment(segmentId)); + .toInstance((segmentId, channelCounters, isReindex) -> getSupplierForSegment(this::newTempFolder, segmentId)); binder.bind(DataServerQueryHandlerFactory.class).toInstance(getTestDataServerQueryHandlerFactory()); binder.bind(IndexIO.class).toInstance(indexIO); binder.bind(SpecificSegmentsQuerySegmentWalker.class).toInstance(qf.walker()); LocalDataSegmentPusherConfig config = new LocalDataSegmentPusherConfig(); - try { - config.storageDirectory = tmpFolder.newFolder("localsegments"); - } - catch (IOException e) { - throw new ISE(e, "Unable to create folder"); - } + config.storageDirectory = newTempFolder("storageDir"); binder.bind(DataSegmentPusher.class).toInstance(new MSQTestDelegateDataSegmentPusher( new LocalDataSegmentPusher(config), segmentManager @@ -474,7 +461,7 @@ public String getFormatString() StorageConnectorProvider.class, MultiStageQuery.class ); - localFileStorageDir = tmpFolder.newFolder("fault"); + localFileStorageDir = newTempFolder("faultStorageDir"); localFileStorageConnector = Mockito.spy( new LocalFileStorageConnector(localFileStorageDir) ); @@ -620,71 +607,59 @@ private DataServerQueryHandlerFactory getTestDataServerQueryHandlerFactory() } @Nonnull - private Supplier> getSupplierForSegment(SegmentId segmentId) + private Supplier> getSupplierForSegment(Function tempFolderProducer, SegmentId segmentId) { if (segmentManager.getSegment(segmentId) == null) { final QueryableIndex index; - TemporaryFolder temporaryFolder = new TemporaryFolder(); - try { - temporaryFolder.create(); - } - catch (IOException e) { - throw new ISE(e, "Unable to create temporary folder for tests"); - } - try { - switch (segmentId.getDataSource()) { - case DATASOURCE1: - IncrementalIndexSchema foo1Schema = new IncrementalIndexSchema.Builder() - .withMetrics( - new CountAggregatorFactory("cnt"), - new FloatSumAggregatorFactory("m1", "m1"), - new DoubleSumAggregatorFactory("m2", "m2"), - new HyperUniquesAggregatorFactory("unique_dim1", "dim1") - ) - .withRollup(false) - .build(); - index = IndexBuilder - .create() - .tmpDir(new File(temporaryFolder.newFolder(), "1")) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(foo1Schema) - .rows(ROWS1) - .buildMMappedIndex(); - break; - case DATASOURCE2: - final IncrementalIndexSchema indexSchemaDifferentDim3M1Types = new IncrementalIndexSchema.Builder() - .withDimensionsSpec( - new DimensionsSpec( - ImmutableList.of( - new StringDimensionSchema("dim1"), - new StringDimensionSchema("dim2"), - new LongDimensionSchema("dim3") - ) - ) - ) - .withMetrics( - new CountAggregatorFactory("cnt"), - new LongSumAggregatorFactory("m1", "m1"), - new DoubleSumAggregatorFactory("m2", "m2"), - new HyperUniquesAggregatorFactory("unique_dim1", "dim1") - ) - .withRollup(false) - .build(); - index = IndexBuilder - .create() - .tmpDir(new File(temporaryFolder.newFolder(), "1")) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(indexSchemaDifferentDim3M1Types) - .rows(ROWS2) - .buildMMappedIndex(); - break; - default: - throw new ISE("Cannot query segment %s in test runner", segmentId); + switch (segmentId.getDataSource()) { + case DATASOURCE1: + IncrementalIndexSchema foo1Schema = new IncrementalIndexSchema.Builder() + .withMetrics( + new CountAggregatorFactory("cnt"), + new FloatSumAggregatorFactory("m1", "m1"), + new DoubleSumAggregatorFactory("m2", "m2"), + new HyperUniquesAggregatorFactory("unique_dim1", "dim1") + ) + .withRollup(false) + .build(); + index = IndexBuilder + .create() + .tmpDir(new File(tempFolderProducer.apply("tmpDir"), "1")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema(foo1Schema) + .rows(ROWS1) + .buildMMappedIndex(); + break; + case DATASOURCE2: + final IncrementalIndexSchema indexSchemaDifferentDim3M1Types = new IncrementalIndexSchema.Builder() + .withDimensionsSpec( + new DimensionsSpec( + ImmutableList.of( + new StringDimensionSchema("dim1"), + new StringDimensionSchema("dim2"), + new LongDimensionSchema("dim3") + ) + ) + ) + .withMetrics( + new CountAggregatorFactory("cnt"), + new LongSumAggregatorFactory("m1", "m1"), + new DoubleSumAggregatorFactory("m2", "m2"), + new HyperUniquesAggregatorFactory("unique_dim1", "dim1") + ) + .withRollup(false) + .build(); + index = IndexBuilder + .create() + .tmpDir(new File(tempFolderProducer.apply("tmpDir"), "1")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema(indexSchemaDifferentDim3M1Types) + .rows(ROWS2) + .buildMMappedIndex(); + break; + default: + throw new ISE("Cannot query segment %s in test runner", segmentId); - } - } - catch (IOException e) { - throw new ISE(e, "Unable to load index for segment %s", segmentId); } Segment segment = new Segment() { @@ -1015,7 +990,7 @@ public void verifyPlanningErrors() () -> runMultiStageQuery(sql, queryContext) ); - MatcherAssert.assertThat(e, expectedValidationErrorMatcher); + assertThat(e, expectedValidationErrorMatcher); } protected void verifyWorkerCount(CounterSnapshotsTree counterSnapshotsTree) @@ -1339,7 +1314,7 @@ public void verifyExecutionError() Assert.fail(StringUtils.format("Query did not throw an exception (sql = [%s])", sql)); } catch (Exception e) { - MatcherAssert.assertThat( + assertThat( StringUtils.format("Query error did not match expectations (sql = [%s])", sql), e, expectedExecutionErrorMatcher @@ -1457,7 +1432,7 @@ public Pair, List>> if (expectedExecutionErrorMatcher == null) { throw new ISE(e, "Query %s failed", sql); } - MatcherAssert.assertThat(e, expectedExecutionErrorMatcher); + assertThat(e, expectedExecutionErrorMatcher); return null; } } diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestFileUtils.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestFileUtils.java deleted file mode 100644 index 4ba1a0e6233b..000000000000 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/MSQTestFileUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.msq.test; - -import com.google.common.collect.ImmutableList; -import com.google.common.io.ByteStreams; -import org.apache.druid.java.util.common.IOE; -import org.apache.druid.java.util.common.StringUtils; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; - -public class MSQTestFileUtils -{ - - /** - * Helper method that copies a resource to a temporary file, then returns it. - */ - public static File getResourceAsTemporaryFile(TemporaryFolder temporaryFolder, Object object, final String resource) throws IOException - { - final File file = temporaryFolder.newFile(); - final InputStream stream = object.getClass().getResourceAsStream(resource); - - if (stream == null) { - throw new IOE("No such resource [%s]", resource); - } - - ByteStreams.copy(stream, Files.newOutputStream(file.toPath())); - return file; - } - - /** - * Helper method that populates a temporary file with {@code numRows} rows and {@code numColumns} columns where the - * first column is a string 'timestamp' while the rest are string columns with junk value - */ - public static File generateTemporaryNdJsonFile(TemporaryFolder temporaryFolder, final int numRows, final int numColumns) throws IOException - { - final File file = temporaryFolder.newFile(); - for (int currentRow = 0; currentRow < numRows; ++currentRow) { - StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append("\"timestamp\":\"2016-06-27T00:00:11.080Z\""); - for (int currentColumn = 1; currentColumn < numColumns; ++currentColumn) { - sb.append(StringUtils.format(",\"column%s\":\"val%s\"", currentColumn, currentRow)); - } - sb.append("}"); - Files.write(file.toPath(), ImmutableList.of(sb.toString()), StandardCharsets.UTF_8, StandardOpenOption.APPEND); - } - file.deleteOnExit(); - return file; - } -} diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/DimensionSchemaUtilsTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/DimensionSchemaUtilsTest.java index a82f5a35f9c0..0a4e3ddbd814 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/DimensionSchemaUtilsTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/DimensionSchemaUtilsTest.java @@ -179,19 +179,19 @@ public void testSchemaMvdMode() DruidException.class, () -> DimensionSchemaUtils.createDimensionSchema("x", ColumnType.LONG_ARRAY, false, ArrayIngestMode.MVD) ); - Assert.assertEquals("Numeric arrays can only be ingested when 'arrayIngestMode' is set to 'array' in the MSQ query's context. Current value of the parameter [mvd]", t.getMessage()); + Assert.assertEquals("Numeric arrays can only be ingested when 'arrayIngestMode' is set to 'array'. Current value of the parameter is[mvd]", t.getMessage()); t = Assert.assertThrows( DruidException.class, () -> DimensionSchemaUtils.createDimensionSchema("x", ColumnType.DOUBLE_ARRAY, false, ArrayIngestMode.MVD) ); - Assert.assertEquals("Numeric arrays can only be ingested when 'arrayIngestMode' is set to 'array' in the MSQ query's context. Current value of the parameter [mvd]", t.getMessage()); + Assert.assertEquals("Numeric arrays can only be ingested when 'arrayIngestMode' is set to 'array'. Current value of the parameter is[mvd]", t.getMessage()); t = Assert.assertThrows( DruidException.class, () -> DimensionSchemaUtils.createDimensionSchema("x", ColumnType.FLOAT_ARRAY, false, ArrayIngestMode.MVD) ); - Assert.assertEquals("Numeric arrays can only be ingested when 'arrayIngestMode' is set to 'array' in the MSQ query's context. Current value of the parameter [mvd]", t.getMessage()); + Assert.assertEquals("Numeric arrays can only be ingested when 'arrayIngestMode' is set to 'array'. Current value of the parameter is[mvd]", t.getMessage()); } @Test diff --git a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/MultiStageQueryContextTest.java b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/MultiStageQueryContextTest.java index c2b401c70db5..9f24a8b4331d 100644 --- a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/MultiStageQueryContextTest.java +++ b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/util/MultiStageQueryContextTest.java @@ -314,7 +314,7 @@ public void testUseConcurrentLocks() private static List decodeSortOrder(@Nullable final String input) { - return MultiStageQueryContext.decodeSortOrder(input); + return MultiStageQueryContext.decodeList(MultiStageQueryContext.CTX_SORT_ORDER, input); } private static IndexSpec decodeIndexSpec(@Nullable final Object inputSpecObject) diff --git a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/RetryableS3OutputStream.java b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/RetryableS3OutputStream.java index 82f5cd812cc9..c71bb4e788b7 100644 --- a/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/RetryableS3OutputStream.java +++ b/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/output/RetryableS3OutputStream.java @@ -269,7 +269,12 @@ public void close() throws IOException // Closeables are closed in LIFO order closer.register(() -> { // This should be emitted as a metric - LOG.info("Total push time: [%d] ms", pushStopwatch.elapsed(TimeUnit.MILLISECONDS)); + LOG.info( + "Pushed total [%d] parts containing [%d] bytes in [%d]ms.", + numChunksPushed, + resultsSize, + pushStopwatch.elapsed(TimeUnit.MILLISECONDS) + ); }); closer.register(() -> org.apache.commons.io.FileUtils.forceDelete(chunkStorePath)); diff --git a/extensions-core/stats/pom.xml b/extensions-core/stats/pom.xml index 78a148e979ba..726c4a71d4d6 100644 --- a/extensions-core/stats/pom.xml +++ b/extensions-core/stats/pom.xml @@ -93,6 +93,37 @@ + + junit + junit + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + + org.apache.druid druid-processing @@ -117,11 +148,6 @@ test-jar - - junit - junit - test - org.mockito mockito-core diff --git a/extensions-core/stats/src/test/java/org/apache/druid/query/aggregation/variance/sql/VarianceSqlAggregatorTest.java b/extensions-core/stats/src/test/java/org/apache/druid/query/aggregation/variance/sql/VarianceSqlAggregatorTest.java index 879d6e093a77..66c6290ea3eb 100644 --- a/extensions-core/stats/src/test/java/org/apache/druid/query/aggregation/variance/sql/VarianceSqlAggregatorTest.java +++ b/extensions-core/stats/src/test/java/org/apache/druid/query/aggregation/variance/sql/VarianceSqlAggregatorTest.java @@ -62,9 +62,8 @@ import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.List; public class VarianceSqlAggregatorTest extends BaseCalciteQueryTest @@ -81,13 +80,13 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { ComplexMetrics.registerSerde(VarianceSerde.TYPE_NAME, new VarianceSerde()); final QueryableIndex index = IndexBuilder.create(CalciteTests.getJsonMapper().registerModules(new DruidStatsModule().getJacksonModules())) - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() diff --git a/extensions-core/testing-tools/pom.xml b/extensions-core/testing-tools/pom.xml index 408aa2d1a8e3..978072714b8d 100644 --- a/extensions-core/testing-tools/pom.xml +++ b/extensions-core/testing-tools/pom.xml @@ -125,6 +125,31 @@ junit test + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + nl.jqno.equalsverifier equalsverifier diff --git a/extensions-core/testing-tools/src/test/java/org/apache/druid/query/sql/SleepSqlTest.java b/extensions-core/testing-tools/src/test/java/org/apache/druid/query/sql/SleepSqlTest.java index d8dc51f6c245..9df6d0f31729 100644 --- a/extensions-core/testing-tools/src/test/java/org/apache/druid/query/sql/SleepSqlTest.java +++ b/extensions-core/testing-tools/src/test/java/org/apache/druid/query/sql/SleepSqlTest.java @@ -30,7 +30,7 @@ import org.apache.druid.segment.virtual.ExpressionVirtualColumn; import org.apache.druid.sql.calcite.BaseCalciteQueryTest; import org.apache.druid.sql.calcite.filtration.Filtration; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SleepSqlTest extends BaseCalciteQueryTest { diff --git a/indexing-hadoop/pom.xml b/indexing-hadoop/pom.xml index 2ac50b53a51f..1e7ff45e2706 100644 --- a/indexing-hadoop/pom.xml +++ b/indexing-hadoop/pom.xml @@ -180,9 +180,21 @@ test - log4j - log4j - 1.2.17 + org.apache.logging.log4j + log4j-api + ${log4j.version} + test + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + test + + + org.apache.logging.log4j + log4j-1.2-api + ${log4j.version} test diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/MarkSegmentsAsUnusedAction.java b/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/MarkSegmentsAsUnusedAction.java index ddf57afbc185..93cb75280fac 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/MarkSegmentsAsUnusedAction.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/common/actions/MarkSegmentsAsUnusedAction.java @@ -63,9 +63,8 @@ public TypeReference getReturnTypeReference() @Override public Integer perform(Task task, TaskActionToolbox toolbox) { - int numMarked = toolbox.getIndexerMetadataStorageCoordinator() - .markSegmentsAsUnusedWithinInterval(dataSource, interval); - return numMarked; + return toolbox.getIndexerMetadataStorageCoordinator() + .markSegmentsAsUnusedWithinInterval(dataSource, interval); } @Override diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentGenerateTask.java b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentGenerateTask.java index 1cb3d36ad759..46be219d878a 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentGenerateTask.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/PartialSegmentGenerateTask.java @@ -27,8 +27,10 @@ import org.apache.druid.indexer.partitions.PartitionsSpec; import org.apache.druid.indexing.common.IngestionStatsAndErrorsTaskReport; import org.apache.druid.indexing.common.IngestionStatsAndErrorsTaskReportData; +import org.apache.druid.indexing.common.TaskRealtimeMetricsMonitorBuilder; import org.apache.druid.indexing.common.TaskReport; import org.apache.druid.indexing.common.TaskToolbox; +import org.apache.druid.indexing.common.stats.TaskRealtimeMetricsMonitor; import org.apache.druid.indexing.common.task.BatchAppenderators; import org.apache.druid.indexing.common.task.IndexTaskUtils; import org.apache.druid.indexing.common.task.InputSourceProcessor; @@ -40,7 +42,6 @@ import org.apache.druid.indexing.firehose.WindowedSegmentId; import org.apache.druid.indexing.input.DruidInputSource; import org.apache.druid.indexing.worker.shuffle.ShuffleDataSegmentPusher; -import org.apache.druid.query.DruidMetrics; import org.apache.druid.segment.incremental.ParseExceptionHandler; import org.apache.druid.segment.incremental.ParseExceptionReport; import org.apache.druid.segment.incremental.RowIngestionMeters; @@ -48,7 +49,6 @@ import org.apache.druid.segment.indexing.RealtimeIOConfig; import org.apache.druid.segment.realtime.FireDepartment; import org.apache.druid.segment.realtime.FireDepartmentMetrics; -import org.apache.druid.segment.realtime.RealtimeMetricsMonitor; import org.apache.druid.segment.realtime.appenderator.Appenderator; import org.apache.druid.segment.realtime.appenderator.BatchAppenderatorDriver; import org.apache.druid.segment.realtime.appenderator.SegmentAllocator; @@ -179,9 +179,10 @@ private List generateSegments( final FireDepartmentMetrics fireDepartmentMetrics = fireDepartmentForMetrics.getMetrics(); buildSegmentsMeters = toolbox.getRowIngestionMetersFactory().createRowIngestionMeters(); - RealtimeMetricsMonitor metricsMonitor = new RealtimeMetricsMonitor( - Collections.singletonList(fireDepartmentForMetrics), - Collections.singletonMap(DruidMetrics.TASK_ID, new String[]{getId()}) + TaskRealtimeMetricsMonitor metricsMonitor = TaskRealtimeMetricsMonitorBuilder.build( + this, + fireDepartmentForMetrics, + buildSegmentsMeters ); toolbox.addMonitor(metricsMonitor); diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java index bbd3f2964b61..9e0c8d80c835 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/SinglePhaseSubTask.java @@ -36,10 +36,12 @@ import org.apache.druid.indexer.partitions.DynamicPartitionsSpec; import org.apache.druid.indexing.common.IngestionStatsAndErrorsTaskReport; import org.apache.druid.indexing.common.IngestionStatsAndErrorsTaskReportData; +import org.apache.druid.indexing.common.TaskRealtimeMetricsMonitorBuilder; import org.apache.druid.indexing.common.TaskReport; import org.apache.druid.indexing.common.TaskToolbox; import org.apache.druid.indexing.common.actions.SurrogateTaskActionClient; import org.apache.druid.indexing.common.actions.TaskActionClient; +import org.apache.druid.indexing.common.stats.TaskRealtimeMetricsMonitor; import org.apache.druid.indexing.common.task.AbstractBatchIndexTask; import org.apache.druid.indexing.common.task.AbstractTask; import org.apache.druid.indexing.common.task.BatchAppenderators; @@ -53,7 +55,6 @@ import org.apache.druid.java.util.common.granularity.Granularity; import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.java.util.common.parsers.CloseableIterator; -import org.apache.druid.query.DruidMetrics; import org.apache.druid.segment.incremental.ParseExceptionHandler; import org.apache.druid.segment.incremental.ParseExceptionReport; import org.apache.druid.segment.incremental.RowIngestionMeters; @@ -63,7 +64,6 @@ import org.apache.druid.segment.indexing.granularity.GranularitySpec; import org.apache.druid.segment.realtime.FireDepartment; import org.apache.druid.segment.realtime.FireDepartmentMetrics; -import org.apache.druid.segment.realtime.RealtimeMetricsMonitor; import org.apache.druid.segment.realtime.appenderator.Appenderator; import org.apache.druid.segment.realtime.appenderator.AppenderatorDriverAddResult; import org.apache.druid.segment.realtime.appenderator.BaseAppenderatorDriver; @@ -373,9 +373,10 @@ private Set generateAndPushSegments( new FireDepartment(dataSchema, new RealtimeIOConfig(null, null), null); final FireDepartmentMetrics fireDepartmentMetrics = fireDepartmentForMetrics.getMetrics(); - RealtimeMetricsMonitor metricsMonitor = new RealtimeMetricsMonitor( - Collections.singletonList(fireDepartmentForMetrics), - Collections.singletonMap(DruidMetrics.TASK_ID, new String[]{getId()}) + TaskRealtimeMetricsMonitor metricsMonitor = TaskRealtimeMetricsMonitorBuilder.build( + this, + fireDepartmentForMetrics, + rowIngestionMeters ); toolbox.addMonitor(metricsMonitor); diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/HeapMemoryTaskStorage.java b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/HeapMemoryTaskStorage.java index d01b4b4a3f8c..e84976db86c4 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/HeapMemoryTaskStorage.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/HeapMemoryTaskStorage.java @@ -28,6 +28,7 @@ import com.google.common.collect.Ordering; import com.google.errorprone.annotations.concurrent.GuardedBy; import com.google.inject.Inject; +import org.apache.druid.error.EntryAlreadyExists; import org.apache.druid.indexer.TaskInfo; import org.apache.druid.indexer.TaskStatus; import org.apache.druid.indexer.TaskStatusPlus; @@ -37,7 +38,6 @@ import org.apache.druid.indexing.common.task.Task; import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.java.util.common.logger.Logger; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.TaskLookup; import org.apache.druid.metadata.TaskLookup.CompleteTaskLookup; import org.apache.druid.metadata.TaskLookup.TaskLookupType; @@ -76,7 +76,7 @@ public HeapMemoryTaskStorage(TaskStorageConfig config) } @Override - public void insert(Task task, TaskStatus status) throws EntryExistsException + public void insert(Task task, TaskStatus status) { Preconditions.checkNotNull(task, "task"); Preconditions.checkNotNull(status, "status"); @@ -88,12 +88,12 @@ public void insert(Task task, TaskStatus status) throws EntryExistsException ); TaskStuff newTaskStuff = new TaskStuff(task, status, DateTimes.nowUtc(), task.getDataSource()); - TaskStuff alreadyExisted = tasks.putIfAbsent(task.getId(), newTaskStuff); - if (alreadyExisted != null) { - throw new EntryExistsException("Task", task.getId()); + TaskStuff existingTaskStuff = tasks.putIfAbsent(task.getId(), newTaskStuff); + if (existingTaskStuff != null) { + throw EntryAlreadyExists.exception("Task[%s] already exists", task.getId()); } - log.info("Inserted task %s with status: %s", task.getId(), status); + log.info("Inserted task[%s] with status[%s]", task.getId(), status); } @Override diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/MetadataTaskStorage.java b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/MetadataTaskStorage.java index 251a2c8ce31a..15730d48bb1f 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/MetadataTaskStorage.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/MetadataTaskStorage.java @@ -25,7 +25,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.inject.Inject; -import org.apache.druid.common.exception.DruidException; +import org.apache.druid.error.DruidException; import org.apache.druid.indexer.TaskInfo; import org.apache.druid.indexer.TaskStatus; import org.apache.druid.indexer.TaskStatusPlus; @@ -38,7 +38,6 @@ import org.apache.druid.java.util.common.lifecycle.LifecycleStart; import org.apache.druid.java.util.common.lifecycle.LifecycleStop; import org.apache.druid.java.util.emitter.EmittingLogger; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.MetadataStorageActionHandler; import org.apache.druid.metadata.MetadataStorageActionHandlerFactory; import org.apache.druid.metadata.MetadataStorageActionHandlerTypes; @@ -126,7 +125,7 @@ public void stop() } @Override - public void insert(final Task task, final TaskStatus status) throws EntryExistsException + public void insert(final Task task, final TaskStatus status) { Preconditions.checkNotNull(task, "task"); Preconditions.checkNotNull(status, "status"); diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskQueue.java b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskQueue.java index 830ae9b732bd..6dbf6e707989 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskQueue.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskQueue.java @@ -32,6 +32,7 @@ import org.apache.druid.annotations.SuppressFBWarnings; import org.apache.druid.common.utils.IdUtils; import org.apache.druid.error.DruidException; +import org.apache.druid.error.EntryAlreadyExists; import org.apache.druid.indexer.RunnerTaskState; import org.apache.druid.indexer.TaskLocation; import org.apache.druid.indexer.TaskStatus; @@ -55,7 +56,6 @@ import org.apache.druid.java.util.emitter.EmittingLogger; import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.java.util.emitter.service.ServiceMetricEvent; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.server.coordinator.stats.CoordinatorRunStats; import org.apache.druid.utils.CollectionUtils; @@ -495,16 +495,14 @@ private boolean isTaskPending(Task task) * @param task task to add * * @return true - * - * @throws EntryExistsException if the task already exists */ - public boolean add(final Task task) throws EntryExistsException + public boolean add(final Task task) { // Before adding the task, validate the ID, so it can be safely used in file paths, znodes, etc. IdUtils.validateId("Task ID", task.getId()); if (taskStorage.getTask(task.getId()).isPresent()) { - throw new EntryExistsException("Task", task.getId()); + throw EntryAlreadyExists.exception("Task[%s] already exists", task.getId()); } // Set forceTimeChunkLock before adding task spec to taskStorage, so that we can see always consistent task spec. diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskStorage.java b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskStorage.java index e9b0af24057f..235c763e1f71 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskStorage.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/TaskStorage.java @@ -26,7 +26,6 @@ import org.apache.druid.indexing.common.TaskLock; import org.apache.druid.indexing.common.actions.TaskAction; import org.apache.druid.indexing.common.task.Task; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.TaskLookup; import org.apache.druid.metadata.TaskLookup.TaskLookupType; @@ -42,10 +41,8 @@ public interface TaskStorage * * @param task task to add * @param status task status - * - * @throws EntryExistsException if the task ID already exists */ - void insert(Task task, TaskStatus status) throws EntryExistsException; + void insert(Task task, TaskStatus status); /** * Persists task status in the storage facility. This method should throw an exception if the task status lifecycle diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java index 8416b2e09688..3e6ebc4816ff 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java @@ -35,7 +35,6 @@ import org.apache.druid.common.config.ConfigManager.SetResult; import org.apache.druid.common.config.JacksonConfigManager; import org.apache.druid.error.DruidException; -import org.apache.druid.error.ErrorResponse; import org.apache.druid.indexer.RunnerTaskState; import org.apache.druid.indexer.TaskIdentifier; import org.apache.druid.indexer.TaskInfo; @@ -71,6 +70,7 @@ import org.apache.druid.metadata.TaskLookup.CompleteTaskLookup; import org.apache.druid.metadata.TaskLookup.TaskLookupType; import org.apache.druid.server.http.HttpMediaType; +import org.apache.druid.server.http.ServletResourceUtils; import org.apache.druid.server.http.security.ConfigResourceFilter; import org.apache.druid.server.http.security.DatasourceResourceFilter; import org.apache.druid.server.http.security.StateResourceFilter; @@ -241,15 +241,7 @@ public Response taskPost( return Response.ok(ImmutableMap.of("task", task.getId())).build(); } catch (DruidException e) { - return Response - .status(e.getStatusCode()) - .entity(new ErrorResponse(e)) - .build(); - } - catch (org.apache.druid.common.exception.DruidException e) { - return Response.status(e.getResponseCode()) - .entity(ImmutableMap.of("error", e.getMessage())) - .build(); + return ServletResourceUtils.buildErrorResponseFrom(e); } } ); diff --git a/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisor.java b/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisor.java index 5ea7a4168cb1..507001a7af6d 100644 --- a/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisor.java +++ b/indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisor.java @@ -88,7 +88,6 @@ import org.apache.druid.java.util.emitter.EmittingLogger; import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.java.util.emitter.service.ServiceMetricEvent; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.MetadataSupervisorManager; import org.apache.druid.query.DruidMetrics; import org.apache.druid.query.ordering.StringComparators; @@ -3934,9 +3933,9 @@ private void createTasksForGroup(int groupId, int replicas) try { taskQueue.get().add(indexTask); } - catch (EntryExistsException e) { + catch (DruidException e) { stateManager.recordThrowableEvent(e); - log.error("Tried to add task [%s] but it already exists", indexTask.getId()); + log.noStackTrace().error(e, "Tried to add task [%s] but encountered error", indexTask.getId()); } } else { log.error("Failed to get task queue because I'm not the leader!"); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/AppenderatorDriverRealtimeIndexTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/AppenderatorDriverRealtimeIndexTaskTest.java index b864fdb44a54..ef769d73006d 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/AppenderatorDriverRealtimeIndexTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/AppenderatorDriverRealtimeIndexTaskTest.java @@ -48,6 +48,7 @@ import org.apache.druid.discovery.DataNodeService; import org.apache.druid.discovery.DruidNodeAnnouncer; import org.apache.druid.discovery.LookupNodeService; +import org.apache.druid.error.DruidException; import org.apache.druid.indexer.IngestionState; import org.apache.druid.indexer.TaskState; import org.apache.druid.indexer.TaskStatus; @@ -92,7 +93,6 @@ import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.java.util.metrics.MonitorScheduler; import org.apache.druid.math.expr.ExprMacroTable; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.IndexerSQLMetadataStorageCoordinator; import org.apache.druid.metadata.TestDerbyConnector; import org.apache.druid.query.DefaultQueryRunnerFactoryConglomerate; @@ -1302,8 +1302,8 @@ private ListenableFuture runTask(final Task task) try { taskStorage.insert(task, TaskStatus.running(task.getId())); } - catch (EntryExistsException e) { - // suppress + catch (DruidException e) { + log.noStackTrace().info(e, "Suppressing exception while inserting task [%s]", task.getId()); } taskLockbox.syncFromStorage(); final TaskToolbox toolbox = taskToolboxFactory.build(task); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java index bec9bd135e74..b82dafccc6be 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/IngestionTestBase.java @@ -62,7 +62,6 @@ import org.apache.druid.java.util.common.RE; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.emitter.EmittingLogger; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.IndexerSQLMetadataStorageCoordinator; import org.apache.druid.metadata.SQLMetadataConnector; import org.apache.druid.metadata.SegmentsMetadataManager; @@ -158,7 +157,7 @@ public TestLocalTaskActionClient createActionClient(Task task) return new TestLocalTaskActionClient(task); } - public void prepareTaskForLocking(Task task) throws EntryExistsException + public void prepareTaskForLocking(Task task) { lockbox.add(task); taskStorage.insert(task, TaskStatus.running(task.getId())); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTaskTest.java index 2de9a0f10f2a..382673bed4b8 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTaskTest.java @@ -55,109 +55,94 @@ public class KillUnusedSegmentsTaskTest extends IngestionTestBase private TestTaskRunner taskRunner; + private DataSegment segment1; + private DataSegment segment2; + private DataSegment segment3; + private DataSegment segment4; + @Before public void setup() { taskRunner = new TestTaskRunner(); + + final String version = DateTimes.nowUtc().toString(); + segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), version); + segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), version); + segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), version); + segment4 = newSegment(Intervals.of("2019-04-01/2019-05-01"), version); } @Test public void testKill() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final Set segments = ImmutableSet.of( - newSegment(Intervals.of("2019-01-01/2019-02-01"), version), - newSegment(Intervals.of("2019-02-01/2019-03-01"), version), - newSegment(Intervals.of("2019-03-01/2019-04-01"), version), - newSegment(Intervals.of("2019-04-01/2019-05-01"), version) - ); + final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); - Assert.assertEquals(segments, announced); Assert.assertTrue( getSegmentsMetadataManager().markSegmentAsUnused( - newSegment(Intervals.of("2019-02-01/2019-03-01"), version).getId() + segment2.getId() ) ); Assert.assertTrue( getSegmentsMetadataManager().markSegmentAsUnused( - newSegment(Intervals.of("2019-03-01/2019-04-01"), version).getId() + segment3.getId() ) ); - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2019-03-01/2019-04-01"), - null, - null, - false, - null, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2019-03-01/2019-04-01")) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); - final List unusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2019/2020"), - null, - null, - null + final List observedUnusedSegments = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2019/2020"), + null, + null, + null ); - Assert.assertEquals(ImmutableList.of(newSegment(Intervals.of("2019-02-01/2019-03-01"), version)), unusedSegments); - Assertions.assertThat(getMetadataStorageCoordinator().retrieveUsedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2019/2020"), - Segments.ONLY_VISIBLE) - ).containsExactlyInAnyOrder( - newSegment(Intervals.of("2019-01-01/2019-02-01"), version), - newSegment(Intervals.of("2019-04-01/2019-05-01"), version) - ); + Assert.assertEquals(ImmutableList.of(segment2), observedUnusedSegments); + Assertions.assertThat( + getMetadataStorageCoordinator().retrieveUsedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2019/2020"), + Segments.ONLY_VISIBLE + ) + ).containsExactlyInAnyOrder(segment1, segment4); - Assert.assertEquals(new KillTaskReport.Stats(1, 2, 0), getReportedStats()); + Assert.assertEquals( + new KillTaskReport.Stats(1, 2, 0), + getReportedStats() + ); } @Test public void testKillWithMarkUnused() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final Set segments = ImmutableSet.of( - newSegment(Intervals.of("2019-01-01/2019-02-01"), version), - newSegment(Intervals.of("2019-02-01/2019-03-01"), version), - newSegment(Intervals.of("2019-03-01/2019-04-01"), version), - newSegment(Intervals.of("2019-04-01/2019-05-01"), version) - ); + final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); - Assert.assertEquals(segments, announced); Assert.assertTrue( getSegmentsMetadataManager().markSegmentAsUnused( - newSegment(Intervals.of("2019-02-01/2019-03-01"), version).getId() + segment2.getId() ) ); - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2019-03-01/2019-04-01"), - null, - null, - true, - null, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2019-03-01/2019-04-01")) + .markAsUnused(true) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); - final List unusedSegments = + final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, Intervals.of("2019/2020"), @@ -166,16 +151,19 @@ public void testKillWithMarkUnused() throws Exception null ); - Assert.assertEquals(ImmutableList.of(newSegment(Intervals.of("2019-02-01/2019-03-01"), version)), unusedSegments); + Assert.assertEquals(ImmutableList.of(segment2), observedUnusedSegments); Assertions.assertThat( - getMetadataStorageCoordinator() - .retrieveUsedSegmentsForInterval(DATA_SOURCE, Intervals.of("2019/2020"), Segments.ONLY_VISIBLE) - ).containsExactlyInAnyOrder( - newSegment(Intervals.of("2019-01-01/2019-02-01"), version), - newSegment(Intervals.of("2019-04-01/2019-05-01"), version) - ); + getMetadataStorageCoordinator().retrieveUsedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2019/2020"), + Segments.ONLY_VISIBLE + ) + ).containsExactlyInAnyOrder(segment1, segment4); - Assert.assertEquals(new KillTaskReport.Stats(1, 2, 1), getReportedStats()); + Assert.assertEquals( + new KillTaskReport.Stats(1, 2, 1), + getReportedStats() + ); } @Test @@ -186,13 +174,13 @@ public void testKillSegmentsWithVersions() throws Exception final String v2 = now.minusHours(2).toString(); final String v3 = now.minusHours(3).toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v1); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v1); - final DataSegment segment4 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v2); - final DataSegment segment5 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v3); + final DataSegment segment1V1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1); + final DataSegment segment2V1 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v1); + final DataSegment segment3V1 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v1); + final DataSegment segment4V2 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v2); + final DataSegment segment5V3 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v3); - final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4, segment5); + final Set segments = ImmutableSet.of(segment1V1, segment2V1, segment3V1, segment4V2, segment5V3); Assert.assertEquals(segments, getMetadataStorageCoordinator().commitSegments(segments)); Assert.assertEquals( @@ -202,17 +190,12 @@ public void testKillSegmentsWithVersions() throws Exception ) ); - final KillUnusedSegmentsTask task = new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018/2020"), - ImmutableList.of(v1, v2), - null, - false, - 3, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018/2020")) + .versions(ImmutableList.of(v1, v2)) + .batchSize(3) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); Assert.assertEquals( @@ -220,14 +203,15 @@ public void testKillSegmentsWithVersions() throws Exception getReportedStats() ); - final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2018/2020"), - null, - null - ); + final List observedUnusedSegments = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2018/2020"), + null, + null + ); - Assert.assertEquals(ImmutableSet.of(segment5), new HashSet<>(observedUnusedSegments)); + Assert.assertEquals(ImmutableSet.of(segment5V3), new HashSet<>(observedUnusedSegments)); } @Test @@ -238,13 +222,13 @@ public void testKillSegmentsWithVersionsAndLimit() throws Exception final String v2 = now.minusHours(2).toString(); final String v3 = now.minusHours(3).toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v1); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v1); - final DataSegment segment4 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v2); - final DataSegment segment5 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v3); + final DataSegment segment1V1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1); + final DataSegment segment2V1 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v1); + final DataSegment segment3V1 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v1); + final DataSegment segment4V2 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v2); + final DataSegment segment5V3 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v3); - final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4, segment5); + final Set segments = ImmutableSet.of(segment1V1, segment2V1, segment3V1, segment4V2, segment5V3); Assert.assertEquals(segments, getMetadataStorageCoordinator().commitSegments(segments)); Assert.assertEquals( @@ -254,17 +238,13 @@ public void testKillSegmentsWithVersionsAndLimit() throws Exception ) ); - final KillUnusedSegmentsTask task = new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018/2020"), - ImmutableList.of(v1), - null, - false, - 3, - 2, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018/2020")) + .versions(ImmutableList.of(v1)) + .batchSize(3) + .limit(2) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); Assert.assertEquals( @@ -272,14 +252,15 @@ public void testKillSegmentsWithVersionsAndLimit() throws Exception getReportedStats() ); - final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2018/2020"), - null, - null - ); + final List observedUnusedSegments = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2018/2020"), + null, + null + ); - Assert.assertEquals(ImmutableSet.of(segment3, segment4, segment5), new HashSet<>(observedUnusedSegments)); + Assert.assertEquals(ImmutableSet.of(segment3V1, segment4V2, segment5V3), new HashSet<>(observedUnusedSegments)); } @Test @@ -290,13 +271,13 @@ public void testKillWithNonExistentVersion() throws Exception final String v2 = now.minusHours(2).toString(); final String v3 = now.minusHours(3).toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v1); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v1); - final DataSegment segment4 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v2); - final DataSegment segment5 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v3); + final DataSegment segment1V1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1); + final DataSegment segment2V1 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v1); + final DataSegment segment3V1 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v1); + final DataSegment segment4V2 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v2); + final DataSegment segment5V3 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v3); - final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4, segment5); + final Set segments = ImmutableSet.of(segment1V1, segment2V1, segment3V1, segment4V2, segment5V3); Assert.assertEquals(segments, getMetadataStorageCoordinator().commitSegments(segments)); Assert.assertEquals( @@ -306,17 +287,13 @@ public void testKillWithNonExistentVersion() throws Exception ) ); - final KillUnusedSegmentsTask task = new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018/2020"), - ImmutableList.of(now.plusDays(100).toString()), - null, - false, - 3, - 2, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018/2020")) + .versions(ImmutableList.of(now.plusDays(100).toString())) + .batchSize(3) + .limit(2) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); Assert.assertEquals( @@ -324,12 +301,13 @@ public void testKillWithNonExistentVersion() throws Exception getReportedStats() ); - final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2018/2020"), - null, - null - ); + final List observedUnusedSegments = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2018/2020"), + null, + null + ); Assert.assertEquals(segments, new HashSet<>(observedUnusedSegments)); } @@ -348,12 +326,12 @@ public void testKillUnusedSegmentsWithUsedLoadSpec() throws Exception final String v2 = now.minusHours(2).toString(); final String v3 = now.minusHours(3).toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1, ImmutableMap.of("foo", "1")); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v2, ImmutableMap.of("foo", "1")); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v3, ImmutableMap.of("foo", "1")); + final DataSegment segment1V1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), v1, ImmutableMap.of("foo", "1")); + final DataSegment segment2V2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), v2, ImmutableMap.of("foo", "1")); + final DataSegment segment3V3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), v3, ImmutableMap.of("foo", "1")); - final Set segments = ImmutableSet.of(segment1, segment2, segment3); - final Set unusedSegments = ImmutableSet.of(segment1, segment2); + final Set segments = ImmutableSet.of(segment1V1, segment2V2, segment3V3); + final Set unusedSegments = ImmutableSet.of(segment1V1, segment2V2); Assert.assertEquals(segments, getMetadataStorageCoordinator().commitSegments(segments)); Assert.assertEquals( @@ -363,17 +341,12 @@ public void testKillUnusedSegmentsWithUsedLoadSpec() throws Exception ) ); - final KillUnusedSegmentsTask task = new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018/2020"), - ImmutableList.of(v1, v2), - null, - false, - null, - 100, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018/2020")) + .versions(ImmutableList.of(v1, v2)) + .limit(100) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); Assert.assertEquals( @@ -381,12 +354,13 @@ public void testKillUnusedSegmentsWithUsedLoadSpec() throws Exception getReportedStats() ); - final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2018/2020"), - null, - null - ); + final List observedUnusedSegments = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2018/2020"), + null, + null + ); Assert.assertEquals(ImmutableSet.of(), new HashSet<>(observedUnusedSegments)); } @@ -394,61 +368,42 @@ public void testKillUnusedSegmentsWithUsedLoadSpec() throws Exception @Test public void testGetInputSourceResources() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2019-03-01/2019-04-01"), - null, - null, - true, - null, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2019-03-01/2019-04-01")) + .markAsUnused(true) + .build(); Assert.assertTrue(task.getInputSourceResources().isEmpty()); } @Test public void testKillBatchSizeOneAndLimit4() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final Set segments = ImmutableSet.of( - newSegment(Intervals.of("2019-01-01/2019-02-01"), version), - newSegment(Intervals.of("2019-02-01/2019-03-01"), version), - newSegment(Intervals.of("2019-03-01/2019-04-01"), version), - newSegment(Intervals.of("2019-04-01/2019-05-01"), version) - ); + final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); Assert.assertEquals(segments, announced); - Assert.assertEquals( segments.size(), getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01") + Intervals.of("2018-01-01/2020-01-01"), + null ) ); - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 1, - 4, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .batchSize(1) + .limit(4) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); // we expect ALL tasks to be deleted - final List unusedSegments = + final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, Intervals.of("2019/2020"), @@ -456,8 +411,11 @@ public void testKillBatchSizeOneAndLimit4() throws Exception null ); - Assert.assertEquals(Collections.emptyList(), unusedSegments); - Assert.assertEquals(new KillTaskReport.Stats(4, 4, 0), getReportedStats()); + Assert.assertEquals(Collections.emptyList(), observedUnusedSegments); + Assert.assertEquals( + new KillTaskReport.Stats(4, 4, 0), + getReportedStats() + ); } /** @@ -468,12 +426,6 @@ public void testKillBatchSizeOneAndLimit4() throws Exception @Test public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), version); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), version); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), version); - final DataSegment segment4 = newSegment(Intervals.of("2019-04-01/2019-05-01"), version); - final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); @@ -483,7 +435,8 @@ public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() 1, getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - segment1.getInterval() + segment1.getInterval(), + null ) ); @@ -491,7 +444,8 @@ public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() 1, getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - segment4.getInterval() + segment4.getInterval(), + null ) ); @@ -499,7 +453,8 @@ public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() 1, getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - segment3.getInterval() + segment3.getInterval(), + null ) ); @@ -509,23 +464,16 @@ public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() final Interval umbrellaInterval = JodaUtils.umbrellaInterval(segmentIntervals); - - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - umbrellaInterval, - null, - null, - false, - 1, - 10, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .batchSize(1) + .limit(10) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); - final List unusedSegments = + final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, umbrellaInterval, @@ -533,8 +481,11 @@ public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() null ); - Assert.assertEquals(ImmutableList.of(), unusedSegments); - Assert.assertEquals(new KillTaskReport.Stats(3, 4, 0), getReportedStats()); + Assert.assertEquals(ImmutableList.of(), observedUnusedSegments); + Assert.assertEquals( + new KillTaskReport.Stats(3, 4, 0), + getReportedStats() + ); } /** @@ -552,12 +503,6 @@ public void testKillMultipleUnusedSegmentsWithNullMaxUsedStatusLastUpdatedTime() @Test public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedTime() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), version); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), version); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), version); - final DataSegment segment4 = newSegment(Intervals.of("2019-04-01/2019-05-01"), version); - final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); @@ -567,7 +512,8 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT 1, getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - segment1.getInterval() + segment1.getInterval(), + null ) ); @@ -575,27 +521,27 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT 1, getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - segment4.getInterval() + segment4.getInterval(), + null ) ); - // Capture the last updated time cutoff - final DateTime maxUsedStatusLastUpdatedTime1 = DateTimes.nowUtc(); - - // Delay for 1s, mark the segments as unused and then capture the last updated time cutoff again - Thread.sleep(1000); + final DateTime lastUpdatedTime1 = DateTimes.nowUtc(); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment1.getId().toString(), lastUpdatedTime1); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment4.getId().toString(), lastUpdatedTime1); - // now mark the third segment as unused + // Now mark the third segment as unused Assert.assertEquals( 1, getSegmentsMetadataManager().markAsUnusedSegmentsInInterval( DATA_SOURCE, - segment3.getInterval() + segment3.getInterval(), + null ) ); - final DateTime maxUsedStatusLastUpdatedTime2 = DateTimes.nowUtc(); - + final DateTime lastUpdatedTime2 = DateTimes.nowUtc(); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment3.getId().toString(), lastUpdatedTime2); final List segmentIntervals = segments.stream() .map(DataSegment::getInterval) @@ -603,22 +549,17 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT final Interval umbrellaInterval = JodaUtils.umbrellaInterval(segmentIntervals); - final KillUnusedSegmentsTask task1 = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - umbrellaInterval, - null, - null, - false, - 1, - 10, - maxUsedStatusLastUpdatedTime1 - ); + final KillUnusedSegmentsTask task1 = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .batchSize(1) + .limit(10) + .maxUsedStatusLastUpdatedTime(lastUpdatedTime1) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task1).get().getStatusCode()); - final List unusedSegments = + final List observedUnusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, umbrellaInterval, @@ -626,25 +567,23 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT null ); - Assert.assertEquals(ImmutableList.of(segment3), unusedSegments); - Assert.assertEquals(new KillTaskReport.Stats(2, 3, 0), getReportedStats()); + Assert.assertEquals(ImmutableList.of(segment3), observedUnusedSegments); + Assert.assertEquals( + new KillTaskReport.Stats(2, 3, 0), + getReportedStats() + ); - final KillUnusedSegmentsTask task2 = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - umbrellaInterval, - null, - null, - false, - 1, - 10, - maxUsedStatusLastUpdatedTime2 - ); + final KillUnusedSegmentsTask task2 = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .batchSize(1) + .limit(10) + .maxUsedStatusLastUpdatedTime(lastUpdatedTime2) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task2).get().getStatusCode()); - final List unusedSegments2 = + final List observedUnusedSegments2 = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, umbrellaInterval, @@ -652,8 +591,11 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT null ); - Assert.assertEquals(ImmutableList.of(), unusedSegments2); - Assert.assertEquals(new KillTaskReport.Stats(1, 2, 0), getReportedStats()); + Assert.assertEquals(ImmutableList.of(), observedUnusedSegments2); + Assert.assertEquals( + new KillTaskReport.Stats(1, 2, 0), + getReportedStats() + ); } /** @@ -672,12 +614,6 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT @Test public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedTime2() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final DataSegment segment1 = newSegment(Intervals.of("2019-01-01/2019-02-01"), version); - final DataSegment segment2 = newSegment(Intervals.of("2019-02-01/2019-03-01"), version); - final DataSegment segment3 = newSegment(Intervals.of("2019-03-01/2019-04-01"), version); - final DataSegment segment4 = newSegment(Intervals.of("2019-04-01/2019-05-01"), version); - final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); @@ -693,10 +629,9 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT ) ); - final DateTime maxUsedStatusLastUpdatedTime1 = DateTimes.nowUtc(); - - // Delay for 1s, mark the segments as unused and then capture the last updated time cutoff again - Thread.sleep(1000); + final DateTime lastUpdatedTime1 = DateTimes.nowUtc(); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment1.getId().toString(), lastUpdatedTime1); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment4.getId().toString(), lastUpdatedTime1); Assert.assertEquals( 2, @@ -708,8 +643,9 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT ) ); - final DateTime maxUsedStatusLastUpdatedTime2 = DateTimes.nowUtc(); - + final DateTime lastUpdatedTime2 = DateTimes.nowUtc(); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment2.getId().toString(), lastUpdatedTime2); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment3.getId().toString(), lastUpdatedTime2); final List segmentIntervals = segments.stream() .map(DataSegment::getInterval) @@ -717,57 +653,53 @@ public void testKillMultipleUnusedSegmentsWithDifferentMaxUsedStatusLastUpdatedT final Interval umbrellaInterval = JodaUtils.umbrellaInterval(segmentIntervals); - - final KillUnusedSegmentsTask task1 = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - umbrellaInterval, - null, - null, - false, - 1, - 10, - maxUsedStatusLastUpdatedTime1 - ); + final KillUnusedSegmentsTask task1 = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .batchSize(1) + .limit(10) + .maxUsedStatusLastUpdatedTime(lastUpdatedTime1) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task1).get().getStatusCode()); - final List unusedSegments = + final List observedUnusedSegments1 = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, umbrellaInterval, null, null - ); + ); + + Assert.assertEquals(ImmutableList.of(segment2, segment3), observedUnusedSegments1); + Assert.assertEquals( + new KillTaskReport.Stats(2, 3, 0), + getReportedStats() + ); - Assert.assertEquals(ImmutableList.of(segment2, segment3), unusedSegments); - Assert.assertEquals(new KillTaskReport.Stats(2, 3, 0), getReportedStats()); + final KillUnusedSegmentsTask task2 = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .batchSize(1) + .limit(10) + .maxUsedStatusLastUpdatedTime(lastUpdatedTime2) + .build(); - final KillUnusedSegmentsTask task2 = - new KillUnusedSegmentsTask( - null, + Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task2).get().getStatusCode()); + + final List observedUnusedSegments2 = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( DATA_SOURCE, umbrellaInterval, null, - null, - false, - 1, - 10, - maxUsedStatusLastUpdatedTime2 + null ); - Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task2).get().getStatusCode()); - - final List unusedSegments2 = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - umbrellaInterval, - null, - null + Assert.assertEquals(ImmutableList.of(), observedUnusedSegments2); + Assert.assertEquals( + new KillTaskReport.Stats(2, 3, 0), + getReportedStats() ); - - Assert.assertEquals(ImmutableList.of(), unusedSegments2); - Assert.assertEquals(new KillTaskReport.Stats(2, 3, 0), getReportedStats()); } @Test @@ -790,11 +722,10 @@ public void testKillMultipleUnusedSegmentsWithVersionAndDifferentLastUpdatedTime ) ); - // Capture the last updated time cutoff - final DateTime maxUsedStatusLastUpdatedTime1 = DateTimes.nowUtc(); - - // Delay for 1s, mark the segments as unused and then capture the last updated time cutoff again - Thread.sleep(1000); + final DateTime lastUpdatedTime1 = DateTimes.nowUtc(); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment1.getId().toString(), lastUpdatedTime1); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment2.getId().toString(), lastUpdatedTime1); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment4.getId().toString(), lastUpdatedTime1); Assert.assertEquals( 2, @@ -802,7 +733,9 @@ public void testKillMultipleUnusedSegmentsWithVersionAndDifferentLastUpdatedTime ImmutableSet.of(segment3.getId(), segment5.getId()) ) ); - final DateTime maxUsedStatusLastUpdatedTime2 = DateTimes.nowUtc(); + + final DateTime lastUpdatedTime2 = DateTimes.nowUtc(); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment4.getId().toString(), lastUpdatedTime2); final List segmentIntervals = segments.stream() .map(DataSegment::getInterval) @@ -810,18 +743,14 @@ public void testKillMultipleUnusedSegmentsWithVersionAndDifferentLastUpdatedTime final Interval umbrellaInterval = JodaUtils.umbrellaInterval(segmentIntervals); - final KillUnusedSegmentsTask task1 = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - umbrellaInterval, - ImmutableList.of(version.toString()), - null, - false, - 1, - 10, - maxUsedStatusLastUpdatedTime1 - ); + final KillUnusedSegmentsTask task1 = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .versions(ImmutableList.of(version.toString())) + .batchSize(1) + .limit(10) + .maxUsedStatusLastUpdatedTime(lastUpdatedTime1) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task1).get().getStatusCode()); Assert.assertEquals( @@ -839,18 +768,14 @@ public void testKillMultipleUnusedSegmentsWithVersionAndDifferentLastUpdatedTime Assert.assertEquals(ImmutableSet.of(segment3, segment4, segment5), new HashSet<>(observedUnusedSegments)); - final KillUnusedSegmentsTask task2 = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - umbrellaInterval, - ImmutableList.of(version.toString()), - null, - false, - 1, - 10, - maxUsedStatusLastUpdatedTime2 - ); + final KillUnusedSegmentsTask task2 = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(umbrellaInterval) + .versions(ImmutableList.of(version.toString())) + .batchSize(1) + .limit(10) + .maxUsedStatusLastUpdatedTime(lastUpdatedTime2) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task2).get().getStatusCode()); Assert.assertEquals( @@ -872,151 +797,99 @@ public void testKillMultipleUnusedSegmentsWithVersionAndDifferentLastUpdatedTime @Test public void testKillBatchSizeThree() throws Exception { - final String version = DateTimes.nowUtc().toString(); - final Set segments = ImmutableSet.of( - newSegment(Intervals.of("2019-01-01/2019-02-01"), version), - newSegment(Intervals.of("2019-02-01/2019-03-01"), version), - newSegment(Intervals.of("2019-03-01/2019-04-01"), version), - newSegment(Intervals.of("2019-04-01/2019-05-01"), version) - ); + final Set segments = ImmutableSet.of(segment1, segment2, segment3, segment4); final Set announced = getMetadataStorageCoordinator().commitSegments(segments); - Assert.assertEquals(segments, announced); - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - true, - 3, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .markAsUnused(true) + .batchSize(3) + .build(); Assert.assertEquals(TaskState.SUCCESS, taskRunner.run(task).get().getStatusCode()); - // we expect ALL tasks to be deleted + final List observedUnusedSegments = + getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( + DATA_SOURCE, + Intervals.of("2019/2020"), + null, + null + ); - final List unusedSegments = getMetadataStorageCoordinator().retrieveUnusedSegmentsForInterval( - DATA_SOURCE, - Intervals.of("2019/2020"), - null, - null + Assert.assertEquals(Collections.emptyList(), observedUnusedSegments); + Assert.assertEquals( + new KillTaskReport.Stats(4, 3, 4), + getReportedStats() ); - - Assert.assertEquals(Collections.emptyList(), unusedSegments); - - Assert.assertEquals(new KillTaskReport.Stats(4, 3, 4), getReportedStats()); } @Test public void testComputeNextBatchSizeDefault() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - null, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .build(); Assert.assertEquals(100, task.computeNextBatchSize(50)); } @Test public void testComputeNextBatchSizeWithBatchSizeLargerThanLimit() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 10, - 5, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .batchSize(10) + .limit(5) + .build(); Assert.assertEquals(5, task.computeNextBatchSize(0)); } @Test public void testComputeNextBatchSizeWithBatchSizeSmallerThanLimit() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 5, - 10, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .batchSize(5) + .limit(10) + .build(); Assert.assertEquals(5, task.computeNextBatchSize(0)); } @Test public void testComputeNextBatchSizeWithRemainingLessThanLimit() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 5, - 10, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .batchSize(5) + .limit(10) + .build(); Assert.assertEquals(3, task.computeNextBatchSize(7)); } @Test public void testGetNumTotalBatchesDefault() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - null, - null, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .build(); Assert.assertNull(task.getNumTotalBatches()); } @Test public void testGetNumTotalBatchesWithBatchSizeLargerThanLimit() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 10, - 5, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .batchSize(10) + .limit(5) + .build(); Assert.assertEquals(1, (int) task.getNumTotalBatches()); } @@ -1026,17 +899,11 @@ public void testInvalidLimit() MatcherAssert.assertThat( Assert.assertThrows( DruidException.class, - () -> new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 10, - 0, - null - ) + () -> new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .limit(0) + .build() ), DruidExceptionMatcher.invalidInput().expectMessageIs( "limit[0] must be a positive integer." @@ -1050,17 +917,11 @@ public void testInvalidBatchSize() MatcherAssert.assertThat( Assert.assertThrows( DruidException.class, - () -> new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 0, - 10, - null - ) + () -> new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .batchSize(0) + .build() ), DruidExceptionMatcher.invalidInput().expectMessageIs( "batchSize[0] must be a positive integer." @@ -1074,17 +935,13 @@ public void testInvalidLimitWithMarkAsUnused() MatcherAssert.assertThat( Assert.assertThrows( DruidException.class, - () -> new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - true, - 10, - 10, - null - ) + () -> new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .markAsUnused(true) + .batchSize(10) + .limit(10) + .build() ), DruidExceptionMatcher.invalidInput().expectMessageIs( "limit[10] cannot be provided when markAsUnused is enabled." @@ -1093,22 +950,17 @@ public void testInvalidLimitWithMarkAsUnused() } @Test - public void testInvalidVersionWithMarkAsUnused() + public void testInvalidVersionsWithMarkAsUnused() { MatcherAssert.assertThat( Assert.assertThrows( DruidException.class, - () -> new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - ImmutableList.of("foo"), - null, - true, - 10, - null, - null - ) + () -> new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .markAsUnused(true) + .versions(ImmutableList.of("foo")) + .build() ), DruidExceptionMatcher.invalidInput().expectMessageIs( "versions[[foo]] cannot be provided when markAsUnused is enabled." @@ -1119,18 +971,13 @@ public void testInvalidVersionWithMarkAsUnused() @Test public void testGetNumTotalBatchesWithBatchSizeSmallerThanLimit() { - final KillUnusedSegmentsTask task = - new KillUnusedSegmentsTask( - null, - DATA_SOURCE, - Intervals.of("2018-01-01/2020-01-01"), - null, - null, - false, - 5, - 10, - null - ); + final KillUnusedSegmentsTask task = new KillUnusedSegmentsTaskBuilder() + .dataSource(DATA_SOURCE) + .interval(Intervals.of("2018-01-01/2020-01-01")) + .versions(ImmutableList.of("foo")) + .batchSize(5) + .limit(10) + .build(); Assert.assertEquals(2, (int) task.getNumTotalBatches()); } @@ -1152,6 +999,88 @@ public void testKillTaskReportSerde() throws Exception Assert.assertEquals(stats, deserializedKillReport.getPayload()); } + private static class KillUnusedSegmentsTaskBuilder + { + private String id; + private String dataSource; + private Interval interval; + private List versions; + private Map context; + private Boolean markAsUnused; + private Integer batchSize; + private Integer limit; + private DateTime maxUsedStatusLastUpdatedTime; + + public KillUnusedSegmentsTaskBuilder id(String id) + { + this.id = id; + return this; + } + + public KillUnusedSegmentsTaskBuilder dataSource(String dataSource) + { + this.dataSource = dataSource; + return this; + } + + public KillUnusedSegmentsTaskBuilder interval(Interval interval) + { + this.interval = interval; + return this; + } + + public KillUnusedSegmentsTaskBuilder versions(List versions) + { + this.versions = versions; + return this; + } + + public KillUnusedSegmentsTaskBuilder context(Map context) + { + this.context = context; + return this; + } + + public KillUnusedSegmentsTaskBuilder markAsUnused(Boolean markAsUnused) + { + this.markAsUnused = markAsUnused; + return this; + } + + public KillUnusedSegmentsTaskBuilder batchSize(Integer batchSize) + { + this.batchSize = batchSize; + return this; + } + + public KillUnusedSegmentsTaskBuilder limit(Integer limit) + { + this.limit = limit; + return this; + } + + public KillUnusedSegmentsTaskBuilder maxUsedStatusLastUpdatedTime(DateTime maxUsedStatusLastUpdatedTime) + { + this.maxUsedStatusLastUpdatedTime = maxUsedStatusLastUpdatedTime; + return this; + } + + public KillUnusedSegmentsTask build() + { + return new KillUnusedSegmentsTask( + id, + dataSource, + interval, + versions, + context, + markAsUnused, + batchSize, + limit, + maxUsedStatusLastUpdatedTime + ); + } + } + private KillTaskReport.Stats getReportedStats() { try { diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/RealtimeIndexTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/RealtimeIndexTaskTest.java index 2e3e7d5d8ce4..43253a10bccc 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/RealtimeIndexTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/RealtimeIndexTaskTest.java @@ -41,6 +41,7 @@ import org.apache.druid.discovery.DataNodeService; import org.apache.druid.discovery.DruidNodeAnnouncer; import org.apache.druid.discovery.LookupNodeService; +import org.apache.druid.error.DruidException; import org.apache.druid.indexer.TaskState; import org.apache.druid.indexer.TaskStatus; import org.apache.druid.indexing.common.SegmentCacheManagerFactory; @@ -80,7 +81,6 @@ import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.java.util.metrics.MonitorScheduler; import org.apache.druid.math.expr.ExprMacroTable; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.query.DefaultQueryRunnerFactoryConglomerate; import org.apache.druid.query.DirectQueryProcessingPool; import org.apache.druid.query.Druids; @@ -911,8 +911,8 @@ private TaskToolbox makeToolbox( try { taskStorage.insert(task, TaskStatus.running(task.getId())); } - catch (EntryExistsException e) { - // suppress + catch (DruidException e) { + log.noStackTrace().info(e, "Suppressing exception while inserting task [%s]", task.getId()); } taskLockbox.syncFromStorage(); final TaskActionToolbox taskActionToolbox = new TaskActionToolbox( diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java index f438b7bba844..6b662e473b05 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/AbstractParallelIndexSupervisorTaskTest.java @@ -83,7 +83,6 @@ import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.java.util.metrics.StubServiceEmitter; import org.apache.druid.math.expr.ExprMacroTable; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.LongSumAggregatorFactory; import org.apache.druid.query.expression.LookupEnabledTestExprMacroTable; @@ -451,12 +450,7 @@ private Future runTask(Task task) if (tasks.put(task.getId(), taskContainer) != null) { throw new ISE("Duplicate task ID[%s]", task.getId()); } - try { - prepareTaskForLocking(task); - } - catch (EntryExistsException e) { - throw new RuntimeException(e); - } + prepareTaskForLocking(task); task.addToContextIfAbsent( SinglePhaseParallelIndexTaskRunner.CTX_USE_LINEAGE_BASED_SEGMENT_ALLOCATION_KEY, SinglePhaseParallelIndexTaskRunner.DEFAULT_USE_LINEAGE_BASED_SEGMENT_ALLOCATION diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTaskTest.java index a3d62b0c10ae..a340655dfcc9 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexSupervisorTaskTest.java @@ -62,7 +62,6 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.internal.matchers.ThrowableMessageMatcher; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; @@ -83,7 +82,6 @@ import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.mock; -@RunWith(Enclosed.class) public class ParallelIndexSupervisorTaskTest { @RunWith(Parameterized.class) diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionCardinalityTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionCardinalityTaskTest.java index d1a143d056c9..c4ae9392450a 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionCardinalityTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionCardinalityTaskTest.java @@ -58,17 +58,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -@RunWith(Enclosed.class) public class PartialDimensionCardinalityTaskTest { private static final ObjectMapper OBJECT_MAPPER = ParallelIndexTestingFactory.createObjectMapper(); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionDistributionTaskTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionDistributionTaskTest.java index 8ed96d0ddb09..1346b29d889d 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionDistributionTaskTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/PartialDimensionDistributionTaskTest.java @@ -54,10 +54,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; @@ -68,7 +66,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -@RunWith(Enclosed.class) public class PartialDimensionDistributionTaskTest { private static final ObjectMapper OBJECT_MAPPER = ParallelIndexTestingFactory.createObjectMapper(); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/distribution/StringSketchTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/distribution/StringSketchTest.java index f31eeb830cc0..6f76d3296550 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/distribution/StringSketchTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/distribution/StringSketchTest.java @@ -33,9 +33,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; @@ -45,7 +43,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -@RunWith(Enclosed.class) public class StringSketchTest { private static final int FACTOR = 2; @@ -140,7 +137,6 @@ private long getCount() } } - @RunWith(Enclosed.class) public static class PartitionTest { private static final StringSketch SKETCH; diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/iterator/DefaultIndexTaskInputRowIteratorBuilderTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/iterator/DefaultIndexTaskInputRowIteratorBuilderTest.java index ddf0fbd5a887..31f310e77ba2 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/iterator/DefaultIndexTaskInputRowIteratorBuilderTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/common/task/batch/parallel/iterator/DefaultIndexTaskInputRowIteratorBuilderTest.java @@ -29,14 +29,11 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.util.Collections; import java.util.List; -@RunWith(Enclosed.class) public class DefaultIndexTaskInputRowIteratorBuilderTest { public static class BuildTest diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockBoxConcurrencyTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockBoxConcurrencyTest.java index 8e809169b30d..19af66254ba8 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockBoxConcurrencyTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockBoxConcurrencyTest.java @@ -31,7 +31,6 @@ import org.apache.druid.java.util.common.Intervals; import org.apache.druid.java.util.common.concurrent.Execs; import org.apache.druid.metadata.DerbyMetadataStorageActionHandlerFactory; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.IndexerSQLMetadataStorageCoordinator; import org.apache.druid.metadata.TestDerbyConnector; import org.joda.time.Interval; @@ -100,7 +99,7 @@ private LockResult acquireTimeChunkLock(TaskLockType lockType, Task task, Interv @Test(timeout = 60_000L) public void testDoInCriticalSectionWithDifferentTasks() - throws ExecutionException, InterruptedException, EntryExistsException + throws ExecutionException, InterruptedException { final Interval interval = Intervals.of("2017-01-01/2017-01-02"); final Task lowPriorityTask = NoopTask.ofPriority(10); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockConfigTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockConfigTest.java index 364b492148de..7d2fbcd79230 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockConfigTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockConfigTest.java @@ -31,7 +31,6 @@ import org.apache.druid.indexing.overlord.config.TaskQueueConfig; import org.apache.druid.indexing.test.TestIndexerMetadataStorageCoordinator; import org.apache.druid.java.util.emitter.service.ServiceEmitter; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.server.metrics.NoopServiceEmitter; import org.easymock.EasyMock; import org.junit.Assert; @@ -51,7 +50,7 @@ public void setup() } @Test - public void testDefault() throws EntryExistsException + public void testDefault() { final TaskQueue taskQueue = createTaskQueue(null); taskQueue.start(); @@ -65,7 +64,7 @@ public void testDefault() throws EntryExistsException } @Test - public void testNotForceTimeChunkLock() throws EntryExistsException + public void testNotForceTimeChunkLock() { final TaskQueue taskQueue = createTaskQueue(false); taskQueue.start(); @@ -79,7 +78,7 @@ public void testNotForceTimeChunkLock() throws EntryExistsException } @Test - public void testOverwriteDefault() throws EntryExistsException + public void testOverwriteDefault() { final TaskQueue taskQueue = createTaskQueue(null); taskQueue.start(); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockboxTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockboxTest.java index c4ee78ea6a89..307879fed8bd 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockboxTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskLockboxTest.java @@ -52,7 +52,6 @@ import org.apache.druid.java.util.emitter.EmittingLogger; import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.metadata.DerbyMetadataStorageActionHandlerFactory; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.IndexerSQLMetadataStorageCoordinator; import org.apache.druid.metadata.LockFilterPolicy; import org.apache.druid.metadata.MetadataStorageTablesConfig; @@ -228,7 +227,7 @@ public void testTrySharedLock() } @Test - public void testTryMixedLocks() throws EntryExistsException + public void testTryMixedLocks() { final Task lowPriorityTask = NoopTask.ofPriority(0); final Task lowPriorityTask2 = NoopTask.ofPriority(0); @@ -320,7 +319,7 @@ public void testTimeoutForLock() throws InterruptedException } @Test - public void testSyncFromStorage() throws EntryExistsException + public void testSyncFromStorage() { final TaskLockbox originalBox = new TaskLockbox(taskStorage, metadataStorageCoordinator); for (int i = 0; i < 5; i++) { @@ -358,7 +357,7 @@ public void testSyncFromStorage() throws EntryExistsException } @Test - public void testSyncFromStorageWithMissingTaskLockPriority() throws EntryExistsException + public void testSyncFromStorageWithMissingTaskLockPriority() { final Task task = NoopTask.create(); taskStorage.insert(task, TaskStatus.running(task.getId())); @@ -382,7 +381,7 @@ public void testSyncFromStorageWithMissingTaskLockPriority() throws EntryExistsE } @Test - public void testSyncFromStorageWithMissingTaskPriority() throws EntryExistsException + public void testSyncFromStorageWithMissingTaskPriority() { final Task task = NoopTask.create(); taskStorage.insert(task, TaskStatus.running(task.getId())); @@ -413,7 +412,7 @@ public void testSyncFromStorageWithMissingTaskPriority() throws EntryExistsExcep } @Test - public void testSyncFromStorageWithInvalidPriority() throws EntryExistsException + public void testSyncFromStorageWithInvalidPriority() { final Task task = NoopTask.create(); taskStorage.insert(task, TaskStatus.running(task.getId())); @@ -481,7 +480,7 @@ public void testSyncWithUnknownTaskTypesFromModuleNotLoaded() } @Test - public void testRevokedLockSyncFromStorage() throws EntryExistsException + public void testRevokedLockSyncFromStorage() { final TaskLockbox originalBox = new TaskLockbox(taskStorage, metadataStorageCoordinator); @@ -628,7 +627,7 @@ public void testDoInCriticalSectionWithRevokedLock() throws Exception } @Test(timeout = 60_000L) - public void testAcquireLockAfterRevoked() throws EntryExistsException, InterruptedException + public void testAcquireLockAfterRevoked() throws InterruptedException { final Interval interval = Intervals.of("2017-01-01/2017-01-02"); final Task lowPriorityTask = NoopTask.ofPriority(0); @@ -653,7 +652,7 @@ public void testAcquireLockAfterRevoked() throws EntryExistsException, Interrupt } @Test - public void testUnlock() throws EntryExistsException + public void testUnlock() { final List lowPriorityTasks = new ArrayList<>(); final List highPriorityTasks = new ArrayList<>(); @@ -718,7 +717,7 @@ public void testUnlock() throws EntryExistsException } @Test - public void testFindLockPosseAfterRevokeWithDifferentLockIntervals() throws EntryExistsException + public void testFindLockPosseAfterRevokeWithDifferentLockIntervals() { final Task lowPriorityTask = NoopTask.ofPriority(0); final Task highPriorityTask = NoopTask.ofPriority(10); @@ -828,7 +827,7 @@ public void testSegmentAndTimeChunkLockForSameInterval() } @Test - public void testSegmentAndTimeChunkLockForSameIntervalWithDifferentPriority() throws EntryExistsException + public void testSegmentAndTimeChunkLockForSameIntervalWithDifferentPriority() { final Task task1 = NoopTask.ofPriority(10); lockbox.add(task1); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskQueueTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskQueueTest.java index 8ca1ff49f057..8ee341c5db16 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskQueueTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/overlord/TaskQueueTest.java @@ -64,7 +64,6 @@ import org.apache.druid.java.util.common.granularity.Granularity; import org.apache.druid.java.util.http.client.HttpClient; import org.apache.druid.java.util.metrics.StubServiceEmitter; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.segment.TestHelper; import org.apache.druid.server.coordinator.stats.CoordinatorRunStats; import org.apache.druid.server.initialization.IndexerZkConfig; @@ -209,7 +208,7 @@ public void testTaskErrorWhenExceptionIsThrownDueToQueueSize() } @Test - public void testSetUseLineageBasedSegmentAllocationByDefault() throws EntryExistsException + public void testSetUseLineageBasedSegmentAllocationByDefault() { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final TaskQueue taskQueue = new TaskQueue( @@ -234,7 +233,7 @@ public void testSetUseLineageBasedSegmentAllocationByDefault() throws EntryExist } @Test - public void testDefaultTaskContextOverrideDefaultLineageBasedSegmentAllocation() throws EntryExistsException + public void testDefaultTaskContextOverrideDefaultLineageBasedSegmentAllocation() { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final TaskQueue taskQueue = new TaskQueue( @@ -269,7 +268,7 @@ public Map getContext() } @Test - public void testUserProvidedTaskContextOverrideDefaultLineageBasedSegmentAllocation() throws EntryExistsException + public void testUserProvidedTaskContextOverrideDefaultLineageBasedSegmentAllocation() { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final TaskQueue taskQueue = new TaskQueue( @@ -301,7 +300,7 @@ public void testUserProvidedTaskContextOverrideDefaultLineageBasedSegmentAllocat } @Test - public void testLockConfigTakePrecedenceThanDefaultTaskContext() throws EntryExistsException + public void testLockConfigTakePrecedenceThanDefaultTaskContext() { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final TaskQueue taskQueue = new TaskQueue( @@ -334,7 +333,7 @@ public Map getContext() } @Test - public void testUserProvidedContextOverrideLockConfig() throws EntryExistsException + public void testUserProvidedContextOverrideLockConfig() { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final TaskQueue taskQueue = new TaskQueue( @@ -364,7 +363,7 @@ public void testUserProvidedContextOverrideLockConfig() throws EntryExistsExcept } @Test - public void testTaskStatusWhenExceptionIsThrownInIsReady() throws EntryExistsException + public void testTaskStatusWhenExceptionIsThrownInIsReady() { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final TaskQueue taskQueue = new TaskQueue( @@ -400,7 +399,7 @@ public boolean isReady(TaskActionClient taskActionClient) } @Test - public void testKilledTasksEmitRuntimeMetricWithHttpRemote() throws EntryExistsException, InterruptedException + public void testKilledTasksEmitRuntimeMetricWithHttpRemote() throws InterruptedException { final TaskActionClientFactory actionClientFactory = createActionClientFactory(); final HttpRemoteTaskRunner taskRunner = createHttpRemoteTaskRunner(ImmutableList.of("t1")); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java b/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java index de854d1be941..2db5da143edd 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskTestBase.java @@ -50,6 +50,7 @@ import org.apache.druid.discovery.DataNodeService; import org.apache.druid.discovery.DruidNodeAnnouncer; import org.apache.druid.discovery.LookupNodeService; +import org.apache.druid.error.DruidException; import org.apache.druid.indexer.TaskStatus; import org.apache.druid.indexing.common.IngestionStatsAndErrorsTaskReportData; import org.apache.druid.indexing.common.LockGranularity; @@ -88,7 +89,6 @@ import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.java.util.metrics.MonitorScheduler; import org.apache.druid.metadata.DerbyMetadataStorageActionHandlerFactory; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.metadata.IndexerSQLMetadataStorageCoordinator; import org.apache.druid.metadata.TestDerbyConnector; import org.apache.druid.query.DirectQueryProcessingPool; @@ -154,6 +154,8 @@ public abstract class SeekableStreamIndexTaskTestBase extends EasyMockSupport { + private static final Logger log = new Logger(SeekableStreamIndexTaskTestBase.class); + @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); @@ -475,8 +477,8 @@ protected ListenableFuture runTask(final Task task) try { taskStorage.insert(task, TaskStatus.running(task.getId())); } - catch (EntryExistsException e) { - // suppress + catch (DruidException e) { + log.noStackTrace().info(e, "Suppressing exception while inserting task [%s]", task.getId()); } taskLockbox.syncFromStorage(); final TaskToolbox toolbox = toolboxFactory.build(task); diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisorStateTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisorStateTest.java index ea083423eaa3..2602f8e5441f 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisorStateTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisorStateTest.java @@ -79,7 +79,6 @@ import org.apache.druid.java.util.common.parsers.JSONPathSpec; import org.apache.druid.java.util.metrics.DruidMonitorSchedulerConfig; import org.apache.druid.java.util.metrics.StubServiceEmitter; -import org.apache.druid.metadata.EntryExistsException; import org.apache.druid.query.DruidMetrics; import org.apache.druid.query.aggregation.AggregatorFactory; import org.apache.druid.query.aggregation.CountAggregatorFactory; @@ -2074,7 +2073,7 @@ public LagStats computeLagStats() EasyMock.verify(executorService, spec); } - private void expectEmitterSupervisor(boolean suspended) throws EntryExistsException + private void expectEmitterSupervisor(boolean suspended) { spec = createMock(SeekableStreamSupervisorSpec.class); EasyMock.expect(spec.getSupervisorStateManagerConfig()).andReturn(supervisorConfig).anyTimes(); diff --git a/integration-tests-ex/cases/pom.xml b/integration-tests-ex/cases/pom.xml index dc871bec097b..eb4822c425df 100644 --- a/integration-tests-ex/cases/pom.xml +++ b/integration-tests-ex/cases/pom.xml @@ -530,5 +530,14 @@ + + IT-Security + + false + + + Security + + diff --git a/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/auth/ITSecurityBasicQuery.java b/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/auth/ITSecurityBasicQuery.java index 9d76af07de8e..181e8e92d1fd 100644 --- a/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/auth/ITSecurityBasicQuery.java +++ b/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/auth/ITSecurityBasicQuery.java @@ -22,8 +22,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; +import org.apache.druid.common.utils.IdUtils; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.http.client.response.StatusResponseHolder; +import org.apache.druid.msq.indexing.MSQControllerTask; import org.apache.druid.server.security.Action; import org.apache.druid.server.security.Resource; import org.apache.druid.server.security.ResourceAction; @@ -31,9 +33,11 @@ import org.apache.druid.storage.local.LocalFileExportStorageProvider; import org.apache.druid.storage.s3.output.S3ExportStorageProvider; import org.apache.druid.testing.clients.CoordinatorResourceTestClient; +import org.apache.druid.testing.clients.OverlordResourceTestClient; import org.apache.druid.testing.clients.SecurityClient; import org.apache.druid.testing.utils.DataLoaderHelper; import org.apache.druid.testing.utils.MsqTestQueryHelper; +import org.apache.druid.tests.indexer.AbstractIndexerTest; import org.apache.druid.testsEx.categories.Security; import org.apache.druid.testsEx.config.DruidTestRunner; import org.jboss.netty.handler.codec.http.HttpResponseStatus; @@ -62,10 +66,13 @@ public class ITSecurityBasicQuery private CoordinatorResourceTestClient coordinatorClient; @Inject private SecurityClient securityClient; + @Inject + private OverlordResourceTestClient overlordResourceTestClient; public static final String USER_1 = "user1"; public static final String ROLE_1 = "role1"; public static final String USER_1_PASSWORD = "password1"; + private static final String EXPORT_TASK = "/indexer/export_task.json"; @Before public void setUp() throws IOException @@ -154,6 +161,9 @@ public void testIngestionWithPermissions() throws Exception ); securityClient.setPermissionsToRole(ROLE_1, permissions); + // Wait for a second so that the auth is synced, to avoid flakiness + Thread.sleep(1000); + String queryLocal = StringUtils.format( "INSERT INTO %s\n" @@ -216,6 +226,9 @@ public void testExportWithoutPermissions() throws IOException, ExecutionExceptio ); securityClient.setPermissionsToRole(ROLE_1, permissions); + // Wait for a second so that the auth is synced, to avoid flakiness + Thread.sleep(4000); + String exportQuery = StringUtils.format( "INSERT INTO extern(%s(exportPath => '%s'))\n" @@ -253,6 +266,9 @@ public void testExportWithPermissions() throws IOException, ExecutionException, ); securityClient.setPermissionsToRole(ROLE_1, permissions); + // Wait for a second so that the auth is synced, to avoid flakyness + Thread.sleep(1000); + String exportQuery = StringUtils.format( "INSERT INTO extern(%s(exportPath => '%s'))\n" @@ -276,4 +292,53 @@ public void testExportWithPermissions() throws IOException, ExecutionException, Assert.assertEquals(HttpResponseStatus.ACCEPTED, statusResponseHolder.getStatus()); } + + @Test + public void testExportTaskSubmitOverlordWithPermission() throws Exception + { + // No external write permissions for s3 + List permissions = ImmutableList.of( + new ResourceAction(new Resource(".*", "DATASOURCE"), Action.READ), + new ResourceAction(new Resource("EXTERNAL", "EXTERNAL"), Action.READ), + new ResourceAction(new Resource(LocalFileExportStorageProvider.TYPE_NAME, "EXTERNAL"), Action.WRITE), + new ResourceAction(new Resource("STATE", "STATE"), Action.READ), + new ResourceAction(new Resource(".*", "DATASOURCE"), Action.WRITE) + ); + securityClient.setPermissionsToRole(ROLE_1, permissions); + + // Wait for a second so that the auth is synced, to avoid flakiness + Thread.sleep(1000); + + String task = createTaskString(); + StatusResponseHolder statusResponseHolder = overlordResourceTestClient.submitTaskAndReturnStatusWithAuth(task, USER_1, USER_1_PASSWORD); + Assert.assertEquals(HttpResponseStatus.OK, statusResponseHolder.getStatus()); + } + + @Test + public void testExportTaskSubmitOverlordWithoutPermission() throws Exception + { + // No external write permissions for s3 + List permissions = ImmutableList.of( + new ResourceAction(new Resource(".*", "DATASOURCE"), Action.READ), + new ResourceAction(new Resource("EXTERNAL", "EXTERNAL"), Action.READ), + new ResourceAction(new Resource(S3ExportStorageProvider.TYPE_NAME, "EXTERNAL"), Action.WRITE), + new ResourceAction(new Resource("STATE", "STATE"), Action.READ), + new ResourceAction(new Resource(".*", "DATASOURCE"), Action.WRITE) + ); + securityClient.setPermissionsToRole(ROLE_1, permissions); + + // Wait for a second so that the auth is synced, to avoid flakiness + Thread.sleep(1000); + + String task = createTaskString(); + StatusResponseHolder statusResponseHolder = overlordResourceTestClient.submitTaskAndReturnStatusWithAuth(task, USER_1, USER_1_PASSWORD); + Assert.assertEquals(HttpResponseStatus.FORBIDDEN, statusResponseHolder.getStatus()); + } + + private String createTaskString() throws Exception + { + String queryId = IdUtils.newTaskId(MSQControllerTask.TYPE, "external", null); + String template = AbstractIndexerTest.getResourceAsString(EXPORT_TASK); + return StringUtils.replace(template, "%%QUERY_ID%%", queryId); + } } diff --git a/integration-tests-ex/cases/src/test/resources/indexer/export_task.json b/integration-tests-ex/cases/src/test/resources/indexer/export_task.json new file mode 100644 index 000000000000..e5bfdac4af71 --- /dev/null +++ b/integration-tests-ex/cases/src/test/resources/indexer/export_task.json @@ -0,0 +1,224 @@ +{ + "type": "query_controller", + "id": "%%QUERY_ID%%", + "spec": { + "query": { + "queryType": "scan", + "dataSource": { + "type": "external", + "inputSource": { + "type": "local", + "files": [ + "/resources/data/batch_index/json/wikipedia_index_data1.json" + ] + }, + "inputFormat": { + "type": "json", + "keepNullColumns": false, + "assumeNewlineDelimited": false, + "useJsonNodeReader": false + }, + "signature": [ + { + "name": "timestamp", + "type": "STRING" + }, + { + "name": "isRobot", + "type": "STRING" + }, + { + "name": "diffUrl", + "type": "STRING" + }, + { + "name": "added", + "type": "LONG" + }, + { + "name": "countryIsoCode", + "type": "STRING" + }, + { + "name": "regionName", + "type": "STRING" + }, + { + "name": "channel", + "type": "STRING" + }, + { + "name": "flags", + "type": "STRING" + }, + { + "name": "delta", + "type": "LONG" + }, + { + "name": "isUnpatrolled", + "type": "STRING" + }, + { + "name": "isNew", + "type": "STRING" + }, + { + "name": "deltaBucket", + "type": "DOUBLE" + }, + { + "name": "isMinor", + "type": "STRING" + }, + { + "name": "isAnonymous", + "type": "STRING" + }, + { + "name": "deleted", + "type": "LONG" + }, + { + "name": "cityName", + "type": "STRING" + }, + { + "name": "metroCode", + "type": "LONG" + }, + { + "name": "namespace", + "type": "STRING" + }, + { + "name": "comment", + "type": "STRING" + }, + { + "name": "page", + "type": "STRING" + }, + { + "name": "commentLength", + "type": "LONG" + }, + { + "name": "countryName", + "type": "STRING" + }, + { + "name": "user", + "type": "STRING" + }, + { + "name": "regionIsoCode", + "type": "STRING" + } + ] + }, + "intervals": { + "type": "intervals", + "intervals": [ + "-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z" + ] + }, + "resultFormat": "compactedList", + "columns": [ + "added", + "delta", + "page" + ], + "legacy": false, + "context": { + "__exportFileFormat": "CSV", + "__resultFormat": "array", + "__user": "allowAll", + "executionMode": "async", + "finalize": false, + "finalizeAggregations": false, + "groupByEnableMultiValueUnnesting": false, + "maxNumTasks": 4, + "maxParseExceptions": 0, + "queryId": "b1491ce2-7d2a-4a7a-baa6-25a1a77135e5", + "scanSignature": "[{\"name\":\"added\",\"type\":\"LONG\"},{\"name\":\"delta\",\"type\":\"LONG\"},{\"name\":\"page\",\"type\":\"STRING\"}]", + "sqlQueryId": "b1491ce2-7d2a-4a7a-baa6-25a1a77135e5", + "sqlStringifyArrays": false, + "waitUntilSegmentsLoad": true + }, + "columnTypes": [ + "LONG", + "LONG", + "STRING" + ], + "granularity": { + "type": "all" + } + }, + "columnMappings": [ + { + "queryColumn": "page", + "outputColumn": "page" + }, + { + "queryColumn": "added", + "outputColumn": "added" + }, + { + "queryColumn": "delta", + "outputColumn": "delta" + } + ], + "destination": { + "type": "export", + "exportStorageProvider": { + "type": "local", + "exportPath": "/shared/export/" + }, + "resultFormat": "csv" + }, + "assignmentStrategy": "max", + "tuningConfig": { + "maxNumWorkers": 3, + "maxRowsInMemory": 100000, + "rowsPerSegment": 3000000 + } + }, + "sqlQuery": " INSERT INTO extern(local(exportPath => '/shared/export/'))\n AS CSV\n SELECT page, added, delta\n FROM TABLE(\n EXTERN(\n '{\"type\":\"local\",\"files\":[\"/resources/data/batch_index/json/wikipedia_index_data1.json\"]}',\n '{\"type\":\"json\"}',\n '[{\"type\":\"string\",\"name\":\"timestamp\"},{\"type\":\"string\",\"name\":\"isRobot\"},{\"type\":\"string\",\"name\":\"diffUrl\"},{\"type\":\"long\",\"name\":\"added\"},{\"type\":\"string\",\"name\":\"countryIsoCode\"},{\"type\":\"string\",\"name\":\"regionName\"},{\"type\":\"string\",\"name\":\"channel\"},{\"type\":\"string\",\"name\":\"flags\"},{\"type\":\"long\",\"name\":\"delta\"},{\"type\":\"string\",\"name\":\"isUnpatrolled\"},{\"type\":\"string\",\"name\":\"isNew\"},{\"type\":\"double\",\"name\":\"deltaBucket\"},{\"type\":\"string\",\"name\":\"isMinor\"},{\"type\":\"string\",\"name\":\"isAnonymous\"},{\"type\":\"long\",\"name\":\"deleted\"},{\"type\":\"string\",\"name\":\"cityName\"},{\"type\":\"long\",\"name\":\"metroCode\"},{\"type\":\"string\",\"name\":\"namespace\"},{\"type\":\"string\",\"name\":\"comment\"},{\"type\":\"string\",\"name\":\"page\"},{\"type\":\"long\",\"name\":\"commentLength\"},{\"type\":\"string\",\"name\":\"countryName\"},{\"type\":\"string\",\"name\":\"user\"},{\"type\":\"string\",\"name\":\"regionIsoCode\"}]'\n )\n )\n", + "sqlQueryContext": { + "__exportFileFormat": "CSV", + "finalizeAggregations": false, + "sqlQueryId": "b1491ce2-7d2a-4a7a-baa6-25a1a77135e5", + "groupByEnableMultiValueUnnesting": false, + "maxNumTasks": 4, + "waitUntilSegmentsLoad": true, + "executionMode": "async", + "__resultFormat": "array", + "sqlStringifyArrays": false, + "queryId": "b1491ce2-7d2a-4a7a-baa6-25a1a77135e5" + }, + "sqlResultsContext": { + "timeZone": "UTC", + "serializeComplexValues": true, + "stringifyArrays": false + }, + "sqlTypeNames": [ + "VARCHAR", + "BIGINT", + "BIGINT" + ], + "nativeTypeNames": [ + "STRING", + "LONG", + "LONG" + ], + "context": { + "forceTimeChunkLock": true + }, + "groupId": "%%QUERY_ID%%", + "dataSource": "__query_select", + "resource": { + "availabilityGroup": "%%QUERY_ID%%", + "requiredCapacity": 1 + } +} diff --git a/integration-tests/src/main/java/org/apache/druid/testing/clients/OverlordResourceTestClient.java b/integration-tests/src/main/java/org/apache/druid/testing/clients/OverlordResourceTestClient.java index 95f56ad4b759..ac4ef536b026 100644 --- a/integration-tests/src/main/java/org/apache/druid/testing/clients/OverlordResourceTestClient.java +++ b/integration-tests/src/main/java/org/apache/druid/testing/clients/OverlordResourceTestClient.java @@ -116,6 +116,22 @@ public String submitTask(final String task) } } + public StatusResponseHolder submitTaskAndReturnStatusWithAuth( + final String task, + final String username, + final String password + ) throws Exception + { + return httpClient.go( + new Request(HttpMethod.POST, new URL(getIndexerURL() + "task")) + .setContent( + "application/json", + StringUtils.toUtf8(task) + ).setBasicAuthentication(username, password), + StatusResponseHandler.getInstance() + ).get(); + } + public TaskStatusPlus getTaskStatus(String taskID) { try { diff --git a/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java b/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java index ea8e61ef3177..a287843a8f77 100644 --- a/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/druid/tests/security/ITBasicAuthConfigurationTest.java @@ -85,7 +85,7 @@ protected void setupHttpClientsAndUsers() throws Exception // Add a large enough delay to allow propagation of credentials to all services. It'd be ideal // to have a "readiness" endpoint exposed by different services that'd return the version of auth creds cached. try { - Thread.sleep(10000); + Thread.sleep(20000); } catch (InterruptedException e) { // Ignore exception diff --git a/pom.xml b/pom.xml index 7c60fb85b7f6..627ed64b399a 100644 --- a/pom.xml +++ b/pom.xml @@ -1056,7 +1056,7 @@ org.junit junit-bom - 5.10.0 + 5.10.2 pom import @@ -1347,6 +1347,32 @@ + + org.openrewrite.maven + rewrite-maven-plugin + 5.23.1 + + + org.apache.druid.RewriteRules + + ${maven.multiModuleProjectDirectory}/rewrite.yml + false + true + false + true + + **/*.json + **/*.yaml + + + + + org.openrewrite.recipe + rewrite-testing-frameworks + 2.4.1 + + + org.jacoco jacoco-maven-plugin @@ -1573,6 +1599,7 @@ + 3.0.0 @@ -1709,7 +1736,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.2.5 @@ -1741,6 +1768,11 @@ true + + + + diff --git a/processing/src/main/java/org/apache/druid/common/exception/DruidException.java b/processing/src/main/java/org/apache/druid/common/exception/DruidException.java deleted file mode 100644 index 42a679b6bd2f..000000000000 --- a/processing/src/main/java/org/apache/druid/common/exception/DruidException.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.common.exception; - -/** - * A generic exception thrown by Druid. - * - * This class is deprecated and should not be used. {@link org.apache.druid.error.DruidException} should be used for - * any error that is intended to be delivered to the end user. - */ -@Deprecated -public class DruidException extends RuntimeException -{ - public static final int HTTP_CODE_SERVER_ERROR = 500; - public static final int HTTP_CODE_BAD_REQUEST = 400; - - private final int responseCode; - private final boolean isTransient; - - public DruidException(String message, int responseCode, Throwable cause, boolean isTransient) - { - super(message, cause); - this.responseCode = responseCode; - this.isTransient = isTransient; - } - - public int getResponseCode() - { - return responseCode; - } - - /** - * Returns true if this is a transient exception and might go away if the - * operation is retried. - */ - public boolean isTransient() - { - return isTransient; - } -} diff --git a/processing/src/main/java/org/apache/druid/error/BaseFailure.java b/processing/src/main/java/org/apache/druid/error/BaseFailure.java new file mode 100644 index 000000000000..a18186ef93f5 --- /dev/null +++ b/processing/src/main/java/org/apache/druid/error/BaseFailure.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +package org.apache.druid.error; + +/** + * {@link DruidException.Failure} for a specific errorCode, category and persona. + */ +public abstract class BaseFailure extends DruidException.Failure +{ + private final Throwable t; + private final String msg; + private final Object[] args; + private final DruidException.Persona persona; + private final DruidException.Category category; + + protected BaseFailure( + String errorCode, + DruidException.Persona persona, + DruidException.Category category, + Throwable t, + String msg, + Object... args + ) + { + super(errorCode); + this.persona = persona; + this.category = category; + this.t = t; + this.msg = msg; + this.args = args; + } + + @Override + protected DruidException makeException(DruidException.DruidExceptionBuilder bob) + { + bob = bob.forPersona(persona).ofCategory(category); + if (t == null) { + return bob.build(msg, args); + } else { + return bob.build(t, msg, args); + } + } +} diff --git a/processing/src/main/java/org/apache/druid/error/DruidException.java b/processing/src/main/java/org/apache/druid/error/DruidException.java index 61f2b657b454..c4ec6bcb115f 100644 --- a/processing/src/main/java/org/apache/druid/error/DruidException.java +++ b/processing/src/main/java/org/apache/druid/error/DruidException.java @@ -36,18 +36,18 @@ * be using the DruidException. *

* Said another way, when a developer builds a DruidException in the code, they should be confident that the exception - * will make its way back to the user. DruidException is always the answer to "how do I generate an error message and + * will make its way back to the user. DruidException is always the answer to "How do I generate an error message and * deliver it to the user"? *

* At the time that DruidException was introduced, this type of "show this to the user please" exception was largely * handled by created {@link org.apache.druid.java.util.common.RE}, {@link org.apache.druid.java.util.common.IAE}, or * {@link org.apache.druid.java.util.common.ISE} objects. It is intended that DruidException replaces all usage of * these exceptions where the intention is to deliver a message to the user, which we believe to be the vast majority - * of usages. In cases where those exceptions are with the intention of being caught and acted upon, they should + * of usages. In cases where those exceptions are thrown with the intention of being caught and acted upon, * no change should occur. - * - * Notes about exception messages *

+ *

Notes about exception messages:

+ * * Firstly, exception messages should always be written with the notions from the style conventions covered in * {@code dev/style-conventions.md}. Whenever possible, we should also try to provide an action to take to resolve * the issue. @@ -78,8 +78,8 @@ * which is something that we would expect an operator to be in charge of. So, we would pick the OPERATOR persona * message, which also allows us to include more specific information about what server was not found and provide a * more meaningful action to take (check the health of your brokers). - * - * Description of fields of DruidException + *

+ *

Description of fields of DruidException

* Every error consists of: *
    *
  • A target persona
  • @@ -90,7 +90,7 @@ *
*

*

- * The target persona indicates who the message is written for. This is important for 2 reasons + * The target persona indicates who the message is written for. This is important for 2 reasons. *

    *
  1. It identifies why the developer is creating the exception and who they believe can take action on it. * This context allows for code reviewers and other developers to evaluate the message with the persona in mind
  2. @@ -114,8 +114,8 @@ * The context is a place to add extra information about the error that is not necessarily interpolated into the * error message. It's a way to carry extra information that might be useful to a developer, but not necessarily to * the target persona. - * - * Notes for developers working with DruidException + *

    + *

    Notes for developers working with DruidException:

    *

    * A DruidException can be built from one of 2 static methods: {@link #forPersona} or {@link #fromFailure(Failure)}. * The only way to set a specific error code is to build a DruidException from a Failure, when built in-line using @@ -131,7 +131,7 @@ public class DruidException extends RuntimeException { /** - * Starts building a "general" DruidException targeting the specific persona. + * Starts building a "general" DruidException targeting the specified persona. * * @param persona the target persona of the exception message * @return a builder that can be used to complete the creation of the DruidException @@ -386,8 +386,8 @@ public int getExpectedStatus() public static class PartialDruidExceptionBuilder { - private String errorCode; - private Persona targetPersona; + private final String errorCode; + private final Persona targetPersona; private PartialDruidExceptionBuilder(String errorCode, Persona targetPersona) { diff --git a/processing/src/main/java/org/apache/druid/error/EntryAlreadyExists.java b/processing/src/main/java/org/apache/druid/error/EntryAlreadyExists.java new file mode 100644 index 000000000000..2e6966ce315d --- /dev/null +++ b/processing/src/main/java/org/apache/druid/error/EntryAlreadyExists.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.apache.druid.error; + +/** + * A failure type used to make {@link DruidException}s of category + * {@link DruidException.Category#INVALID_INPUT} for persona {@link DruidException.Persona#USER}, + * denoting that a certain entry already exists. + */ +public class EntryAlreadyExists extends BaseFailure +{ + public static final String ERROR_CODE = "entryAlreadyExists"; + + public static DruidException exception(String msg, Object... args) + { + return exception(null, msg, args); + } + + public static DruidException exception(Throwable t, String msg, Object... args) + { + return DruidException.fromFailure(new EntryAlreadyExists(t, msg, args)); + } + + public EntryAlreadyExists( + Throwable t, + String msg, + Object... args + ) + { + super( + ERROR_CODE, + DruidException.Persona.USER, + DruidException.Category.INVALID_INPUT, + t, msg, args + ); + } +} diff --git a/processing/src/main/java/org/apache/druid/error/Forbidden.java b/processing/src/main/java/org/apache/druid/error/Forbidden.java index 13470d241c4d..b06431617af3 100644 --- a/processing/src/main/java/org/apache/druid/error/Forbidden.java +++ b/processing/src/main/java/org/apache/druid/error/Forbidden.java @@ -19,9 +19,8 @@ package org.apache.druid.error; -public class Forbidden extends DruidException.Failure +public class Forbidden extends BaseFailure { - public static DruidException exception() { return exception("Unauthorized"); @@ -37,32 +36,18 @@ public static DruidException exception(Throwable t, String msg, Object... args) return DruidException.fromFailure(new Forbidden(t, msg, args)); } - private final Throwable t; - private final String msg; - private final Object[] args; - private Forbidden( Throwable t, String msg, Object... args ) { - super("forbidden"); - this.t = t; - this.msg = msg; - this.args = args; + super( + "forbidden", + DruidException.Persona.USER, + DruidException.Category.FORBIDDEN, + t, msg, args + ); } - @Override - public DruidException makeException(DruidException.DruidExceptionBuilder bob) - { - bob = bob.forPersona(DruidException.Persona.USER) - .ofCategory(DruidException.Category.FORBIDDEN); - - if (t == null) { - return bob.build(msg, args); - } else { - return bob.build(t, msg, args); - } - } } diff --git a/processing/src/main/java/org/apache/druid/error/InternalServerError.java b/processing/src/main/java/org/apache/druid/error/InternalServerError.java index b730acb0e3d6..4ba221920d56 100644 --- a/processing/src/main/java/org/apache/druid/error/InternalServerError.java +++ b/processing/src/main/java/org/apache/druid/error/InternalServerError.java @@ -19,21 +19,18 @@ package org.apache.druid.error; -public class InternalServerError extends DruidException.Failure +public class InternalServerError extends BaseFailure { public static DruidException exception(String errorCode, String msg, Object... args) { return exception(null, errorCode, msg, args); } + public static DruidException exception(Throwable t, String errorCode, String msg, Object... args) { return DruidException.fromFailure(new InternalServerError(t, errorCode, msg, args)); } - private final Throwable t; - private final String msg; - private final Object[] args; - private InternalServerError( Throwable t, String errorCode, @@ -41,22 +38,11 @@ private InternalServerError( Object... args ) { - super(errorCode); - this.t = t; - this.msg = msg; - this.args = args; - } - - @Override - public DruidException makeException(DruidException.DruidExceptionBuilder bob) - { - bob = bob.forPersona(DruidException.Persona.OPERATOR) - .ofCategory(DruidException.Category.RUNTIME_FAILURE); - - if (t == null) { - return bob.build(msg, args); - } else { - return bob.build(t, msg, args); - } + super( + errorCode, + DruidException.Persona.OPERATOR, + DruidException.Category.RUNTIME_FAILURE, + t, msg, args + ); } } diff --git a/processing/src/main/java/org/apache/druid/error/InvalidInput.java b/processing/src/main/java/org/apache/druid/error/InvalidInput.java index ce50d4db3763..136dfca5077c 100644 --- a/processing/src/main/java/org/apache/druid/error/InvalidInput.java +++ b/processing/src/main/java/org/apache/druid/error/InvalidInput.java @@ -19,7 +19,11 @@ package org.apache.druid.error; -public class InvalidInput extends DruidException.Failure +/** + * A failure type used to make {@link DruidException}s of category + * {@link DruidException.Category#INVALID_INPUT} for persona {@link DruidException.Persona#USER}. + */ +public class InvalidInput extends BaseFailure { public static DruidException exception(String msg, Object... args) { @@ -31,33 +35,18 @@ public static DruidException exception(Throwable t, String msg, Object... args) return DruidException.fromFailure(new InvalidInput(t, msg, args)); } - private final Throwable t; - private final String msg; - private final Object[] args; - public InvalidInput( Throwable t, String msg, Object... args ) { - super("invalidInput"); - this.t = t; - this.msg = msg; - this.args = args; + super( + "invalidInput", + DruidException.Persona.USER, + DruidException.Category.INVALID_INPUT, + t, msg, args + ); } - - @Override - public DruidException makeException(DruidException.DruidExceptionBuilder bob) - { - bob = bob.forPersona(DruidException.Persona.USER) - .ofCategory(DruidException.Category.INVALID_INPUT); - - if (t == null) { - return bob.build(msg, args); - } else { - return bob.build(t, msg, args); - } - } } diff --git a/processing/src/main/java/org/apache/druid/error/NotFound.java b/processing/src/main/java/org/apache/druid/error/NotFound.java index 03d2a1077021..cc2a731177b5 100644 --- a/processing/src/main/java/org/apache/druid/error/NotFound.java +++ b/processing/src/main/java/org/apache/druid/error/NotFound.java @@ -19,9 +19,8 @@ package org.apache.druid.error; -public class NotFound extends DruidException.Failure +public class NotFound extends BaseFailure { - public static DruidException exception(String msg, Object... args) { return exception(null, msg, args); @@ -32,33 +31,17 @@ public static DruidException exception(Throwable t, String msg, Object... args) return DruidException.fromFailure(new NotFound(t, msg, args)); } - private final Throwable t; - private final String msg; - private final Object[] args; - public NotFound( Throwable t, String msg, Object... args ) { - super("notFound"); - this.t = t; - this.msg = msg; - this.args = args; - } - - - @Override - public DruidException makeException(DruidException.DruidExceptionBuilder bob) - { - bob = bob.forPersona(DruidException.Persona.USER) - .ofCategory(DruidException.Category.NOT_FOUND); - - if (t == null) { - return bob.build(msg, args); - } else { - return bob.build(t, msg, args); - } + super( + "notFound", + DruidException.Persona.USER, + DruidException.Category.NOT_FOUND, + t, msg, args + ); } } diff --git a/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java b/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java index 2b304b3f6c3b..85cebd478ee0 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java +++ b/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java @@ -43,7 +43,7 @@ * {@link Expr}. */ @Immutable -abstract class ConstantExpr implements Expr, Expr.SingleThreadSpecializable +abstract class ConstantExpr implements Expr { final ExpressionType outputType; @@ -115,9 +115,14 @@ public final ExprEval eval(ObjectBinding bindings) protected abstract ExprEval realEval(); + @Override + public String toString() + { + return String.valueOf(value); + } @Override - public Expr toSingleThreaded() + public Expr asSingleThreaded(InputBindingInspector inspector) { return new ExprEvalBasedConstantExpr(realEval()); } @@ -145,6 +150,18 @@ protected ExprEval realEval() return eval; } + @Override + public String stringify() + { + return eval.toExpr().stringify(); + } + + @Override + public String toString() + { + return eval.toExpr().toString(); + } + @Override public int hashCode() { diff --git a/processing/src/main/java/org/apache/druid/math/expr/Expr.java b/processing/src/main/java/org/apache/druid/math/expr/Expr.java index 3eb7ac467e58..8fa025cf9145 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/Expr.java +++ b/processing/src/main/java/org/apache/druid/math/expr/Expr.java @@ -182,6 +182,16 @@ default boolean canVectorize(InputBindingInspector inspector) return false; } + /** + * Possibly convert the {@link Expr} into an optimized, possibly not thread-safe {@link Expr}. Does not convert + * child {@link Expr}. Most callers should use {@link Expr#singleThreaded(Expr, InputBindingInspector)} to convert + * an entire tree, which delegates to this method to translate individual nodes. + */ + default Expr asSingleThreaded(InputBindingInspector inspector) + { + return this; + } + /** * Builds a 'vectorized' expression processor, that can operate on batches of input values for use in vectorized * query engines. @@ -769,30 +779,9 @@ private static Set map( * Returns the single-threaded version of the given expression tree. * * Nested expressions in the subtree are also optimized. - * Individual {@link Expr}-s which have a singleThreaded implementation via {@link SingleThreadSpecializable} are substituted. */ - static Expr singleThreaded(Expr expr) + static Expr singleThreaded(Expr expr, InputBindingInspector inspector) { - return expr.visit( - node -> { - if (node instanceof SingleThreadSpecializable) { - SingleThreadSpecializable canBeSingleThreaded = (SingleThreadSpecializable) node; - return canBeSingleThreaded.toSingleThreaded(); - } else { - return node; - } - } - ); - } - - /** - * Implementing this interface allows to provide a non-threadsafe {@link Expr} implementation. - */ - interface SingleThreadSpecializable - { - /** - * Non-threadsafe version of this expression. - */ - Expr toSingleThreaded(); + return expr.visit(node -> node.asSingleThreaded(inspector)); } } diff --git a/processing/src/main/java/org/apache/druid/math/expr/Function.java b/processing/src/main/java/org/apache/druid/math/expr/Function.java index 2c8a26759f34..e8ff45d90f13 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/Function.java +++ b/processing/src/main/java/org/apache/druid/math/expr/Function.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; import org.apache.druid.common.config.NullHandling; import org.apache.druid.error.DruidException; import org.apache.druid.java.util.common.DateTimes; @@ -63,6 +64,14 @@ @SuppressWarnings("unused") public interface Function extends NamedFunction { + /** + * Possibly convert a {@link Function} into an optimized, possibly not thread-safe {@link Function}. + */ + default Function asSingleThreaded(List args, Expr.InputBindingInspector inspector) + { + return this; + } + /** * Evaluate the function, given a list of arguments and a set of bindings to provide values for {@link IdentifierExpr}. */ @@ -3243,6 +3252,67 @@ public Set getArrayInputs(List args) } } + /** + * Primarily internal helper function used to coerce null, [], and [null] into [null], similar to the logic done + * by {@link org.apache.druid.segment.virtual.ExpressionSelectors#supplierFromDimensionSelector} when the 3rd + * argument is true, which is done when implicitly mapping scalar functions over mvd values. + */ + class MultiValueStringHarmonizeNullsFunction implements Function + { + @Override + public String name() + { + return "mv_harmonize_nulls"; + } + + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + final ExprEval eval = args.get(0).eval(bindings).castTo(ExpressionType.STRING_ARRAY); + if (eval.value() == null || eval.asArray().length == 0) { + return ExprEval.ofArray(ExpressionType.STRING_ARRAY, new Object[]{null}); + } + return eval; + } + + @Override + public void validateArguments(List args) + { + validationHelperCheckArgumentCount(args, 1); + } + + @Nullable + @Override + public ExpressionType getOutputType(Expr.InputBindingInspector inspector, List args) + { + return ExpressionType.STRING_ARRAY; + } + + @Override + public boolean hasArrayInputs() + { + return true; + } + + @Override + public boolean hasArrayOutput() + { + return true; + } + + @Override + public Set getScalarInputs(List args) + { + return Collections.emptySet(); + } + + @Override + public Set getArrayInputs(List args) + { + return ImmutableSet.copyOf(args); + } + } + class ArrayToMultiValueStringFunction implements Function { @Override @@ -3757,7 +3827,7 @@ Object[] merge(TypeSignature elementType, T[] array1, T[] array2) } } - class ArrayContainsFunction extends ArraysFunction + class ArrayContainsFunction implements Function { @Override public String name() @@ -3779,15 +3849,124 @@ public ExpressionType getOutputType(Expr.InputBindingInspector inspector, List args, Expr.ObjectBinding bindings) { + final ExprEval lhsExpr = args.get(0).eval(bindings); + final ExprEval rhsExpr = args.get(1).eval(bindings); + final Object[] array1 = lhsExpr.asArray(); - final Object[] array2 = rhsExpr.asArray(); - return ExprEval.ofLongBoolean(Arrays.asList(array1).containsAll(Arrays.asList(array2))); + if (array1 == null) { + return ExprEval.ofLong(null); + } + ExpressionType array1Type = lhsExpr.asArrayType(); + + if (rhsExpr.isArray()) { + final Object[] array2 = rhsExpr.castTo(array1Type).asArray(); + if (array2 == null) { + return ExprEval.ofLongBoolean(false); + } + return ExprEval.ofLongBoolean(Arrays.asList(array1).containsAll(Arrays.asList(array2))); + } else { + final Object elem = rhsExpr.castTo((ExpressionType) array1Type.getElementType()).value(); + return ExprEval.ofLongBoolean(Arrays.asList(array1).contains(elem)); + } + } + + @Override + public void validateArguments(List args) + { + validationHelperCheckArgumentCount(args, 2); + } + + @Override + public Set getScalarInputs(List args) + { + return Collections.emptySet(); + } + + @Override + public Set getArrayInputs(List args) + { + return ImmutableSet.copyOf(args); + } + + @Override + public boolean hasArrayInputs() + { + return true; + } + + @Override + public Function asSingleThreaded(List args, Expr.InputBindingInspector inspector) + { + if (args.get(1).isLiteral()) { + final ExpressionType lhsType = args.get(0).getOutputType(inspector); + if (lhsType == null) { + return this; + } + final ExpressionType lhsArrayType = ExpressionType.asArrayType(lhsType); + final ExprEval rhsEval = args.get(1).eval(InputBindings.nilBindings()); + if (rhsEval.isArray()) { + final Object[] rhsArray = rhsEval.castTo(lhsArrayType).asArray(); + return new ContainsConstantArray(rhsArray); + } else { + final Object val = rhsEval.castTo((ExpressionType) lhsArrayType.getElementType()).value(); + return new ContainsConstantScalar(val); + } + } + return this; + } + + private static final class ContainsConstantArray extends ArrayContainsFunction + { + @Nullable + final Object[] rhsArray; + + public ContainsConstantArray(@Nullable Object[] rhsArray) + { + this.rhsArray = rhsArray; + } + + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + final ExprEval lhsExpr = args.get(0).eval(bindings); + final Object[] array1 = lhsExpr.asArray(); + if (array1 == null) { + return ExprEval.ofLong(null); + } + if (rhsArray == null) { + return ExprEval.ofLongBoolean(false); + } + return ExprEval.ofLongBoolean(Arrays.asList(array1).containsAll(Arrays.asList(rhsArray))); + } + } + + private static final class ContainsConstantScalar extends ArrayContainsFunction + { + @Nullable + final Object val; + + public ContainsConstantScalar(@Nullable Object val) + { + this.val = val; + } + + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + final ExprEval lhsExpr = args.get(0).eval(bindings); + + final Object[] array1 = lhsExpr.asArray(); + if (array1 == null) { + return ExprEval.ofLong(null); + } + return ExprEval.ofLongBoolean(Arrays.asList(array1).contains(val)); + } } } - class ArrayOverlapFunction extends ArraysFunction + class ArrayOverlapFunction implements Function { @Override public String name() @@ -3803,15 +3982,110 @@ public ExpressionType getOutputType(Expr.InputBindingInspector inspector, List args, Expr.ObjectBinding bindings) { - final Object[] array1 = lhsExpr.asArray(); - final List array2 = Arrays.asList(rhsExpr.asArray()); - boolean any = false; + final ExprEval arrayExpr1 = args.get(0).eval(bindings); + final ExprEval arrayExpr2 = args.get(1).eval(bindings); + + final Object[] array1 = arrayExpr1.asArray(); + if (array1 == null) { + return ExprEval.ofLong(null); + } + ExpressionType array1Type = arrayExpr1.asArrayType(); + final Object[] array2 = arrayExpr2.castTo(array1Type).asArray(); + if (array2 == null) { + return ExprEval.ofLongBoolean(false); + } + List asList = Arrays.asList(array2); for (Object check : array1) { - any |= array2.contains(check); + if (asList.contains(check)) { + return ExprEval.ofLongBoolean(true); + } + } + return ExprEval.ofLongBoolean(false); + } + + @Override + public void validateArguments(List args) + { + validationHelperCheckArgumentCount(args, 2); + } + + @Override + public Set getScalarInputs(List args) + { + return Collections.emptySet(); + } + + @Override + public Set getArrayInputs(List args) + { + return ImmutableSet.copyOf(args); + } + + @Override + public boolean hasArrayInputs() + { + return true; + } + + @Override + public Function asSingleThreaded(List args, Expr.InputBindingInspector inspector) + { + if (args.get(1).isLiteral()) { + final ExpressionType lhsType = args.get(0).getOutputType(inspector); + if (lhsType == null) { + return this; + } + final ExpressionType lhsArrayType = ExpressionType.asArrayType(lhsType); + final ExprEval rhsEval = args.get(1).eval(InputBindings.nilBindings()); + final Object[] rhsArray = rhsEval.castTo(lhsArrayType).asArray(); + if (rhsArray == null) { + return new ArrayOverlapFunction() + { + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + final ExprEval arrayExpr1 = args.get(0).eval(bindings); + final Object[] array1 = arrayExpr1.asArray(); + if (array1 == null) { + return ExprEval.ofLong(null); + } + return ExprEval.ofLongBoolean(false); + } + }; + } + final Set set = new ObjectAVLTreeSet<>(lhsArrayType.getElementType().getNullableStrategy()); + set.addAll(Arrays.asList(rhsArray)); + return new OverlapConstantArray(set); + } + return this; + } + + private static final class OverlapConstantArray extends ArrayContainsFunction + { + final Set set; + + public OverlapConstantArray(Set set) + { + this.set = set; + } + + @Override + public ExprEval apply(List args, Expr.ObjectBinding bindings) + { + final ExprEval lhsExpr = args.get(0).eval(bindings); + final Object[] array1 = lhsExpr.asArray(); + if (array1 == null) { + return ExprEval.ofLong(null); + } + for (Object check : array1) { + if (set.contains(check)) { + return ExprEval.ofLongBoolean(true); + } + } + return ExprEval.ofLongBoolean(false); } - return ExprEval.ofLongBoolean(any); } } diff --git a/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java b/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java index a87d5d9cd683..6fe762a63374 100644 --- a/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java +++ b/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java @@ -179,6 +179,16 @@ class FunctionExpr implements Expr function.validateArguments(args); } + @Override + public Expr asSingleThreaded(InputBindingInspector inspector) + { + return new FunctionExpr( + function.asSingleThreaded(args, inspector), + name, + args + ); + } + @Override public String toString() { diff --git a/processing/src/main/java/org/apache/druid/metadata/EntryExistsException.java b/processing/src/main/java/org/apache/druid/metadata/EntryExistsException.java deleted file mode 100644 index 445ad7ad7b3c..000000000000 --- a/processing/src/main/java/org/apache/druid/metadata/EntryExistsException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.metadata; - -import org.apache.druid.common.exception.DruidException; -import org.apache.druid.java.util.common.StringUtils; - -/** - * A non-transient Druid metadata exception thrown when trying to insert a - * duplicate entry in the metadata. - * - * @deprecated Usages of this exception will be replaced by the new - * {@link org.apache.druid.error.DruidException} in a future release. - */ -@Deprecated -public class EntryExistsException extends DruidException -{ - - private static final int HTTP_BAD_REQUEST = 400; - - public EntryExistsException(String entryType, String entryId) - { - this(entryType, entryId, null); - } - - public EntryExistsException(String entryType, String entryId, Throwable t) - { - super(StringUtils.format("%s [%s] already exists.", entryType, entryId), HTTP_BAD_REQUEST, t, false); - } - -} diff --git a/processing/src/main/java/org/apache/druid/metadata/MetadataStorageActionHandler.java b/processing/src/main/java/org/apache/druid/metadata/MetadataStorageActionHandler.java index ca6ede6d32f0..45bfde49a8af 100644 --- a/processing/src/main/java/org/apache/druid/metadata/MetadataStorageActionHandler.java +++ b/processing/src/main/java/org/apache/druid/metadata/MetadataStorageActionHandler.java @@ -44,7 +44,6 @@ public interface MetadataStorageActionHandler inPair = (SerializablePair) objectsWhichMightBeNumeric[index]; - if (inPair.lhs != null && inPair.lhs < firstTime) { + if (inPair != null && inPair.lhs != null && inPair.lhs < firstTime) { firstTime = inPair.lhs; if (useDefault || inPair.rhs != null) { updateTimeWithValue(buf, position, firstTime, index); diff --git a/processing/src/main/java/org/apache/druid/query/aggregation/last/NumericLastVectorAggregator.java b/processing/src/main/java/org/apache/druid/query/aggregation/last/NumericLastVectorAggregator.java index ff5fa3af9e98..a556c3fea186 100644 --- a/processing/src/main/java/org/apache/druid/query/aggregation/last/NumericLastVectorAggregator.java +++ b/processing/src/main/java/org/apache/druid/query/aggregation/last/NumericLastVectorAggregator.java @@ -96,7 +96,7 @@ public void aggregate(ByteBuffer buf, int position, int startRow, int endRow) if (objectsWhichMightBeNumeric != null) { final SerializablePair inPair = (SerializablePair) objectsWhichMightBeNumeric[index]; - if (inPair.lhs != null && inPair.lhs >= lastTime) { + if (inPair != null && inPair.lhs != null && inPair.lhs >= lastTime) { lastTime = inPair.lhs; if (useDefault || inPair.rhs != null) { updateTimeWithValue(buf, position, lastTime, index); diff --git a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/AbstractBufferHashGrouper.java b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/AbstractBufferHashGrouper.java index 74018c3e012f..f3bc195dcbdf 100644 --- a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/AbstractBufferHashGrouper.java +++ b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/AbstractBufferHashGrouper.java @@ -169,6 +169,7 @@ public AggregateResult aggregate(KeyType key, int keyHash) @Override public void close() { + keySerde.reset(); aggregators.close(); } diff --git a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java index 22761f5c9d3d..e98af6af84fb 100644 --- a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java +++ b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/RowBasedGrouperHelper.java @@ -1380,6 +1380,14 @@ public void reset() if (enableRuntimeDictionaryGeneration) { dictionary.clear(); reverseDictionary.clear(); + stringArrayDictionary.clear(); + reverseStringArrayDictionary.clear(); + doubleArrayDictionary.clear(); + reverseDoubleArrayDictionary.clear(); + floatArrayDictionary.clear(); + reverseFloatArrayDictionary.clear(); + longArrayDictionary.clear(); + reverseLongArrayDictionary.clear(); rankOfDictionaryIds = null; currentEstimatedSize = 0; } diff --git a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/SpillingGrouper.java b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/SpillingGrouper.java index dd038acbd186..4e9b96102a16 100644 --- a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/SpillingGrouper.java +++ b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/SpillingGrouper.java @@ -215,6 +215,7 @@ public void reset() public void close() { grouper.close(); + keySerde.reset(); deleteFiles(); } diff --git a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/StreamingMergeSortedGrouper.java b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/StreamingMergeSortedGrouper.java index e1ffe90a4b01..a4ad0c6348d2 100644 --- a/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/StreamingMergeSortedGrouper.java +++ b/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/StreamingMergeSortedGrouper.java @@ -348,6 +348,7 @@ public void reset() @Override public void close() { + keySerde.reset(); for (BufferAggregator aggregator : aggregators) { try { aggregator.close(); diff --git a/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java b/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java index a1cd7cd32d6e..ca2cda3e0e14 100644 --- a/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java +++ b/processing/src/main/java/org/apache/druid/segment/virtual/ExpressionSelectors.java @@ -195,7 +195,10 @@ public static ColumnValueSelector makeExprEvalSelector( Expr expression ) { - ExpressionPlan plan = ExpressionPlanner.plan(columnSelectorFactory, Expr.singleThreaded(expression)); + ExpressionPlan plan = ExpressionPlanner.plan( + columnSelectorFactory, + Expr.singleThreaded(expression, columnSelectorFactory) + ); final RowIdSupplier rowIdSupplier = columnSelectorFactory.getRowIdSupplier(); if (plan.is(ExpressionPlan.Trait.SINGLE_INPUT_SCALAR)) { @@ -243,7 +246,10 @@ public static DimensionSelector makeDimensionSelector( @Nullable final ExtractionFn extractionFn ) { - final ExpressionPlan plan = ExpressionPlanner.plan(columnSelectorFactory, expression); + final ExpressionPlan plan = ExpressionPlanner.plan( + columnSelectorFactory, + Expr.singleThreaded(expression, columnSelectorFactory) + ); if (plan.any(ExpressionPlan.Trait.SINGLE_INPUT_SCALAR, ExpressionPlan.Trait.SINGLE_INPUT_MAPPABLE)) { final String column = plan.getSingleInputName(); diff --git a/processing/src/test/java/org/apache/druid/common/exception/DruidExceptionTest.java b/processing/src/test/java/org/apache/druid/common/exception/DruidExceptionTest.java deleted file mode 100644 index 9f0d53930e55..000000000000 --- a/processing/src/test/java/org/apache/druid/common/exception/DruidExceptionTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.common.exception; - -import org.junit.Assert; -import org.junit.Test; - -public class DruidExceptionTest -{ - @Test - public void testExceptionMessageAndResponseCode() - { - DruidException exception = Assert.assertThrows( - DruidException.class, - () -> { - throw new DruidException("an error has occurred", 401, null, true); - } - ); - Assert.assertEquals("an error has occurred", exception.getMessage()); - Assert.assertEquals(401, exception.getResponseCode()); - Assert.assertTrue(exception.isTransient()); - } -} diff --git a/processing/src/test/java/org/apache/druid/data/input/HandlingInputRowIteratorTest.java b/processing/src/test/java/org/apache/druid/data/input/HandlingInputRowIteratorTest.java index c70fa3977988..3fa5a3bef27e 100644 --- a/processing/src/test/java/org/apache/druid/data/input/HandlingInputRowIteratorTest.java +++ b/processing/src/test/java/org/apache/druid/data/input/HandlingInputRowIteratorTest.java @@ -25,8 +25,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; import org.mockito.Mockito; import javax.annotation.Nullable; @@ -41,7 +39,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -@RunWith(Enclosed.class) public class HandlingInputRowIteratorTest { public static class AbsentRowTest diff --git a/processing/src/test/java/org/apache/druid/frame/FrameTest.java b/processing/src/test/java/org/apache/druid/frame/FrameTest.java index eafe7f274bcf..029e34bc0116 100644 --- a/processing/src/test/java/org/apache/druid/frame/FrameTest.java +++ b/processing/src/test/java/org/apache/druid/frame/FrameTest.java @@ -45,7 +45,6 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.internal.matchers.ThrowableMessageMatcher; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; @@ -61,7 +60,6 @@ import java.util.ArrayList; import java.util.List; -@RunWith(Enclosed.class) public class FrameTest { // Tests that use good frames built from a standard test file. diff --git a/processing/src/test/java/org/apache/druid/frame/channel/ReadableByteChunksFrameChannelTest.java b/processing/src/test/java/org/apache/druid/frame/channel/ReadableByteChunksFrameChannelTest.java index 2c96be3c1ca8..18168a1776ed 100644 --- a/processing/src/test/java/org/apache/druid/frame/channel/ReadableByteChunksFrameChannelTest.java +++ b/processing/src/test/java/org/apache/druid/frame/channel/ReadableByteChunksFrameChannelTest.java @@ -36,7 +36,6 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; @@ -52,7 +51,6 @@ import java.util.Arrays; import java.util.List; -@RunWith(Enclosed.class) public class ReadableByteChunksFrameChannelTest { /** diff --git a/processing/src/test/java/org/apache/druid/frame/processor/FrameProcessorExecutorTest.java b/processing/src/test/java/org/apache/druid/frame/processor/FrameProcessorExecutorTest.java index 118cd43c48c9..b31818964ea6 100644 --- a/processing/src/test/java/org/apache/druid/frame/processor/FrameProcessorExecutorTest.java +++ b/processing/src/test/java/org/apache/druid/frame/processor/FrameProcessorExecutorTest.java @@ -58,7 +58,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.internal.matchers.ThrowableMessageMatcher; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; @@ -84,7 +83,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -@RunWith(Enclosed.class) public class FrameProcessorExecutorTest { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/frame/processor/SuperSorterTest.java b/processing/src/test/java/org/apache/druid/frame/processor/SuperSorterTest.java index b0ca2f26a4d2..8211839223c9 100644 --- a/processing/src/test/java/org/apache/druid/frame/processor/SuperSorterTest.java +++ b/processing/src/test/java/org/apache/druid/frame/processor/SuperSorterTest.java @@ -62,7 +62,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -79,7 +78,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -@RunWith(Enclosed.class) public class SuperSorterTest { private static final Logger log = new Logger(SuperSorterTest.class); diff --git a/processing/src/test/java/org/apache/druid/frame/segment/FrameStorageAdapterTest.java b/processing/src/test/java/org/apache/druid/frame/segment/FrameStorageAdapterTest.java index 1f4b0e45aee8..481851a0f75a 100644 --- a/processing/src/test/java/org/apache/druid/frame/segment/FrameStorageAdapterTest.java +++ b/processing/src/test/java/org/apache/druid/frame/segment/FrameStorageAdapterTest.java @@ -54,7 +54,6 @@ import org.junit.Assume; import org.junit.Before; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -65,7 +64,6 @@ import java.util.List; import java.util.function.Function; -@RunWith(Enclosed.class) public class FrameStorageAdapterTest { /** diff --git a/processing/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java b/processing/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java index ddda5a5de822..9bbd2de9df64 100644 --- a/processing/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java +++ b/processing/src/test/java/org/apache/druid/guice/DruidSecondaryModuleTest.java @@ -32,8 +32,6 @@ import com.google.inject.Module; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; import javax.annotation.Nullable; import javax.validation.Validation; @@ -43,7 +41,6 @@ import java.util.Objects; import java.util.Properties; -@RunWith(Enclosed.class) public class DruidSecondaryModuleTest { private static final String PROPERTY_NAME = "druid.injected.val"; diff --git a/processing/src/test/java/org/apache/druid/java/util/common/guava/TopNSequenceTest.java b/processing/src/test/java/org/apache/druid/java/util/common/guava/TopNSequenceTest.java index abf11ad51f86..5c7109642e43 100644 --- a/processing/src/test/java/org/apache/druid/java/util/common/guava/TopNSequenceTest.java +++ b/processing/src/test/java/org/apache/druid/java/util/common/guava/TopNSequenceTest.java @@ -25,7 +25,6 @@ import com.google.common.collect.Ordering; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -37,7 +36,6 @@ import java.util.Random; -@RunWith(Enclosed.class) public class TopNSequenceTest { private static final List EMPTY = Collections.emptyList(); diff --git a/processing/src/test/java/org/apache/druid/math/expr/ConstantExprTest.java b/processing/src/test/java/org/apache/druid/math/expr/ConstantExprTest.java index 717944ada34b..a7812f2d160e 100644 --- a/processing/src/test/java/org/apache/druid/math/expr/ConstantExprTest.java +++ b/processing/src/test/java/org/apache/druid/math/expr/ConstantExprTest.java @@ -19,20 +19,159 @@ package org.apache.druid.math.expr; +import org.apache.druid.math.expr.Expr.ObjectBinding; +import org.apache.druid.segment.column.TypeStrategies; +import org.apache.druid.segment.column.TypeStrategiesTest.NullableLongPair; +import org.apache.druid.segment.column.TypeStrategiesTest.NullableLongPairTypeStrategy; import org.apache.druid.testing.InitializedNullHandlingTest; +import org.junit.Assert; import org.junit.Test; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; +import java.math.BigInteger; public class ConstantExprTest extends InitializedNullHandlingTest { @Test - public void testLongSingleThreadedExpr() + public void testLongArrayExpr() { - LongExpr expr = new LongExpr(11L); - assertNotSame(expr.eval(null), expr.eval(null)); - Expr singleExpr = Expr.singleThreaded(expr); - assertSame(singleExpr.eval(null), singleExpr.eval(null)); + ArrayExpr arrayExpr = new ArrayExpr(ExpressionType.LONG_ARRAY, new Long[] {1L, 3L}); + checkExpr( + arrayExpr, + "[1, 3]", + "ARRAY[1, 3]", + arrayExpr + ); + } + + @Test + public void testStringArrayExpr() + { + ArrayExpr arrayExpr = new ArrayExpr(ExpressionType.STRING_ARRAY, new String[] {"foo", "bar"}); + checkExpr( + arrayExpr, + "[foo, bar]", + "ARRAY['foo', 'bar']", + arrayExpr + ); + } + + @Test + public void testBigIntegerExpr() + { + checkExpr( + new BigIntegerExpr(BigInteger.valueOf(37L)), + "37", + "37", + // after reparsing it will become a LongExpr + new LongExpr(37L) + ); + } + + @Test + public void testComplexExpr() + { + TypeStrategies.registerComplex("nullablePair", new NullableLongPairTypeStrategy()); + ComplexExpr complexExpr = new ComplexExpr( + ExpressionTypeFactory.getInstance().ofComplex("nullablePair"), + new NullableLongPair(21L, 37L) + ); + checkExpr( + complexExpr, + "Pair{lhs=21, rhs=37}", + "complex_decode_base64('nullablePair', 'AAAAAAAAAAAVAAAAAAAAAAAl')", + complexExpr + ); + } + + @Test + public void testDoubleExpr() + { + checkExpr( + new DoubleExpr(11.73D), + "11.73", + "11.73", + new DoubleExpr(11.73D) + ); + } + + @Test + public void testNullDoubleExpr() + { + TypeStrategies.registerComplex("nullablePair", new NullableLongPairTypeStrategy()); + checkExpr( + new NullDoubleExpr(), + "null", + "null", + // the expressions 'null' is always parsed as a StringExpr(null) + new StringExpr(null) + ); + } + + @Test + public void testNullLongExpr() + { + checkExpr( + new NullLongExpr(), + "null", + "null", + // the expressions 'null' is always parsed as a StringExpr(null) + new StringExpr(null) + ); + } + + @Test + public void testLong() + { + checkExpr( + new LongExpr(11L), + "11", + "11", + new LongExpr(11L) + ); + } + + @Test + public void testString() + { + checkExpr( + new StringExpr("some"), + "some", + "'some'", + new StringExpr("some") + ); + } + + @Test + public void testStringNull() + { + checkExpr( + new StringExpr(null), + null, + "null", + new StringExpr(null) + ); + } + + private void checkExpr( + Expr expr, + String expectedToString, + String expectedStringify, + Expr expectedReparsedExpr + ) + { + final ObjectBinding bindings = InputBindings.nilBindings(); + if (expr.getLiteralValue() != null) { + Assert.assertNotSame(expr.eval(bindings), expr.eval(bindings)); + } + final Expr singleExpr = Expr.singleThreaded(expr, bindings); + Assert.assertArrayEquals(expr.getCacheKey(), singleExpr.getCacheKey()); + Assert.assertSame(singleExpr.eval(bindings), singleExpr.eval(bindings)); + Assert.assertEquals(expectedToString, expr.toString()); + Assert.assertEquals(expectedStringify, expr.stringify()); + Assert.assertEquals(expectedToString, singleExpr.toString()); + final String stringify = singleExpr.stringify(); + final Expr reParsedExpr = Parser.parse(stringify, ExprMacroTable.nil()); + Assert.assertEquals(expectedReparsedExpr, reParsedExpr); + Assert.assertArrayEquals(expr.getCacheKey(), expectedReparsedExpr.getCacheKey()); } } diff --git a/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java b/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java index 0338efa46640..fae7bca736f0 100644 --- a/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java +++ b/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java @@ -92,7 +92,8 @@ public void setup() .put("someComplex", ExpressionType.fromColumnType(TypeStrategiesTest.NULLABLE_TEST_PAIR_TYPE)) .put("str1", ExpressionType.STRING) .put("str2", ExpressionType.STRING) - .put("nestedArray", ExpressionType.NESTED_DATA); + .put("nestedArray", ExpressionType.NESTED_DATA) + .put("emptyArray", ExpressionType.STRING_ARRAY); final StructuredData nestedArray = StructuredData.wrap( ImmutableList.of( @@ -120,7 +121,8 @@ public void setup() .put("someComplex", new TypeStrategiesTest.NullableLongPair(1L, 2L)) .put("str1", "v1") .put("str2", "v2") - .put("nestedArray", nestedArray); + .put("nestedArray", nestedArray) + .put("emptyArray", new Object[]{}); bestEffortBindings = InputBindings.forMap(builder.build()); typedBindings = InputBindings.forMap( builder.build(), InputBindings.inspectorFromTypeMap(inputTypesBuilder.build()) @@ -373,6 +375,11 @@ public void testArrayContains() assertExpr("array_contains([1, 2, 3], [2, 3])", 1L); assertExpr("array_contains([1, 2, 3], [3, 4])", 0L); assertExpr("array_contains(b, [3, 4])", 1L); + assertExpr("array_contains(null, [3, 4])", null); + assertExpr("array_contains(null, null)", null); + assertExpr("array_contains([1, null, 2], null)", 1L); + assertExpr("array_contains([1, null, 2], [null])", 1L); + assertExpr("array_contains([1, 2], null)", 0L); } @Test @@ -380,6 +387,12 @@ public void testArrayOverlap() { assertExpr("array_overlap([1, 2, 3], [2, 4, 6])", 1L); assertExpr("array_overlap([1, 2, 3], [4, 5, 6])", 0L); + assertExpr("array_overlap(null, [4, 5, 6])", null); + assertExpr("array_overlap([4, null], [4, 5, 6])", 1L); + assertExpr("array_overlap([4, 5, 6], null)", 0L); + assertExpr("array_overlap([4, 5, 6], [null])", 0L); + assertExpr("array_overlap([4, 5, null, 6], null)", 0L); + assertExpr("array_overlap([4, 5, null, 6], [null])", 1L); } @Test @@ -1226,6 +1239,17 @@ public void testDivOnString() ); } + @Test + public void testMvHarmonizeNulls() + { + assertArrayExpr("mv_harmonize_nulls(null)", new Object[]{null}); + assertArrayExpr("mv_harmonize_nulls(emptyArray)", new Object[]{null}); + // does nothing + assertArrayExpr("mv_harmonize_nulls(array(null))", new Object[]{null}); + // does nothing + assertArrayExpr("mv_harmonize_nulls(a)", new Object[]{"foo", "bar", "baz", "foobar"}); + } + private void assertExpr(final String expression, @Nullable final Object expectedResult) { for (Expr.ObjectBinding toUse : allBindings) { @@ -1249,6 +1273,9 @@ private void assertExpr( final Expr roundTripFlatten = Parser.parse(expr.stringify(), ExprMacroTable.nil()); Assert.assertEquals(expr.stringify(), expectedResult, roundTripFlatten.eval(bindings).value()); + final Expr singleThreaded = Expr.singleThreaded(expr, bindings); + Assert.assertEquals(singleThreaded.stringify(), expectedResult, singleThreaded.eval(bindings).value()); + Assert.assertEquals(expr.stringify(), roundTrip.stringify()); Assert.assertEquals(expr.stringify(), roundTripFlatten.stringify()); Assert.assertArrayEquals(expr.getCacheKey(), roundTrip.getCacheKey()); diff --git a/processing/src/test/java/org/apache/druid/metadata/EntryExistsExceptionTest.java b/processing/src/test/java/org/apache/druid/metadata/EntryExistsExceptionTest.java deleted file mode 100644 index 68b8a39b0d45..000000000000 --- a/processing/src/test/java/org/apache/druid/metadata/EntryExistsExceptionTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.metadata; - -import org.apache.druid.common.exception.DruidException; -import org.junit.Assert; -import org.junit.Test; - -public class EntryExistsExceptionTest -{ - @Test - public void testExceptionMessageAndResponseCode() - { - EntryExistsException exception = Assert.assertThrows( - EntryExistsException.class, - () -> { - throw new EntryExistsException("task", "100"); - } - ); - Assert.assertEquals("task [100] already exists.", exception.getMessage()); - Assert.assertEquals(DruidException.HTTP_CODE_BAD_REQUEST, exception.getResponseCode()); - } -} diff --git a/processing/src/test/java/org/apache/druid/metadata/TaskLookupTest.java b/processing/src/test/java/org/apache/druid/metadata/TaskLookupTest.java index 39714b42de68..76d90737a5f5 100644 --- a/processing/src/test/java/org/apache/druid/metadata/TaskLookupTest.java +++ b/processing/src/test/java/org/apache/druid/metadata/TaskLookupTest.java @@ -29,10 +29,7 @@ import org.joda.time.Period; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -@RunWith(Enclosed.class) public class TaskLookupTest { public static class CompleteTaskLookupTest diff --git a/processing/src/test/java/org/apache/druid/query/DruidsTest.java b/processing/src/test/java/org/apache/druid/query/DruidsTest.java index f4144d2d55f3..51be50650beb 100644 --- a/processing/src/test/java/org/apache/druid/query/DruidsTest.java +++ b/processing/src/test/java/org/apache/druid/query/DruidsTest.java @@ -34,10 +34,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -@RunWith(Enclosed.class) public class DruidsTest { private static final String DATASOURCE = "datasource"; diff --git a/processing/src/test/java/org/apache/druid/query/aggregation/GroupingAggregatorFactoryTest.java b/processing/src/test/java/org/apache/druid/query/aggregation/GroupingAggregatorFactoryTest.java index 0117bf1e82a5..2be56bd0f0eb 100644 --- a/processing/src/test/java/org/apache/druid/query/aggregation/GroupingAggregatorFactoryTest.java +++ b/processing/src/test/java/org/apache/druid/query/aggregation/GroupingAggregatorFactoryTest.java @@ -31,7 +31,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -39,7 +38,6 @@ import java.util.Arrays; import java.util.Collection; -@RunWith(Enclosed.class) public class GroupingAggregatorFactoryTest { public static GroupingAggregatorFactory makeFactory(String[] groupings, @Nullable String[] keyDims) diff --git a/processing/src/test/java/org/apache/druid/query/aggregation/first/FloatFirstVectorAggregationTest.java b/processing/src/test/java/org/apache/druid/query/aggregation/first/FloatFirstVectorAggregationTest.java index c7e8210d6c6d..a95e89bd32af 100644 --- a/processing/src/test/java/org/apache/druid/query/aggregation/first/FloatFirstVectorAggregationTest.java +++ b/processing/src/test/java/org/apache/druid/query/aggregation/first/FloatFirstVectorAggregationTest.java @@ -64,12 +64,16 @@ public class FloatFirstVectorAggregationTest extends InitializedNullHandlingTest new SerializablePairLongFloat(2345300L, 4.2F) }; + private final SerializablePairLongFloat[] nullPairs = {null, null, null, null}; + private VectorObjectSelector selector; + private VectorObjectSelector selector1; private BaseLongVectorValueSelector timeSelector; private ByteBuffer buf; private FloatFirstVectorAggregator target; + private FloatFirstVectorAggregator target1; private FloatFirstAggregatorFactory floatFirstAggregatorFactory; private VectorColumnSelectorFactory selectorFactory; @@ -119,6 +123,27 @@ public int getCurrentVectorSize() } }; + selector1 = new VectorObjectSelector() + { + @Override + public Object[] getObjectVector() + { + return nullPairs; + } + + @Override + public int getMaxVectorSize() + { + return 4; + } + + @Override + public int getCurrentVectorSize() + { + return 0; + } + }; + nonFloatValueSelector = new BaseLongVectorValueSelector(new NoFilterVectorOffset( LONG_VALUES.length, 0, @@ -219,6 +244,7 @@ public ColumnCapabilities getColumnCapabilities(String column) }; target = new FloatFirstVectorAggregator(timeSelector, selector); + target1 = new FloatFirstVectorAggregator(timeSelector, selector1); clearBufferForPositions(0, 0); floatFirstAggregatorFactory = new FloatFirstAggregatorFactory(NAME, FIELD_NAME, TIME_COL); @@ -252,6 +278,16 @@ public void aggregate() Assert.assertEquals(pairs[0].rhs, result.rhs, EPSILON); } + @Test + public void aggregateNulls1() + { + target1.init(buf, 0); + target1.aggregate(buf, 0, 0, VALUES.length); + Pair result = (Pair) target1.get(buf, 0); + Assert.assertEquals(Long.MAX_VALUE, result.lhs.longValue()); + Assert.assertEquals(NullHandling.defaultFloatValue(), result.rhs); + } + @Test public void aggregateWithNulls() { diff --git a/processing/src/test/java/org/apache/druid/query/scan/ScanQueryRunnerFactoryTest.java b/processing/src/test/java/org/apache/druid/query/scan/ScanQueryRunnerFactoryTest.java index 3f28688df57b..1c9a6c67ded8 100644 --- a/processing/src/test/java/org/apache/druid/query/scan/ScanQueryRunnerFactoryTest.java +++ b/processing/src/test/java/org/apache/druid/query/scan/ScanQueryRunnerFactoryTest.java @@ -43,7 +43,6 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -57,7 +56,6 @@ import java.util.stream.IntStream; -@RunWith(Enclosed.class) public class ScanQueryRunnerFactoryTest { private static final ScanQueryConfig CONFIG = new ScanQueryConfig() diff --git a/processing/src/test/java/org/apache/druid/segment/QueryableIndexStorageAdapterTest.java b/processing/src/test/java/org/apache/druid/segment/QueryableIndexStorageAdapterTest.java index 42fbffe3050c..ddb1cc419f33 100644 --- a/processing/src/test/java/org/apache/druid/segment/QueryableIndexStorageAdapterTest.java +++ b/processing/src/test/java/org/apache/druid/segment/QueryableIndexStorageAdapterTest.java @@ -37,7 +37,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -46,7 +45,6 @@ import java.util.Arrays; import java.util.Collection; -@RunWith(Enclosed.class) public class QueryableIndexStorageAdapterTest { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/segment/data/VSizeLongSerdeTest.java b/processing/src/test/java/org/apache/druid/segment/data/VSizeLongSerdeTest.java index dfd05be6fd07..cc832f6662d9 100644 --- a/processing/src/test/java/org/apache/druid/segment/data/VSizeLongSerdeTest.java +++ b/processing/src/test/java/org/apache/druid/segment/data/VSizeLongSerdeTest.java @@ -24,7 +24,6 @@ import org.apache.druid.java.util.common.StringUtils; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -35,7 +34,6 @@ import java.util.Collection; import java.util.stream.Collectors; -@RunWith(Enclosed.class) public class VSizeLongSerdeTest { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/segment/filter/ArrayContainsElementFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/ArrayContainsElementFilterTests.java index 434273870ffc..2418941bf73e 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/ArrayContainsElementFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/ArrayContainsElementFilterTests.java @@ -42,14 +42,12 @@ import org.junit.Assert; import org.junit.Assume; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.Closeable; import java.util.Arrays; -@RunWith(Enclosed.class) public class ArrayContainsElementFilterTests { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java index 78b40ea43d7d..3d35817531f4 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/EqualityFilterTests.java @@ -48,14 +48,12 @@ import org.junit.Assert; import org.junit.Assume; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.Closeable; import java.util.Arrays; -@RunWith(Enclosed.class) public class EqualityFilterTests { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/segment/filter/NullFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/NullFilterTests.java index 9cb64f75d780..5f9de867eca1 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/NullFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/NullFilterTests.java @@ -37,7 +37,6 @@ import org.junit.AfterClass; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -45,7 +44,6 @@ import java.util.Arrays; import java.util.Collections; -@RunWith(Enclosed.class) public class NullFilterTests { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/segment/filter/RangeFilterTests.java b/processing/src/test/java/org/apache/druid/segment/filter/RangeFilterTests.java index c21a21981937..424fb28b0863 100644 --- a/processing/src/test/java/org/apache/druid/segment/filter/RangeFilterTests.java +++ b/processing/src/test/java/org/apache/druid/segment/filter/RangeFilterTests.java @@ -50,7 +50,6 @@ import org.junit.Assert; import org.junit.Assume; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -59,7 +58,6 @@ import java.util.Collections; import java.util.List; -@RunWith(Enclosed.class) public class RangeFilterTests { @RunWith(Parameterized.class) diff --git a/processing/src/test/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcherTest.java b/processing/src/test/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcherTest.java index b1a47355f541..6222d50d4468 100644 --- a/processing/src/test/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcherTest.java +++ b/processing/src/test/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcherTest.java @@ -41,9 +41,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -52,12 +50,10 @@ import java.util.function.Function; import java.util.function.IntFunction; -@RunWith(Enclosed.class) public class IndexedTableJoinMatcherTest { private static final int SIZE = 3; - @RunWith(Enclosed.class) public static class ConditionMatcherFactoryTest { public static class MakeLongProcessorTest extends InitializedNullHandlingTest diff --git a/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java b/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java index f264e0a4fded..754eb05b3055 100644 --- a/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java +++ b/processing/src/test/java/org/apache/druid/utils/ConnectionUriUtilsTest.java @@ -24,15 +24,12 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; import java.util.Set; -@RunWith(Enclosed.class) public class ConnectionUriUtilsTest { public static class ThrowIfURLHasNotAllowedPropertiesTest diff --git a/processing/src/test/java/org/apache/druid/utils/DynamicConfigProviderUtilsTest.java b/processing/src/test/java/org/apache/druid/utils/DynamicConfigProviderUtilsTest.java index 496acfabef3b..d10973b82ff2 100644 --- a/processing/src/test/java/org/apache/druid/utils/DynamicConfigProviderUtilsTest.java +++ b/processing/src/test/java/org/apache/druid/utils/DynamicConfigProviderUtilsTest.java @@ -26,13 +26,10 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import java.util.Map; -@RunWith(Enclosed.class) public class DynamicConfigProviderUtilsTest { public static class ThrowIfURLHasNotAllowedPropertiesTest diff --git a/rewrite.yml b/rewrite.yml new file mode 100644 index 000000000000..ac715f51e8cc --- /dev/null +++ b/rewrite.yml @@ -0,0 +1,157 @@ +# 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. +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.apache.druid.RewriteRules +recipeList: + - org.apache.druid.UpgradeCalciteTestsToJunit5 + - org.openrewrite.java.testing.junit5.RemoveObsoleteRunners: + obsoleteRunners: + - org.junit.experimental.runners.Enclosed +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.apache.druid.UpgradeCalciteTestsToJunit5 +preconditions: + - org.openrewrite.java.search.FindImplementations: + typeName: org.apache.druid.sql.calcite.util.CalciteTestBase +recipeList: + - org.apache.druid.JUnit4to5Migration + - org.openrewrite.staticanalysis.UnnecessaryThrows + #- org.openrewrite.java.testing.junit5.StaticImports +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.apache.druid.EasyMockRunnerToEasyMockExtension +displayName: Replace EasyMock `@RunWith` with `@ExtendWith` usage +recipeList: + - org.openrewrite.java.testing.junit5.RunnerToExtension: + runners: + - org.easymock.EasyMockRunner + extension: org.easymock.EasyMockExtension +--- +# Customized version of org.openrewrite.java.testing.junit5.JUnit4to5Migration +# commented lines are recipes which were disabled +type: specs.openrewrite.org/v1beta/recipe +name: org.apache.druid.JUnit4to5Migration +displayName: Custom JUnit Jupiter migration from JUnit 4.x +description: Migrates JUnit 4.x tests to JUnit Jupiter. +tags: + - junit + - testing +recipeList: + - org.openrewrite.java.testing.junit5.UseWiremockExtension + - org.openrewrite.java.testing.junit5.IgnoreToDisabled + - org.openrewrite.java.testing.junit5.ThrowingRunnableToExecutable + - org.openrewrite.java.testing.junit5.RemoveObsoleteRunners: + obsoleteRunners: [org.junit.runners.JUnit4, org.junit.runners.BlockJUnit4ClassRunner] + - org.openrewrite.maven.RemovePluginDependency: + pluginGroupId: org.apache.maven.plugins + pluginArtifactId: maven-surefire-plugin + groupId: org.apache.maven.surefire + artifactId: surefire-junit* + - org.openrewrite.java.testing.junit5.UseHamcrestAssertThat + - org.openrewrite.java.testing.junit5.MigrateAssumptions + - org.openrewrite.java.testing.junit5.UseMockitoExtension + - org.openrewrite.java.testing.junit5.UseTestMethodOrder + - org.openrewrite.java.testing.junit5.MigrateJUnitTestCase + - org.openrewrite.java.ChangeMethodName: + methodPattern: org.junit.Assert assertEquals(.., Object[], Object[]) + newMethodName: assertArrayEquals + #- org.openrewrite.java.testing.junit5.AssertToAssertions + - org.openrewrite.java.testing.junit5.CategoryToTag + - org.openrewrite.java.testing.junit5.CleanupJUnitImports + - org.openrewrite.java.testing.junit5.TemporaryFolderToTempDir + - org.openrewrite.java.testing.junit5.TempDirNonFinal + - org.openrewrite.java.testing.junit5.TestRuleToTestInfo + - org.openrewrite.java.testing.junit5.UpdateBeforeAfterAnnotations + - org.openrewrite.java.testing.junit5.UpdateTestAnnotation + - org.openrewrite.java.testing.junit5.AddMissingTestBeforeAfterAnnotations + - org.openrewrite.java.testing.junit5.ParameterizedRunnerToParameterized + - org.openrewrite.java.testing.junit5.JUnitParamsRunnerToParameterized + - org.apache.druid.EasyMockRunnerToEasyMockExtension + - org.openrewrite.java.testing.junit5.ExpectedExceptionToAssertThrows + - org.openrewrite.java.testing.junit5.UpdateMockWebServer + - org.openrewrite.java.testing.junit5.VertxUnitToVertxJunit5 + - org.openrewrite.java.testing.junit5.EnclosedToNested + - org.openrewrite.java.testing.junit5.AddMissingNested + - org.openrewrite.java.testing.hamcrest.AddHamcrestIfUsed + - org.openrewrite.java.testing.junit5.UseXMLUnitLegacy + - org.openrewrite.java.dependencies.RemoveDependency: + groupId: junit + artifactId: junit + - org.openrewrite.maven.ExcludeDependency: + groupId: junit + artifactId: junit + - org.openrewrite.maven.RemoveExclusion: + groupId: org.testcontainers + artifactId: testcontainers + exclusionGroupId: junit + exclusionArtifactId: junit + - org.openrewrite.maven.RemoveExclusion: + groupId: org.springframework.boot + artifactId: spring-boot-testcontainers + exclusionGroupId: junit + exclusionArtifactId: junit + - org.openrewrite.java.dependencies.RemoveDependency: + groupId: org.junit.vintage + artifactId: junit-vintage-engine + - org.openrewrite.maven.ExcludeDependency: + groupId: org.junit.vintage + artifactId: junit-vintage-engine + - org.openrewrite.java.dependencies.AddDependency: + groupId: org.junit.jupiter + artifactId: junit-jupiter + version: 5.x + onlyIfUsing: org.junit.Test + scope: test + - org.openrewrite.java.dependencies.AddDependency: + groupId: org.junit.jupiter + artifactId: junit-jupiter + version: 5.x + onlyIfUsing: org.junit.jupiter.api.Test + scope: test + acceptTransitive: true + - org.openrewrite.java.dependencies.AddDependency: + groupId: org.junit.jupiter + artifactId: junit-jupiter-api + version: 5.x + onlyIfUsing: org.junit.Test + scope: test + - org.openrewrite.java.dependencies.AddDependency: + groupId: org.junit.jupiter + artifactId: junit-jupiter-api + version: 5.x + onlyIfUsing: org.junit.jupiter.api.Test + scope: test + acceptTransitive: true + - org.openrewrite.java.dependencies.AddDependency: + groupId: org.junit.jupiter + artifactId: junit-jupiter-params + version: 5.x + onlyIfUsing: org.junit.jupiter.params.ParameterizedTest + scope: test + acceptTransitive: true + - org.openrewrite.java.dependencies.UpgradeDependencyVersion: + groupId: org.mockito + artifactId: "*" + newVersion: 3.x + overrideManagedVersion: false + - org.openrewrite.maven.UpgradePluginVersion: + groupId: org.apache.maven.plugins + artifactId: maven-surefire-plugin + newVersion: 2.22.x + - org.openrewrite.maven.UpgradePluginVersion: + groupId: org.apache.maven.plugins + artifactId: maven-failsafe-plugin + newVersion: 2.22.x diff --git a/server/pom.xml b/server/pom.xml index a0132ee3a020..410b51480ed9 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -328,6 +328,31 @@ junit test + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.vintage + junit-vintage-engine + test + org.mockito mockito-core diff --git a/server/src/main/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinator.java b/server/src/main/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinator.java index bc4a1ddb50db..e63dc9c17e0d 100644 --- a/server/src/main/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinator.java +++ b/server/src/main/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinator.java @@ -422,21 +422,14 @@ public SegmentPublishResult commitSegmentsAndMetadata( @Nullable final DataSourceMetadata endMetadata ) throws IOException { - if (segments.isEmpty()) { - throw new IllegalArgumentException("segment set must not be empty"); - } - - final String dataSource = segments.iterator().next().getDataSource(); - for (DataSegment segment : segments) { - if (!dataSource.equals(segment.getDataSource())) { - throw new IllegalArgumentException("segments must all be from the same dataSource"); - } - } + verifySegmentsToCommit(segments); if ((startMetadata == null && endMetadata != null) || (startMetadata != null && endMetadata == null)) { throw new IllegalArgumentException("start/end metadata pair must be either null or non-null"); } + final String dataSource = segments.iterator().next().getDataSource(); + // Find which segments are used (i.e. not overshadowed). final Set usedSegments = new HashSet<>(); List> segmentHolders = diff --git a/server/src/main/java/org/apache/druid/metadata/SQLMetadataStorageActionHandler.java b/server/src/main/java/org/apache/druid/metadata/SQLMetadataStorageActionHandler.java index 9fbc79f273c4..8b003340bed0 100644 --- a/server/src/main/java/org/apache/druid/metadata/SQLMetadataStorageActionHandler.java +++ b/server/src/main/java/org/apache/druid/metadata/SQLMetadataStorageActionHandler.java @@ -28,7 +28,8 @@ import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.Maps; -import org.apache.druid.common.exception.DruidException; +import org.apache.druid.error.DruidException; +import org.apache.druid.error.InvalidInput; import org.apache.druid.indexer.TaskIdentifier; import org.apache.druid.indexer.TaskInfo; import org.apache.druid.java.util.common.DateTimes; @@ -68,6 +69,7 @@ public abstract class SQLMetadataStorageActionHandler { private static final EmittingLogger log = new EmittingLogger(SQLMetadataStorageActionHandler.class); + private static final String CONTEXT_KEY_IS_TRANSIENT = "isTransient"; private final SQLMetadataConnector connector; private final ObjectMapper jsonMapper; @@ -163,7 +165,7 @@ public void insert( final StatusType status, final String type, final String groupId - ) throws EntryExistsException + ) { try { getConnector().retryWithHandle( @@ -236,7 +238,7 @@ private boolean isTransientDruidException(Throwable t) if (t instanceof CallbackFailedException) { return isTransientDruidException(t.getCause()); } else if (t instanceof DruidException) { - return ((DruidException) t).isTransient(); + return Boolean.parseBoolean(((DruidException) t).getContextValue(CONTEXT_KEY_IS_TRANSIENT)); } else { return getConnector().isTransientException(t); } @@ -433,26 +435,20 @@ List> getTaskStatusList( private DruidException wrapInDruidException(String taskId, Throwable t) { if (isStatementException(t) && getEntry(taskId).isPresent()) { - return new EntryExistsException("Task", taskId); + return InvalidInput.exception("Task [%s] already exists", taskId); } else if (connector.isRootCausePacketTooBigException(t)) { - return new DruidException( - StringUtils.format( - "Payload for task [%s] exceeds the packet limit." - + " Update the max_allowed_packet on your metadata store" - + " server or in the connection properties.", - taskId - ), - DruidException.HTTP_CODE_BAD_REQUEST, - t, - false + return InvalidInput.exception( + "Payload for task [%s] exceeds the max allowed packet limit." + + " If you encountered this error while running native batch ingestion," + + " set a 'splitHintSpec' to reduce the payload of each task." + + " If not running native batch ingestion, report this error to your operator.", + taskId ); } else { - return new DruidException( - StringUtils.format("Encountered metadata exception for task [%s]", taskId), - DruidException.HTTP_CODE_SERVER_ERROR, - t, - connector.isTransientException(t) - ); + return DruidException.forPersona(DruidException.Persona.OPERATOR) + .ofCategory(DruidException.Category.RUNTIME_FAILURE) + .build(t, "Encountered metadata exception for task [%s]", taskId) + .withContext(CONTEXT_KEY_IS_TRANSIENT, connector.isTransientException(t)); } } diff --git a/server/src/main/java/org/apache/druid/metadata/SegmentsMetadataManager.java b/server/src/main/java/org/apache/druid/metadata/SegmentsMetadataManager.java index 540eba990f22..f0b9f06425d9 100644 --- a/server/src/main/java/org/apache/druid/metadata/SegmentsMetadataManager.java +++ b/server/src/main/java/org/apache/druid/metadata/SegmentsMetadataManager.java @@ -53,7 +53,13 @@ public interface SegmentsMetadataManager */ int markAsUsedAllNonOvershadowedSegmentsInDataSource(String dataSource); - int markAsUsedNonOvershadowedSegmentsInInterval(String dataSource, Interval interval); + /** + * Marks non-overshadowed unused segments for the given interval and optional list of versions + * as used. If versions are not specified, all versions of non-overshadowed unused segments in the interval + * will be marked as used. + * @return Number of segments updated + */ + int markAsUsedNonOvershadowedSegmentsInInterval(String dataSource, Interval interval, @Nullable List versions); /** * Marks the given segment IDs as "used" only if there are not already overshadowed @@ -81,7 +87,13 @@ public interface SegmentsMetadataManager */ int markAsUnusedAllSegmentsInDataSource(String dataSource); - int markAsUnusedSegmentsInInterval(String dataSource, Interval interval); + /** + * Marks segments as unused that are fully contained in the given interval for an optional list of versions. + * If versions are not specified, all versions of segments in the interval will be marked as unused. + * Segments that are already marked as unused are not updated. + * @return The number of segments updated + */ + int markAsUnusedSegmentsInInterval(String dataSource, Interval interval, @Nullable List versions); int markSegmentsAsUnused(Set segmentIds); diff --git a/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataManager.java b/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataManager.java index 4a268a2257da..66a60e072c02 100644 --- a/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataManager.java +++ b/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataManager.java @@ -654,21 +654,25 @@ public boolean markSegmentAsUsed(final String segmentId) @Override public int markAsUsedAllNonOvershadowedSegmentsInDataSource(final String dataSource) { - return doMarkAsUsedNonOvershadowedSegments(dataSource, null); + return doMarkAsUsedNonOvershadowedSegments(dataSource, null, null); } @Override - public int markAsUsedNonOvershadowedSegmentsInInterval(final String dataSource, final Interval interval) + public int markAsUsedNonOvershadowedSegmentsInInterval( + final String dataSource, + final Interval interval, + @Nullable final List versions + ) { Preconditions.checkNotNull(interval); - return doMarkAsUsedNonOvershadowedSegments(dataSource, interval); + return doMarkAsUsedNonOvershadowedSegments(dataSource, interval, versions); } - /** - * Implementation for both {@link #markAsUsedAllNonOvershadowedSegmentsInDataSource} (if the given interval is null) - * and {@link #markAsUsedNonOvershadowedSegmentsInInterval}. - */ - private int doMarkAsUsedNonOvershadowedSegments(String dataSourceName, @Nullable Interval interval) + private int doMarkAsUsedNonOvershadowedSegments( + final String dataSourceName, + final @Nullable Interval interval, + final @Nullable List versions + ) { final List unusedSegments = new ArrayList<>(); final SegmentTimeline timeline = new SegmentTimeline(); @@ -682,12 +686,12 @@ private int doMarkAsUsedNonOvershadowedSegments(String dataSourceName, @Nullable interval == null ? Intervals.ONLY_ETERNITY : Collections.singletonList(interval); try (final CloseableIterator iterator = - queryTool.retrieveUsedSegments(dataSourceName, intervals)) { + queryTool.retrieveUsedSegments(dataSourceName, intervals, versions)) { timeline.addSegments(iterator); } try (final CloseableIterator iterator = - queryTool.retrieveUnusedSegments(dataSourceName, intervals, null, null, null, null, null)) { + queryTool.retrieveUnusedSegments(dataSourceName, intervals, versions, null, null, null, null)) { while (iterator.hasNext()) { final DataSegment dataSegment = iterator.next(); timeline.addSegments(Iterators.singletonIterator(dataSegment)); @@ -796,7 +800,7 @@ private CloseableIterator retrieveUsedSegmentsOverlappingIntervals( private int markSegmentsAsUsed(final List segmentIds) { if (segmentIds.isEmpty()) { - log.info("No segments found to update!"); + log.info("No segments found to mark as used."); return 0; } @@ -856,13 +860,18 @@ public int markSegmentsAsUnused(Set segmentIds) } @Override - public int markAsUnusedSegmentsInInterval(String dataSourceName, Interval interval) + public int markAsUnusedSegmentsInInterval( + final String dataSource, + final Interval interval, + @Nullable final List versions + ) { + Preconditions.checkNotNull(interval); try { return connector.getDBI().withHandle( handle -> SqlSegmentsMetadataQuery.forHandle(handle, connector, dbTables.get(), jsonMapper) - .markSegmentsUnused(dataSourceName, interval) + .markSegmentsUnused(dataSource, interval, versions) ); } catch (Exception e) { diff --git a/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataQuery.java b/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataQuery.java index 29465cd665ba..5308f9dd7fea 100644 --- a/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataQuery.java +++ b/server/src/main/java/org/apache/druid/metadata/SqlSegmentsMetadataQuery.java @@ -42,6 +42,8 @@ import org.skife.jdbi.v2.PreparedBatch; import org.skife.jdbi.v2.Query; import org.skife.jdbi.v2.ResultIterator; +import org.skife.jdbi.v2.SQLStatement; +import org.skife.jdbi.v2.Update; import javax.annotation.Nullable; import java.util.ArrayList; @@ -119,11 +121,24 @@ public CloseableIterator retrieveUsedSegments( final String dataSource, final Collection intervals ) + { + return retrieveUsedSegments(dataSource, intervals, null); + } + + /** + * Similar to {@link #retrieveUsedSegments}, but with an additional {@code versions} argument. When {@code versions} + * is specified, all used segments in the specified {@code intervals} and {@code versions} are retrieved. + */ + public CloseableIterator retrieveUsedSegments( + final String dataSource, + final Collection intervals, + final List versions + ) { return retrieveSegments( dataSource, intervals, - null, + versions, IntervalMode.OVERLAPS, true, null, @@ -134,7 +149,7 @@ public CloseableIterator retrieveUsedSegments( } /** - * Retrieves segments for a given datasource that are marked unused and that are *fully contained by* any interval + * Retrieves segments for a given datasource that are marked unused and that are fully contained by any interval * in a particular collection of intervals. If the collection of intervals is empty, this method will retrieve all * unused segments. *

    @@ -184,7 +199,7 @@ public CloseableIterator retrieveUnusedSegments( /** * Similar to {@link #retrieveUnusedSegments}, but also retrieves associated metadata for the segments for a given - * datasource that are marked unused and that are *fully contained by* any interval in a particular collection of + * datasource that are marked unused and that are fully contained by any interval in a particular collection of * intervals. If the collection of intervals is empty, this method will retrieve all unused segments. * * This call does not return any information about realtime segments. @@ -312,45 +327,83 @@ public int markSegments(final Collection segmentIds, final boolean us } /** - * Marks all used segments that are *fully contained by* a particular interval as unused. + * Marks all used segments that are fully contained by a particular interval as unused. * - * @return the number of segments actually modified. + * @return Number of segments updated. */ public int markSegmentsUnused(final String dataSource, final Interval interval) + { + return markSegmentsUnused(dataSource, interval, null); + } + + /** + * Marks all used segments that are fully contained by a particular interval filtered by an optional list of versions + * as unused. + * + * @return Number of segments updated. + */ + public int markSegmentsUnused(final String dataSource, final Interval interval, @Nullable final List versions) { if (Intervals.isEternity(interval)) { - return handle - .createStatement( - StringUtils.format( - "UPDATE %s SET used=:used, used_status_last_updated = :used_status_last_updated " - + "WHERE dataSource = :dataSource AND used = true", - dbTables.getSegmentsTable() - ) + final StringBuilder sb = new StringBuilder(); + sb.append( + StringUtils.format( + "UPDATE %s SET used=:used, used_status_last_updated = :used_status_last_updated " + + "WHERE dataSource = :dataSource AND used = true", + dbTables.getSegmentsTable() ) + ); + + final boolean hasVersions = !CollectionUtils.isNullOrEmpty(versions); + + if (hasVersions) { + sb.append(getConditionForVersions(versions)); + } + + final Update stmt = handle + .createStatement(sb.toString()) .bind("dataSource", dataSource) .bind("used", false) - .bind("used_status_last_updated", DateTimes.nowUtc().toString()) - .execute(); + .bind("used_status_last_updated", DateTimes.nowUtc().toString()); + + if (hasVersions) { + bindVersionsToQuery(stmt, versions); + } + + return stmt.execute(); } else if (Intervals.canCompareEndpointsAsStrings(interval) && interval.getStart().getYear() == interval.getEnd().getYear()) { // Safe to write a WHERE clause with this interval. Note that it is unsafe if the years are different, because // that means extra characters can sneak in. (Consider a query interval like "2000-01-01/2001-01-01" and a // segment interval like "20001/20002".) - return handle - .createStatement( - StringUtils.format( - "UPDATE %s SET used=:used, used_status_last_updated = :used_status_last_updated " - + "WHERE dataSource = :dataSource AND used = true AND %s", - dbTables.getSegmentsTable(), - IntervalMode.CONTAINS.makeSqlCondition(connector.getQuoteString(), ":start", ":end") - ) + final StringBuilder sb = new StringBuilder(); + sb.append( + StringUtils.format( + "UPDATE %s SET used=:used, used_status_last_updated = :used_status_last_updated " + + "WHERE dataSource = :dataSource AND used = true AND %s", + dbTables.getSegmentsTable(), + IntervalMode.CONTAINS.makeSqlCondition(connector.getQuoteString(), ":start", ":end") ) + ); + + final boolean hasVersions = !CollectionUtils.isNullOrEmpty(versions); + + if (hasVersions) { + sb.append(getConditionForVersions(versions)); + } + + final Update stmt = handle + .createStatement(sb.toString()) .bind("dataSource", dataSource) .bind("used", false) .bind("start", interval.getStart().toString()) .bind("end", interval.getEnd().toString()) - .bind("used_status_last_updated", DateTimes.nowUtc().toString()) - .execute(); + .bind("used_status_last_updated", DateTimes.nowUtc().toString()); + + if (hasVersions) { + bindVersionsToQuery(stmt, versions); + } + return stmt.execute(); } else { // Retrieve, then drop, since we can't write a WHERE clause directly. final List segments = ImmutableList.copyOf( @@ -358,7 +411,7 @@ public int markSegmentsUnused(final String dataSource, final Interval interval) retrieveSegments( dataSource, Collections.singletonList(interval), - null, + versions, IntervalMode.CONTAINS, true, null, @@ -680,11 +733,10 @@ private Query> buildSegmentsTableQuery( appendConditionForIntervalsAndMatchMode(sb, intervals, matchMode, connector); } - if (!CollectionUtils.isNullOrEmpty(versions)) { - final String versionsStr = versions.stream() - .map(version -> "'" + version + "'") - .collect(Collectors.joining(",")); - sb.append(StringUtils.format(" AND version IN (%s)", versionsStr)); + final boolean hasVersions = !CollectionUtils.isNullOrEmpty(versions); + + if (hasVersions) { + sb.append(getConditionForVersions(versions)); } // Add the used_status_last_updated time filter only for unused segments when maxUsedStatusLastUpdatedTime is non-null. @@ -734,6 +786,10 @@ private Query> buildSegmentsTableQuery( bindQueryIntervals(sql, intervals); } + if (hasVersions) { + bindVersionsToQuery(sql, versions); + } + return sql; } @@ -834,6 +890,36 @@ private static int computeNumChangedSegments(List segmentIds, int[] segm return numChangedSegments; } + private static String getConditionForVersions(final List versions) + { + if (CollectionUtils.isNullOrEmpty(versions)) { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(" AND version IN ("); + for (int i = 0; i < versions.size(); i++) { + sb.append(StringUtils.format(":version%d", i)); + if (i != versions.size() - 1) { + sb.append(","); + } + } + sb.append(")"); + return sb.toString(); + } + + private static void bindVersionsToQuery(final SQLStatement query, final List versions) + { + if (CollectionUtils.isNullOrEmpty(versions)) { + return; + } + + for (int i = 0; i < versions.size(); i++) { + query.bind(StringUtils.format("version%d", i), versions.get(i)); + } + } + enum IntervalMode { CONTAINS { diff --git a/server/src/main/java/org/apache/druid/query/lookup/LookupListeningResource.java b/server/src/main/java/org/apache/druid/query/lookup/LookupListeningResource.java index 53072b96b103..51117835b3bc 100644 --- a/server/src/main/java/org/apache/druid/query/lookup/LookupListeningResource.java +++ b/server/src/main/java/org/apache/druid/query/lookup/LookupListeningResource.java @@ -24,10 +24,10 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import com.sun.jersey.spi.container.ResourceFilters; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.guice.annotations.Json; import org.apache.druid.guice.annotations.Smile; import org.apache.druid.java.util.common.logger.Logger; +import org.apache.druid.server.http.ServletResourceUtils; import org.apache.druid.server.http.security.ConfigResourceFilter; import org.apache.druid.server.listener.resource.AbstractListenerHandler; import org.apache.druid.server.listener.resource.ListenerResource; diff --git a/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java b/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java index 3939b8669771..57833506f093 100644 --- a/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java +++ b/server/src/main/java/org/apache/druid/server/http/CoordinatorCompactionConfigsResource.java @@ -27,7 +27,6 @@ import org.apache.druid.audit.AuditInfo; import org.apache.druid.audit.AuditManager; import org.apache.druid.common.config.ConfigManager.SetResult; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.server.coordinator.CoordinatorCompactionConfig; diff --git a/server/src/main/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResource.java b/server/src/main/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResource.java index a1ec2ea30e1b..1dcbdb7e5f23 100644 --- a/server/src/main/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResource.java +++ b/server/src/main/java/org/apache/druid/server/http/CoordinatorDynamicConfigsResource.java @@ -22,7 +22,6 @@ import com.sun.jersey.spi.container.ResourceFilters; import org.apache.druid.audit.AuditManager; import org.apache.druid.common.config.ConfigManager.SetResult; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.server.coordinator.CoordinatorConfigManager; import org.apache.druid.server.coordinator.CoordinatorDynamicConfig; diff --git a/server/src/main/java/org/apache/druid/server/http/DataSourcesResource.java b/server/src/main/java/org/apache/druid/server/http/DataSourcesResource.java index b83bda21dcd0..2f4334b36ac6 100644 --- a/server/src/main/java/org/apache/druid/server/http/DataSourcesResource.java +++ b/server/src/main/java/org/apache/druid/server/http/DataSourcesResource.java @@ -68,6 +68,7 @@ import org.apache.druid.timeline.TimelineObjectHolder; import org.apache.druid.timeline.VersionedIntervalTimeline; import org.apache.druid.timeline.partition.PartitionChunk; +import org.apache.druid.utils.CollectionUtils; import org.joda.time.DateTime; import org.joda.time.Interval; @@ -166,12 +167,12 @@ public Response getQueryableDataSources( @Path("/{dataSourceName}") @Produces(MediaType.APPLICATION_JSON) @ResourceFilters(DatasourceResourceFilter.class) - public Response getDataSource( + public Response getQueryableDataSource( @PathParam("dataSourceName") final String dataSourceName, @QueryParam("full") final String full ) { - final ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); + final ImmutableDruidDataSource dataSource = getQueryableDataSource(dataSourceName); if (dataSource == null) { return logAndCreateDataSourceNotFoundResponse(dataSourceName); @@ -205,35 +206,44 @@ public Response markAsUsedAllNonOvershadowedSegments(@PathParam("dataSourceName" @Produces(MediaType.APPLICATION_JSON) @ResourceFilters(DatasourceResourceFilter.class) public Response markAsUsedNonOvershadowedSegments( - @PathParam("dataSourceName") String dataSourceName, - MarkDataSourceSegmentsPayload payload + @PathParam("dataSourceName") final String dataSourceName, + final SegmentsToUpdateFilter payload ) { - SegmentUpdateOperation operation = () -> { - final Interval interval = payload.getInterval(); - if (interval != null) { - return segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(dataSourceName, interval); - } else { - final Set segmentIds = payload.getSegmentIds(); - if (segmentIds == null || segmentIds.isEmpty()) { - return 0; - } + if (payload == null || !payload.isValid()) { + return Response + .status(Response.Status.BAD_REQUEST) + .entity(SegmentsToUpdateFilter.INVALID_PAYLOAD_ERROR_MESSAGE) + .build(); + } else { + SegmentUpdateOperation operation = () -> { + final Interval interval = payload.getInterval(); + final List versions = payload.getVersions(); + if (interval != null) { + return segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(dataSourceName, interval, versions); + } else { + final Set segmentIds = payload.getSegmentIds(); + if (segmentIds == null || segmentIds.isEmpty()) { + return 0; + } - // Validate segmentIds - final List invalidSegmentIds = new ArrayList<>(); - for (String segmentId : segmentIds) { - if (SegmentId.iteratePossibleParsingsWithDataSource(dataSourceName, segmentId).isEmpty()) { - invalidSegmentIds.add(segmentId); + // Validate segmentIds + final List invalidSegmentIds = new ArrayList<>(); + for (String segmentId : segmentIds) { + if (SegmentId.iteratePossibleParsingsWithDataSource(dataSourceName, segmentId).isEmpty()) { + invalidSegmentIds.add(segmentId); + } } + if (!invalidSegmentIds.isEmpty()) { + throw InvalidInput.exception("Could not parse invalid segment IDs[%s]", invalidSegmentIds); + } + + return segmentsMetadataManager.markAsUsedNonOvershadowedSegments(dataSourceName, segmentIds); } - if (!invalidSegmentIds.isEmpty()) { - throw InvalidInput.exception("Could not parse invalid segment IDs[%s]", invalidSegmentIds); - } + }; - return segmentsMetadataManager.markAsUsedNonOvershadowedSegments(dataSourceName, segmentIds); - } - }; - return performSegmentUpdate(dataSourceName, payload, operation); + return performSegmentUpdate(dataSourceName, operation); + } } @POST @@ -243,69 +253,55 @@ public Response markAsUsedNonOvershadowedSegments( @Consumes(MediaType.APPLICATION_JSON) public Response markSegmentsAsUnused( @PathParam("dataSourceName") final String dataSourceName, - final MarkDataSourceSegmentsPayload payload, + final SegmentsToUpdateFilter payload, @Context final HttpServletRequest req ) - { - SegmentUpdateOperation operation = () -> { - final Interval interval = payload.getInterval(); - final int numUpdatedSegments; - if (interval != null) { - numUpdatedSegments = segmentsMetadataManager.markAsUnusedSegmentsInInterval(dataSourceName, interval); - } else { - final Set segmentIds = - payload.getSegmentIds() - .stream() - .map(idStr -> SegmentId.tryParse(dataSourceName, idStr)) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - // Filter out segmentIds that do not belong to this datasource - numUpdatedSegments = segmentsMetadataManager.markSegmentsAsUnused( - segmentIds.stream() - .filter(segmentId -> segmentId.getDataSource().equals(dataSourceName)) - .collect(Collectors.toSet()) - ); - } - auditManager.doAudit( - AuditEntry.builder() - .key(dataSourceName) - .type("segment.markUnused") - .payload(payload) - .auditInfo(AuthorizationUtils.buildAuditInfo(req)) - .request(AuthorizationUtils.buildRequestInfo("coordinator", req)) - .build() - ); - return numUpdatedSegments; - }; - return performSegmentUpdate(dataSourceName, payload, operation); - } - - private Response performSegmentUpdate( - String dataSourceName, - MarkDataSourceSegmentsPayload payload, - SegmentUpdateOperation operation - ) { if (payload == null || !payload.isValid()) { - log.warn("Invalid request payload: [%s]", payload); return Response .status(Response.Status.BAD_REQUEST) - .entity("Invalid request payload, either interval or segmentIds array must be specified") + .entity(SegmentsToUpdateFilter.INVALID_PAYLOAD_ERROR_MESSAGE) .build(); + } else { + SegmentUpdateOperation operation = () -> { + final Interval interval = payload.getInterval(); + final List versions = payload.getVersions(); + final int numUpdatedSegments; + if (interval != null) { + numUpdatedSegments = segmentsMetadataManager.markAsUnusedSegmentsInInterval(dataSourceName, interval, versions); + } else { + final Set segmentIds = + payload.getSegmentIds() + .stream() + .map(idStr -> SegmentId.tryParse(dataSourceName, idStr)) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // Filter out segmentIds that do not belong to this datasource + numUpdatedSegments = segmentsMetadataManager.markSegmentsAsUnused( + segmentIds.stream() + .filter(segmentId -> segmentId.getDataSource().equals(dataSourceName)) + .collect(Collectors.toSet()) + ); + } + auditManager.doAudit( + AuditEntry.builder() + .key(dataSourceName) + .type("segment.markUnused") + .payload(payload) + .auditInfo(AuthorizationUtils.buildAuditInfo(req)) + .request(AuthorizationUtils.buildRequestInfo("coordinator", req)) + .build() + ); + return numUpdatedSegments; + }; + return performSegmentUpdate(dataSourceName, operation); } - - final ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); - if (dataSource == null) { - return logAndCreateDataSourceNotFoundResponse(dataSourceName); - } - - return performSegmentUpdate(dataSourceName, operation); } private static Response logAndCreateDataSourceNotFoundResponse(String dataSourceName) { - log.warn("datasource not found [%s]", dataSourceName); + log.warn("datasource[%s] not found", dataSourceName); return Response.noContent().build(); } @@ -322,7 +318,7 @@ private static Response performSegmentUpdate(String dataSourceName, SegmentUpdat .build(); } catch (Exception e) { - log.error(e, "Error occurred while updating segments for data source[%s]", dataSourceName); + log.error(e, "Error occurred while updating segments for datasource[%s]", dataSourceName); return Response .serverError() .entity(ImmutableMap.of("error", "Exception occurred.", "message", Throwables.getRootCause(e).toString())) @@ -434,7 +430,7 @@ public Response getIntervalsWithServedSegmentsOrAllServedSegmentsPerIntervals( ) { if (simple == null && full == null) { - final ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); + final ImmutableDruidDataSource dataSource = getQueryableDataSource(dataSourceName); if (dataSource == null) { return logAndCreateDataSourceNotFoundResponse(dataSourceName); } @@ -460,7 +456,7 @@ public Response getServedSegmentsInInterval( { final Interval theInterval = Intervals.of(interval.replace('_', '/')); if (simple == null && full == null) { - final ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); + final ImmutableDruidDataSource dataSource = getQueryableDataSource(dataSourceName); if (dataSource == null) { return logAndCreateDataSourceNotFoundResponse(dataSourceName); } @@ -570,9 +566,9 @@ private SegmentsLoadStatistics computeSegmentLoadStatistics(Iterable intervalFilter ) { - final ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); + final ImmutableDruidDataSource dataSource = getQueryableDataSource(dataSourceName); if (dataSource == null) { return logAndCreateDataSourceNotFoundResponse(dataSourceName); @@ -667,7 +663,7 @@ public Response getAllServedSegments( @QueryParam("full") String full ) { - ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); + ImmutableDruidDataSource dataSource = getQueryableDataSource(dataSourceName); if (dataSource == null) { return logAndCreateDataSourceNotFoundResponse(dataSourceName); } @@ -689,7 +685,7 @@ public Response getServedSegment( @PathParam("segmentId") String segmentId ) { - ImmutableDruidDataSource dataSource = getDataSource(dataSourceName); + ImmutableDruidDataSource dataSource = getQueryableDataSource(dataSourceName); if (dataSource == null) { return logAndCreateDataSourceNotFoundResponse(dataSourceName); } @@ -747,7 +743,7 @@ public Response getTiersWhereSegmentsAreServed(@PathParam("dataSourceName") Stri } @Nullable - private ImmutableDruidDataSource getDataSource(final String dataSourceName) + private ImmutableDruidDataSource getQueryableDataSource(final String dataSourceName) { List dataSources = serverInventoryView .getInventory() @@ -994,37 +990,61 @@ static boolean isSegmentLoaded(Iterable servedSegments return false; } + /** + * Either {@code interval} or {@code segmentIds} array must be specified, but not both. + * {@code versions} may be optionally specified only when {@code interval} is provided. + */ @VisibleForTesting - protected static class MarkDataSourceSegmentsPayload + static class SegmentsToUpdateFilter { private final Interval interval; private final Set segmentIds; + private final List versions; + + private static final String INVALID_PAYLOAD_ERROR_MESSAGE = "Invalid request payload. Specify either 'interval' or 'segmentIds', but not both." + + " Optionally, include 'versions' only when 'interval' is provided."; @JsonCreator - public MarkDataSourceSegmentsPayload( - @JsonProperty("interval") Interval interval, - @JsonProperty("segmentIds") Set segmentIds + public SegmentsToUpdateFilter( + @JsonProperty("interval") @Nullable Interval interval, + @JsonProperty("segmentIds") @Nullable Set segmentIds, + @JsonProperty("versions") @Nullable List versions ) { this.interval = interval; this.segmentIds = segmentIds; + this.versions = versions; } + @Nullable @JsonProperty public Interval getInterval() { return interval; } + @Nullable @JsonProperty public Set getSegmentIds() { return segmentIds; } - public boolean isValid() + @Nullable + @JsonProperty + public List getVersions() + { + return versions; + } + + private boolean isValid() { - return (interval == null ^ segmentIds == null) && (segmentIds == null || !segmentIds.isEmpty()); + final boolean hasSegmentIds = !CollectionUtils.isNullOrEmpty(segmentIds); + if (interval == null) { + return hasSegmentIds && CollectionUtils.isNullOrEmpty(versions); + } else { + return !hasSegmentIds; + } } } } diff --git a/server/src/main/java/org/apache/druid/server/http/LookupCoordinatorResource.java b/server/src/main/java/org/apache/druid/server/http/LookupCoordinatorResource.java index 9e435e72f952..e954fb521363 100644 --- a/server/src/main/java/org/apache/druid/server/http/LookupCoordinatorResource.java +++ b/server/src/main/java/org/apache/druid/server/http/LookupCoordinatorResource.java @@ -29,7 +29,6 @@ import com.google.common.net.HostAndPort; import com.google.inject.Inject; import com.sun.jersey.spi.container.ResourceFilters; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.guice.annotations.Json; import org.apache.druid.guice.annotations.Smile; import org.apache.druid.java.util.common.IAE; diff --git a/server/src/main/java/org/apache/druid/server/http/SegmentToDrop.java b/server/src/main/java/org/apache/druid/server/http/SegmentToDrop.java deleted file mode 100644 index 67289caaa5b8..000000000000 --- a/server/src/main/java/org/apache/druid/server/http/SegmentToDrop.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.server.http; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - */ -public class SegmentToDrop -{ - private final String fromServer; - private final String segmentName; - - @JsonCreator - public SegmentToDrop( - @JsonProperty("from") String fromServer, - @JsonProperty("segmentName") String segmentName - ) - { - this.fromServer = fromServer; - this.segmentName = segmentName; - } - - public String getFromServer() - { - return fromServer; - } - - public String getSegmentName() - { - return segmentName; - } -} diff --git a/server/src/main/java/org/apache/druid/server/http/SegmentToMove.java b/server/src/main/java/org/apache/druid/server/http/SegmentToMove.java deleted file mode 100644 index f0361e2e238f..000000000000 --- a/server/src/main/java/org/apache/druid/server/http/SegmentToMove.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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. - */ - -package org.apache.druid.server.http; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - */ -public class SegmentToMove -{ - private final String fromServer; - private final String toServer; - private final String segmentName; - - @JsonCreator - public SegmentToMove( - @JsonProperty("from") String fromServer, - @JsonProperty("to") String toServer, - @JsonProperty("segmentName") String segmentName - ) - { - this.fromServer = fromServer; - this.toServer = toServer; - this.segmentName = segmentName; - } - - public String getFromServer() - { - return fromServer; - } - - public String getToServer() - { - return toServer; - } - - public String getSegmentName() - { - return segmentName; - } -} diff --git a/processing/src/main/java/org/apache/druid/common/utils/ServletResourceUtils.java b/server/src/main/java/org/apache/druid/server/http/ServletResourceUtils.java similarity index 81% rename from processing/src/main/java/org/apache/druid/common/utils/ServletResourceUtils.java rename to server/src/main/java/org/apache/druid/server/http/ServletResourceUtils.java index d28f612481db..58f782587891 100644 --- a/processing/src/main/java/org/apache/druid/common/utils/ServletResourceUtils.java +++ b/server/src/main/java/org/apache/druid/server/http/ServletResourceUtils.java @@ -17,12 +17,16 @@ * under the License. */ -package org.apache.druid.common.utils; +package org.apache.druid.server.http; import com.google.common.collect.ImmutableMap; +import org.apache.druid.error.DruidException; +import org.apache.druid.error.ErrorResponse; import org.apache.druid.java.util.common.StringUtils; import javax.annotation.Nullable; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import java.util.Map; public class ServletResourceUtils @@ -49,4 +53,12 @@ public static Map jsonize(String msgFormat, Object... args) { return ImmutableMap.of("error", StringUtils.nonStrictFormat(msgFormat, args)); } + + public static Response buildErrorResponseFrom(DruidException e) + { + return Response.status(e.getStatusCode()) + .type(MediaType.APPLICATION_JSON_TYPE) + .entity(new ErrorResponse(e)) + .build(); + } } diff --git a/server/src/main/java/org/apache/druid/server/listener/resource/AbstractListenerHandler.java b/server/src/main/java/org/apache/druid/server/listener/resource/AbstractListenerHandler.java index d6b608ad0a13..5553b7320c27 100644 --- a/server/src/main/java/org/apache/druid/server/listener/resource/AbstractListenerHandler.java +++ b/server/src/main/java/org/apache/druid/server/listener/resource/AbstractListenerHandler.java @@ -26,9 +26,9 @@ import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.java.util.common.jackson.JacksonUtils; import org.apache.druid.java.util.common.logger.Logger; +import org.apache.druid.server.http.ServletResourceUtils; import javax.annotation.Nullable; import javax.ws.rs.core.Response; diff --git a/server/src/main/java/org/apache/druid/server/listener/resource/ListenerResource.java b/server/src/main/java/org/apache/druid/server/listener/resource/ListenerResource.java index 569f20da7755..f13f2a4360bd 100644 --- a/server/src/main/java/org/apache/druid/server/listener/resource/ListenerResource.java +++ b/server/src/main/java/org/apache/druid/server/listener/resource/ListenerResource.java @@ -23,10 +23,10 @@ import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import org.apache.druid.common.utils.ServletResourceUtils; import org.apache.druid.guice.annotations.Json; import org.apache.druid.guice.annotations.Smile; import org.apache.druid.java.util.common.logger.Logger; +import org.apache.druid.server.http.ServletResourceUtils; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; diff --git a/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java b/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java index 5cce3c2fd3f3..ab0e44c04303 100644 --- a/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java +++ b/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java @@ -39,7 +39,6 @@ import org.apache.druid.query.ResourceLimitExceededException; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -54,7 +53,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -@RunWith(Enclosed.class) public class JsonParserIteratorTest { private static final JavaType JAVA_TYPE = Mockito.mock(JavaType.class); diff --git a/server/src/test/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinatorTest.java b/server/src/test/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinatorTest.java index 31035ca316cd..03de72b96fbb 100644 --- a/server/src/test/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinatorTest.java +++ b/server/src/test/java/org/apache/druid/metadata/IndexerSQLMetadataStorageCoordinatorTest.java @@ -383,17 +383,10 @@ private void markAllSegmentsUnused(Set segments, DateTime usedStatu for (final DataSegment segment : segments) { Assert.assertEquals( 1, - (int) derbyConnector.getDBI().withHandle( - handle -> { - String request = StringUtils.format( - "UPDATE %s SET used = false, used_status_last_updated = :used_status_last_updated WHERE id = :id", - derbyConnectorRule.metadataTablesConfigSupplier().get().getSegmentsTable() - ); - return handle.createStatement(request) - .bind("id", segment.getId().toString()) - .bind("used_status_last_updated", usedStatusLastUpdatedTime.toString() - ).execute(); - } + derbyConnectorRule.segments().update( + "UPDATE %s SET used = false, used_status_last_updated = ? WHERE id = ?", + usedStatusLastUpdatedTime.toString(), + segment.getId().toString() ) ); } diff --git a/server/src/test/java/org/apache/druid/metadata/SQLMetadataConnectorTest.java b/server/src/test/java/org/apache/druid/metadata/SQLMetadataConnectorTest.java index 1c8f6493e22d..d240b83dd07d 100644 --- a/server/src/test/java/org/apache/druid/metadata/SQLMetadataConnectorTest.java +++ b/server/src/test/java/org/apache/druid/metadata/SQLMetadataConnectorTest.java @@ -30,13 +30,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.skife.jdbi.v2.Batch; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.exceptions.CallbackFailedException; import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException; import org.skife.jdbi.v2.exceptions.UnableToObtainConnectionException; -import org.skife.jdbi.v2.tweak.HandleCallback; import java.sql.SQLException; import java.sql.SQLRecoverableException; @@ -47,7 +45,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -175,29 +172,7 @@ public void testGeIndexOnNoTable() public void testAlterSegmentTableAddLastUsed() { connector.createSegmentTable(); - - // Drop column used_status_last_updated to bring us in line with pre-upgrade state - derbyConnectorRule.getConnector().retryWithHandle( - new HandleCallback() - { - @Override - public Void withHandle(Handle handle) - { - final Batch batch = handle.createBatch(); - batch.add( - StringUtils.format( - "ALTER TABLE %1$s DROP COLUMN USED_STATUS_LAST_UPDATED", - derbyConnectorRule.metadataTablesConfigSupplier() - .get() - .getSegmentsTable() - .toUpperCase(Locale.ENGLISH) - ) - ); - batch.execute(); - return null; - } - } - ); + derbyConnectorRule.segments().update("ALTER TABLE %1$s DROP COLUMN USED_STATUS_LAST_UPDATED"); connector.alterSegmentTableAddUsedFlagLastUpdated(); connector.tableHasColumn( diff --git a/server/src/test/java/org/apache/druid/metadata/SQLMetadataStorageActionHandlerTest.java b/server/src/test/java/org/apache/druid/metadata/SQLMetadataStorageActionHandlerTest.java index cee24d314e38..6cddcd5d6468 100644 --- a/server/src/test/java/org/apache/druid/metadata/SQLMetadataStorageActionHandlerTest.java +++ b/server/src/test/java/org/apache/druid/metadata/SQLMetadataStorageActionHandlerTest.java @@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import org.apache.druid.error.DruidException; import org.apache.druid.indexer.TaskIdentifier; import org.apache.druid.indexer.TaskInfo; import org.apache.druid.indexer.TaskLocation; @@ -59,6 +60,7 @@ public class SQLMetadataStorageActionHandlerTest public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule(); private static final ObjectMapper JSON_MAPPER = new DefaultObjectMapper(); + private static final Random RANDOM = new Random(1); private SQLMetadataStorageActionHandler, Map, Map, Map> handler; @@ -131,17 +133,10 @@ public void testEntryAndStatus() handler.insert(entryId, DateTimes.of("2014-01-02T00:00:00.123"), "testDataSource", entry, true, null, "type", "group"); - Assert.assertEquals( - Optional.of(entry), - handler.getEntry(entryId) - ); - + Assert.assertEquals(Optional.of(entry), handler.getEntry(entryId)); Assert.assertEquals(Optional.absent(), handler.getEntry("non_exist_entry")); - Assert.assertEquals(Optional.absent(), handler.getStatus(entryId)); - Assert.assertEquals(Optional.absent(), handler.getStatus("non_exist_entry")); - Assert.assertTrue(handler.setStatus(entryId, true, status1)); Assert.assertEquals( @@ -175,21 +170,12 @@ public void testEntryAndStatus() // inactive statuses cannot be updated, this should fail Assert.assertFalse(handler.setStatus(entryId, false, status2)); - Assert.assertEquals( - Optional.of(status1), - handler.getStatus(entryId) - ); - - Assert.assertEquals( - Optional.of(entry), - handler.getEntry(entryId) - ); - + Assert.assertEquals(Optional.of(status1), handler.getStatus(entryId)); + Assert.assertEquals(Optional.of(entry), handler.getEntry(entryId)); Assert.assertEquals( ImmutableList.of(), handler.getTaskInfos(CompleteTaskLookup.withTasksCreatedPriorTo(null, DateTimes.of("2014-01-03")), null) ); - Assert.assertEquals( ImmutableList.of(status1), handler.getTaskInfos(CompleteTaskLookup.withTasksCreatedPriorTo(null, DateTimes.of("2014-01-01")), null) @@ -200,7 +186,7 @@ public void testEntryAndStatus() } @Test - public void testGetRecentStatuses() throws EntryExistsException + public void testGetRecentStatuses() { for (int i = 1; i < 11; i++) { final String entryId = "abcd_" + i; @@ -211,10 +197,7 @@ public void testGetRecentStatuses() throws EntryExistsException } final List, Map>> statuses = handler.getTaskInfos( - CompleteTaskLookup.withTasksCreatedPriorTo( - 7, - DateTimes.of("2014-01-01") - ), + CompleteTaskLookup.withTasksCreatedPriorTo(7, DateTimes.of("2014-01-01")), null ); Assert.assertEquals(7, statuses.size()); @@ -225,7 +208,7 @@ public void testGetRecentStatuses() throws EntryExistsException } @Test - public void testGetRecentStatuses2() throws EntryExistsException + public void testGetRecentStatuses2() { for (int i = 1; i < 6; i++) { final String entryId = "abcd_" + i; @@ -236,10 +219,7 @@ public void testGetRecentStatuses2() throws EntryExistsException } final List, Map>> statuses = handler.getTaskInfos( - CompleteTaskLookup.withTasksCreatedPriorTo( - 10, - DateTimes.of("2014-01-01") - ), + CompleteTaskLookup.withTasksCreatedPriorTo(10, DateTimes.of("2014-01-01")), null ); Assert.assertEquals(5, statuses.size()); @@ -257,10 +237,13 @@ public void testDuplicateInsertThrowsEntryExistsException() Map status = ImmutableMap.of("count", 42); handler.insert(entryId, DateTimes.of("2014-01-01"), "test", entry, true, status, "type", "group"); - Assert.assertThrows( - EntryExistsException.class, + + DruidException exception = Assert.assertThrows( + DruidException.class, () -> handler.insert(entryId, DateTimes.of("2014-01-01"), "test", entry, true, status, "type", "group") ); + Assert.assertEquals("invalidInput", exception.getErrorCode()); + Assert.assertEquals("Task [abcd] already exists", exception.getMessage()); } @Test @@ -341,7 +324,7 @@ public void testLocks() } @Test - public void testReplaceLock() throws EntryExistsException + public void testReplaceLock() { final String entryId = "ABC123"; Map entry = ImmutableMap.of("a", 1); @@ -371,7 +354,7 @@ public void testReplaceLock() throws EntryExistsException } @Test - public void testGetLockId() throws EntryExistsException + public void testGetLockId() { final String entryId = "ABC123"; Map entry = ImmutableMap.of("a", 1); diff --git a/server/src/test/java/org/apache/druid/metadata/SqlSegmentsMetadataManagerTest.java b/server/src/test/java/org/apache/druid/metadata/SqlSegmentsMetadataManagerTest.java index 7e491325110b..a7128ce27143 100644 --- a/server/src/test/java/org/apache/druid/metadata/SqlSegmentsMetadataManagerTest.java +++ b/server/src/test/java/org/apache/druid/metadata/SqlSegmentsMetadataManagerTest.java @@ -52,7 +52,6 @@ import org.junit.Test; import java.io.IOException; -import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -401,7 +400,10 @@ public void testGetUnusedSegmentIntervals() throws IOException "2017-10-15T20:19:12.565Z" ); publishUnusedSegments(koalaSegment1); - updateUsedStatusLastUpdated(koalaSegment1, DateTimes.nowUtc().minus(Duration.standardHours(2))); + derbyConnectorRule.segments().updateUsedStatusLastUpdated( + koalaSegment1.getId().toString(), + DateTimes.nowUtc().minus(Duration.standardHours(2)) + ); // Publish an unused segment with used_status_last_updated 2 days ago final DataSegment koalaSegment2 = createSegment( @@ -410,7 +412,10 @@ public void testGetUnusedSegmentIntervals() throws IOException "2017-10-15T20:19:12.565Z" ); publishUnusedSegments(koalaSegment2); - updateUsedStatusLastUpdated(koalaSegment2, DateTimes.nowUtc().minus(Duration.standardDays(2))); + derbyConnectorRule.segments().updateUsedStatusLastUpdated( + koalaSegment2.getId().toString(), + DateTimes.nowUtc().minus(Duration.standardDays(2)) + ); // Publish an unused segment and set used_status_last_updated to null final DataSegment koalaSegment3 = createSegment( @@ -585,6 +590,156 @@ public void testMarkAsUsedNonOvershadowedSegments() throws Exception ); } + @Test + public void testMarkAsUsedNonOvershadowedSegmentsInEternityIntervalWithVersions() throws Exception + { + publishWikiSegments(); + sqlSegmentsMetadataManager.startPollingDatabasePeriodically(); + sqlSegmentsMetadataManager.poll(); + Assert.assertTrue(sqlSegmentsMetadataManager.isPollingDatabasePeriodically()); + + final DataSegment koalaSegment1 = createSegment( + DS.KOALA, + "2017-10-15T00:00:00.000/2017-10-17T00:00:00.000", + "2017-10-15T20:19:12.565Z" + ); + + final DataSegment koalaSegment2 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + "2017-10-16T20:19:12.565Z" + ); + + // Overshadowed by koalaSegment2 + final DataSegment koalaSegment3 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + "2017-10-15T20:19:12.565Z" + ); + + publishUnusedSegments(koalaSegment1, koalaSegment2, koalaSegment3); + + sqlSegmentsMetadataManager.poll(); + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + Assert.assertEquals( + 2, + sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + DS.KOALA, + Intervals.ETERNITY, + ImmutableList.of("2017-10-15T20:19:12.565Z", "2017-10-16T20:19:12.565Z") + ) + ); + sqlSegmentsMetadataManager.poll(); + + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2, koalaSegment1, koalaSegment2), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsInFiniteIntervalWithVersions() throws Exception + { + publishWikiSegments(); + sqlSegmentsMetadataManager.startPollingDatabasePeriodically(); + sqlSegmentsMetadataManager.poll(); + Assert.assertTrue(sqlSegmentsMetadataManager.isPollingDatabasePeriodically()); + + final DataSegment koalaSegment1 = createSegment( + DS.KOALA, + "2017-10-15T00:00:00.000/2017-10-17T00:00:00.000", + "2017-10-15T20:19:12.565Z" + ); + + final DataSegment koalaSegment2 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + "2017-10-16T20:19:12.565Z" + ); + + // Overshadowed by koalaSegment2 + final DataSegment koalaSegment3 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + "2017-10-15T20:19:12.565Z" + ); + + publishUnusedSegments(koalaSegment1, koalaSegment2, koalaSegment3); + + sqlSegmentsMetadataManager.poll(); + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + Assert.assertEquals( + 2, + sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + DS.KOALA, + Intervals.of("2017-10-15/2017-10-18"), + ImmutableList.of("2017-10-15T20:19:12.565Z", "2017-10-16T20:19:12.565Z") + ) + ); + sqlSegmentsMetadataManager.poll(); + + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2, koalaSegment1, koalaSegment2), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonExistentVersions() throws Exception + { + publishWikiSegments(); + sqlSegmentsMetadataManager.startPollingDatabasePeriodically(); + sqlSegmentsMetadataManager.poll(); + Assert.assertTrue(sqlSegmentsMetadataManager.isPollingDatabasePeriodically()); + + final DataSegment koalaSegment1 = createSegment( + DS.KOALA, + "2017-10-15T00:00:00.000/2017-10-17T00:00:00.000", + "2017-10-15T20:19:12.565Z" + ); + + final DataSegment koalaSegment2 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + "2017-10-16T20:19:12.565Z" + ); + + // Overshadowed by koalaSegment2 + final DataSegment koalaSegment3 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + "2017-10-15T20:19:12.565Z" + ); + + publishUnusedSegments(koalaSegment1, koalaSegment2, koalaSegment3); + + sqlSegmentsMetadataManager.poll(); + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + Assert.assertEquals( + 0, + sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + DS.KOALA, + Intervals.ETERNITY, + ImmutableList.of("foo", "bar") + ) + ); + sqlSegmentsMetadataManager.poll(); + + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + } + @Test public void testMarkAsUsedNonOvershadowedSegmentsInvalidDataSource() throws Exception { @@ -679,7 +834,7 @@ public void testMarkAsUsedNonOvershadowedSegmentsInInterval() throws IOException ); // 2 out of 3 segments match the interval - Assert.assertEquals(2, sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(DS.KOALA, theInterval)); + Assert.assertEquals(2, sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(DS.KOALA, theInterval, null)); sqlSegmentsMetadataManager.poll(); Assert.assertEquals( @@ -727,7 +882,7 @@ public void testMarkAsUsedNonOvershadowedSegmentsInIntervalWithOverlappingInterv ); // 1 out of 3 segments match the interval, other 2 overlap, only the segment fully contained will be marked unused - Assert.assertEquals(1, sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(DS.KOALA, theInterval)); + Assert.assertEquals(1, sqlSegmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(DS.KOALA, theInterval, null)); sqlSegmentsMetadataManager.poll(); Assert.assertEquals( @@ -783,7 +938,7 @@ public void testMarkAsUnusedSegmentsInInterval() throws IOException final Interval theInterval = Intervals.of("2017-10-15T00:00:00.000/2017-10-18T00:00:00.000"); // 2 out of 3 segments match the interval - Assert.assertEquals(2, sqlSegmentsMetadataManager.markAsUnusedSegmentsInInterval(DS.KOALA, theInterval)); + Assert.assertEquals(2, sqlSegmentsMetadataManager.markAsUnusedSegmentsInInterval(DS.KOALA, theInterval, null)); sqlSegmentsMetadataManager.poll(); Assert.assertEquals( @@ -792,6 +947,104 @@ public void testMarkAsUnusedSegmentsInInterval() throws IOException ); } + @Test + public void testMarkAsUnusedSegmentsInIntervalAndVersions() throws IOException + { + publishWikiSegments(); + sqlSegmentsMetadataManager.startPollingDatabasePeriodically(); + sqlSegmentsMetadataManager.poll(); + Assert.assertTrue(sqlSegmentsMetadataManager.isPollingDatabasePeriodically()); + + final DateTime now = DateTimes.nowUtc(); + final String v1 = now.toString(); + final String v2 = now.plus(Duration.standardDays(1)).toString(); + + final DataSegment koalaSegment1 = createSegment( + DS.KOALA, + "2017-10-15T00:00:00.000/2017-10-16T00:00:00.000", + v1 + ); + final DataSegment koalaSegment2 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + v2 + ); + final DataSegment koalaSegment3 = createSegment( + DS.KOALA, + "2017-10-19T00:00:00.000/2017-10-20T00:00:00.000", + v2 + ); + + publisher.publishSegment(koalaSegment1); + publisher.publishSegment(koalaSegment2); + publisher.publishSegment(koalaSegment3); + final Interval theInterval = Intervals.of("2017-10-15/2017-10-18"); + + Assert.assertEquals( + 2, + sqlSegmentsMetadataManager.markAsUnusedSegmentsInInterval( + DS.KOALA, + theInterval, + ImmutableList.of(v1, v2) + ) + ); + + sqlSegmentsMetadataManager.poll(); + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2, koalaSegment3), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + } + + @Test + public void testMarkAsUnusedSegmentsInIntervalAndNonExistentVersions() throws IOException + { + publishWikiSegments(); + sqlSegmentsMetadataManager.startPollingDatabasePeriodically(); + sqlSegmentsMetadataManager.poll(); + Assert.assertTrue(sqlSegmentsMetadataManager.isPollingDatabasePeriodically()); + + final DateTime now = DateTimes.nowUtc(); + final String v1 = now.toString(); + final String v2 = now.plus(Duration.standardDays(1)).toString(); + + final DataSegment koalaSegment1 = createSegment( + DS.KOALA, + "2017-10-15T00:00:00.000/2017-10-16T00:00:00.000", + v1 + ); + final DataSegment koalaSegment2 = createSegment( + DS.KOALA, + "2017-10-17T00:00:00.000/2017-10-18T00:00:00.000", + v2 + ); + final DataSegment koalaSegment3 = createSegment( + DS.KOALA, + "2017-10-19T00:00:00.000/2017-10-20T00:00:00.000", + v2 + ); + + publisher.publishSegment(koalaSegment1); + publisher.publishSegment(koalaSegment2); + publisher.publishSegment(koalaSegment3); + final Interval theInterval = Intervals.of("2017-10-15/2017-10-18"); + + Assert.assertEquals( + 0, + sqlSegmentsMetadataManager.markAsUnusedSegmentsInInterval( + DS.KOALA, + theInterval, + ImmutableList.of("foo", "bar", "baz") + ) + ); + + sqlSegmentsMetadataManager.poll(); + Assert.assertEquals( + ImmutableSet.of(wikiSegment1, wikiSegment2, koalaSegment1, koalaSegment2, koalaSegment3), + ImmutableSet.copyOf(sqlSegmentsMetadataManager.iterateAllUsedSegments()) + ); + } + @Test public void testMarkAsUnusedSegmentsInIntervalWithOverlappingInterval() throws IOException { @@ -818,7 +1071,7 @@ public void testMarkAsUnusedSegmentsInIntervalWithOverlappingInterval() throws I final Interval theInterval = Intervals.of("2017-10-16T00:00:00.000/2017-10-20T00:00:00.000"); // 1 out of 3 segments match the interval, other 2 overlap, only the segment fully contained will be marked unused - Assert.assertEquals(1, sqlSegmentsMetadataManager.markAsUnusedSegmentsInInterval(DS.KOALA, theInterval)); + Assert.assertEquals(1, sqlSegmentsMetadataManager.markAsUnusedSegmentsInInterval(DS.KOALA, theInterval, null)); sqlSegmentsMetadataManager.poll(); Assert.assertEquals( @@ -904,54 +1157,35 @@ public void testPopulateUsedFlagLastUpdated() throws IOException Assert.assertEquals(0, getCountOfRowsWithLastUsedNull()); } - private void updateSegmentPayload(DataSegment segment, byte[] payload) - { - executeUpdate( - "UPDATE %1$s SET PAYLOAD = ? WHERE ID = ?", - payload, - segment.getId().toString() - ); - } - private int getCountOfRowsWithLastUsedNull() { return derbyConnectorRule.getConnector().retryWithHandle( handle -> handle.select( StringUtils.format( "SELECT ID FROM %1$s WHERE USED_STATUS_LAST_UPDATED IS NULL", - getSegmentsTable() + derbyConnectorRule.segments().getTableName() ) ).size() ); } - private void updateUsedStatusLastUpdated(DataSegment segment, DateTime newValue) + private void updateSegmentPayload(DataSegment segment, byte[] payload) { - executeUpdate( - "UPDATE %1$s SET USED_STATUS_LAST_UPDATED = ? WHERE ID = ?", - newValue.toString(), + derbyConnectorRule.segments().update( + "UPDATE %1$s SET PAYLOAD = ? WHERE ID = ?", + payload, segment.getId().toString() ); } private void updateUsedStatusLastUpdatedToNull(DataSegment segment) { - executeUpdate( + derbyConnectorRule.segments().update( "UPDATE %1$s SET USED_STATUS_LAST_UPDATED = NULL WHERE ID = ?", segment.getId().toString() ); } - private void executeUpdate(String sqlFormat, Object... args) - { - derbyConnectorRule.getConnector().retryWithHandle( - handle -> handle.update( - StringUtils.format(sqlFormat, getSegmentsTable()), - args - ) - ); - } - /** * Alters the column used_status_last_updated to be nullable. This is used to * test backward compatibility with versions of Druid without this column @@ -959,14 +1193,8 @@ private void executeUpdate(String sqlFormat, Object... args) */ private void allowUsedFlagLastUpdatedToBeNullable() { - executeUpdate("ALTER TABLE %1$s ALTER COLUMN USED_STATUS_LAST_UPDATED NULL"); - } - - private String getSegmentsTable() - { - return derbyConnectorRule.metadataTablesConfigSupplier() - .get() - .getSegmentsTable() - .toUpperCase(Locale.ENGLISH); + derbyConnectorRule.segments().update( + "ALTER TABLE %1$s ALTER COLUMN USED_STATUS_LAST_UPDATED NULL" + ); } } diff --git a/server/src/test/java/org/apache/druid/metadata/TestDerbyConnector.java b/server/src/test/java/org/apache/druid/metadata/TestDerbyConnector.java index e5460ce402b4..7ec3152ceedd 100644 --- a/server/src/test/java/org/apache/druid/metadata/TestDerbyConnector.java +++ b/server/src/test/java/org/apache/druid/metadata/TestDerbyConnector.java @@ -23,12 +23,17 @@ import com.google.common.base.Suppliers; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.metadata.storage.derby.DerbyConnector; +import org.joda.time.DateTime; import org.junit.Assert; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.rules.ExternalResource; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.exceptions.UnableToObtainConnectionException; import java.sql.SQLException; +import java.util.Locale; import java.util.UUID; public class TestDerbyConnector extends DerbyConnector @@ -135,5 +140,73 @@ public Supplier metadataTablesConfigSupplier() { return dbTables; } + + public SegmentsTable segments() + { + return new SegmentsTable(this); + } + } + + /** + * A wrapper class for updating the segments table. + */ + public static class SegmentsTable + { + private final DerbyConnectorRule rule; + + public SegmentsTable(DerbyConnectorRule rule) + { + this.rule = rule; + } + + /** + * Updates the segments table with the supplied SQL query format and arguments. + * + * @param sqlFormat the SQL query format with %s placeholder for the table name and ? for each query {@code args} + * @param args the arguments to be substituted into the SQL query + * @return the number of rows affected by the update operation + */ + public int update(String sqlFormat, Object... args) + { + return this.rule.getConnector().retryWithHandle( + handle -> handle.update( + StringUtils.format(sqlFormat, getTableName()), + args + ) + ); + } + + public int updateUsedStatusLastUpdated(String segmentId, DateTime lastUpdatedTime) + { + return update( + "UPDATE %1$s SET USED_STATUS_LAST_UPDATED = ? WHERE ID = ?", + lastUpdatedTime.toString(), + segmentId + ); + } + + public String getTableName() + { + return this.rule.metadataTablesConfigSupplier() + .get() + .getSegmentsTable() + .toUpperCase(Locale.ENGLISH); + } + } + + public static class DerbyConnectorRule5 extends DerbyConnectorRule implements BeforeAllCallback, AfterAllCallback + { + + @Override + public void beforeAll(ExtensionContext context) + { + before(); + } + + @Override + public void afterAll(ExtensionContext context) + { + after(); + } } } diff --git a/server/src/test/java/org/apache/druid/server/coordinator/duty/KillUnusedSegmentsTest.java b/server/src/test/java/org/apache/druid/server/coordinator/duty/KillUnusedSegmentsTest.java index 81bc6c8c490a..aa37913734ee 100644 --- a/server/src/test/java/org/apache/druid/server/coordinator/duty/KillUnusedSegmentsTest.java +++ b/server/src/test/java/org/apache/druid/server/coordinator/duty/KillUnusedSegmentsTest.java @@ -37,7 +37,6 @@ import org.apache.druid.java.util.common.CloseableIterators; import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.java.util.common.Intervals; -import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.parsers.CloseableIterator; import org.apache.druid.metadata.SQLMetadataSegmentPublisher; import org.apache.druid.metadata.SegmentsMetadataManagerConfig; @@ -69,7 +68,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; public class KillUnusedSegmentsTest @@ -776,7 +774,7 @@ private void createAndAddUnusedSegment( throw new RuntimeException(e); } sqlSegmentsMetadataManager.markSegmentsAsUnused(ImmutableSet.of(segment.getId())); - updateUsedStatusLastUpdated(segment, lastUpdatedTime); + derbyConnectorRule.segments().updateUsedStatusLastUpdated(segment.getId().toString(), lastUpdatedTime); } private DataSegment createSegment(final String dataSource, final Interval interval, final String version) @@ -925,25 +923,4 @@ void deleteLastKillTaskId(final String dataSource) observedDatasourceToLastKillTaskId.remove(dataSource); } } - - private void updateUsedStatusLastUpdated(DataSegment segment, DateTime lastUpdatedTime) - { - derbyConnectorRule.getConnector().retryWithHandle( - handle -> handle.update( - StringUtils.format( - "UPDATE %1$s SET USED_STATUS_LAST_UPDATED = ? WHERE ID = ?", getSegmentsTable() - ), - lastUpdatedTime.toString(), - segment.getId().toString() - ) - ); - } - - private String getSegmentsTable() - { - return derbyConnectorRule.metadataTablesConfigSupplier() - .get() - .getSegmentsTable() - .toUpperCase(Locale.ENGLISH); - } } diff --git a/server/src/test/java/org/apache/druid/server/coordinator/simulate/TestSegmentsMetadataManager.java b/server/src/test/java/org/apache/druid/server/coordinator/simulate/TestSegmentsMetadataManager.java index adf12ae70543..d255d0abc7d4 100644 --- a/server/src/test/java/org/apache/druid/server/coordinator/simulate/TestSegmentsMetadataManager.java +++ b/server/src/test/java/org/apache/druid/server/coordinator/simulate/TestSegmentsMetadataManager.java @@ -87,7 +87,7 @@ public int markAsUsedAllNonOvershadowedSegmentsInDataSource(String dataSource) } @Override - public int markAsUsedNonOvershadowedSegmentsInInterval(String dataSource, Interval interval) + public int markAsUsedNonOvershadowedSegmentsInInterval(String dataSource, Interval interval, @Nullable List versions) { return 0; } @@ -116,7 +116,7 @@ public int markAsUnusedAllSegmentsInDataSource(String dataSource) } @Override - public int markAsUnusedSegmentsInInterval(String dataSource, Interval interval) + public int markAsUnusedSegmentsInInterval(String dataSource, Interval interval, @Nullable List versions) { return 0; } diff --git a/server/src/test/java/org/apache/druid/server/http/DataSourcesResourceTest.java b/server/src/test/java/org/apache/druid/server/http/DataSourcesResourceTest.java index 69fe23a75b5d..f6bccbeb7da4 100644 --- a/server/src/test/java/org/apache/druid/server/http/DataSourcesResourceTest.java +++ b/server/src/test/java/org/apache/druid/server/http/DataSourcesResourceTest.java @@ -19,6 +19,8 @@ package org.apache.druid.server.http; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -36,6 +38,7 @@ import org.apache.druid.client.ImmutableSegmentLoadInfo; import org.apache.druid.client.SegmentLoadInfo; import org.apache.druid.error.DruidExceptionMatcher; +import org.apache.druid.jackson.DefaultObjectMapper; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.metadata.MetadataRuleManager; import org.apache.druid.metadata.SegmentsMetadataManager; @@ -320,7 +323,7 @@ public void testFullGetTheDataSource() EasyMock.replay(inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); - Response response = dataSourcesResource.getDataSource("datasource1", "full"); + Response response = dataSourcesResource.getQueryableDataSource("datasource1", "full"); ImmutableDruidDataSource result = (ImmutableDruidDataSource) response.getEntity(); Assert.assertEquals(200, response.getStatus()); ImmutableDruidDataSourceTestUtils.assertEquals(dataSource1.toImmutableDruidDataSource(), result); @@ -335,7 +338,7 @@ public void testNullGetTheDataSource() EasyMock.replay(inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); - Assert.assertEquals(204, dataSourcesResource.getDataSource("none", null).getStatus()); + Assert.assertEquals(204, dataSourcesResource.getQueryableDataSource("none", null).getStatus()); EasyMock.verify(inventoryView, server); } @@ -352,7 +355,7 @@ public void testSimpleGetTheDataSource() EasyMock.replay(inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); - Response response = dataSourcesResource.getDataSource("datasource1", null); + Response response = dataSourcesResource.getQueryableDataSource("datasource1", null); Assert.assertEquals(200, response.getStatus()); Map> result = (Map>) response.getEntity(); Assert.assertEquals(1, ((Map) (result.get("tiers").get(null))).get("segmentCount")); @@ -385,7 +388,7 @@ public void testSimpleGetTheDataSourceManyTiers() EasyMock.replay(inventoryView, server, server2, server3); DataSourcesResource dataSourcesResource = createResource(); - Response response = dataSourcesResource.getDataSource("datasource1", null); + Response response = dataSourcesResource.getQueryableDataSource("datasource1", null); Assert.assertEquals(200, response.getStatus()); Map> result = (Map>) response.getEntity(); Assert.assertEquals(2, ((Map) (result.get("tiers").get("cold"))).get("segmentCount")); @@ -423,7 +426,7 @@ public void testSimpleGetTheDataSourceWithReplicatedSegments() EasyMock.replay(inventoryView); DataSourcesResource dataSourcesResource = createResource(); - Response response = dataSourcesResource.getDataSource("datasource1", null); + Response response = dataSourcesResource.getQueryableDataSource("datasource1", null); Assert.assertEquals(200, response.getStatus()); Map> result1 = (Map>) response.getEntity(); Assert.assertEquals(2, ((Map) (result1.get("tiers").get("tier1"))).get("segmentCount")); @@ -438,7 +441,7 @@ public void testSimpleGetTheDataSourceWithReplicatedSegments() Assert.assertEquals(30L, result1.get("segments").get("size")); Assert.assertEquals(60L, result1.get("segments").get("replicatedSize")); - response = dataSourcesResource.getDataSource("datasource2", null); + response = dataSourcesResource.getQueryableDataSource("datasource2", null); Assert.assertEquals(200, response.getStatus()); Map> result2 = (Map>) response.getEntity(); Assert.assertEquals(1, ((Map) (result2.get("tiers").get("tier1"))).get("segmentCount")); @@ -733,19 +736,57 @@ public void testMarkSegmentAsUsedNoChange() @Test public void testMarkAsUsedNonOvershadowedSegmentsInterval() { - DruidDataSource dataSource = new DruidDataSource("datasource1", new HashMap<>()); Interval interval = Intervals.of("2010-01-22/P1D"); - int numUpdatedSegments = - segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(EasyMock.eq("datasource1"), EasyMock.eq(interval)); + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + EasyMock.eq("datasource1"), EasyMock.eq(interval), EasyMock.isNull() + ); EasyMock.expect(numUpdatedSegments).andReturn(3).once(); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(interval, null) + new DataSourcesResource.SegmentsToUpdateFilter(interval, null, null) + ); + Assert.assertEquals(200, response.getStatus()); + EasyMock.verify(segmentsMetadataManager, inventoryView, server); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsIntervalWithVersions() + { + Interval interval = Intervals.of("2010-01-22/P1D"); + + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + EasyMock.eq("datasource1"), EasyMock.eq(interval), EasyMock.eq(ImmutableList.of("v0")) + ); + EasyMock.expect(numUpdatedSegments).andReturn(3).once(); + EasyMock.replay(segmentsMetadataManager, inventoryView, server); + + DataSourcesResource dataSourcesResource = createResource(); + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(interval, null, ImmutableList.of("v0")) + ); + Assert.assertEquals(200, response.getStatus()); + EasyMock.verify(segmentsMetadataManager, inventoryView, server); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsIntervalWithNonExistentVersion() + { + Interval interval = Intervals.of("2010-01-22/P1D"); + + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + EasyMock.eq("datasource1"), EasyMock.eq(interval), EasyMock.eq(ImmutableList.of("foo")) + ); + EasyMock.expect(numUpdatedSegments).andReturn(0).once(); + EasyMock.replay(segmentsMetadataManager, inventoryView, server); + + DataSourcesResource dataSourcesResource = createResource(); + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(interval, null, ImmutableList.of("foo")) ); Assert.assertEquals(200, response.getStatus()); EasyMock.verify(segmentsMetadataManager, inventoryView, server); @@ -754,20 +795,18 @@ public void testMarkAsUsedNonOvershadowedSegmentsInterval() @Test public void testMarkAsUsedNonOvershadowedSegmentsIntervalNoneUpdated() { - DruidDataSource dataSource = new DruidDataSource("datasource1", new HashMap<>()); Interval interval = Intervals.of("2010-01-22/P1D"); - int numUpdatedSegments = - segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(EasyMock.eq("datasource1"), EasyMock.eq(interval)); + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + EasyMock.eq("datasource1"), EasyMock.eq(interval), EasyMock.isNull() + ); EasyMock.expect(numUpdatedSegments).andReturn(0).once(); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(interval, null) + new DataSourcesResource.SegmentsToUpdateFilter(interval, null, null) ); Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); EasyMock.verify(segmentsMetadataManager, inventoryView, server); @@ -776,20 +815,18 @@ public void testMarkAsUsedNonOvershadowedSegmentsIntervalNoneUpdated() @Test public void testMarkAsUsedNonOvershadowedSegmentsSet() { - DruidDataSource dataSource = new DruidDataSource("datasource1", new HashMap<>()); Set segmentIds = ImmutableSet.of(dataSegmentList.get(1).getId().toString()); - int numUpdatedSegments = - segmentsMetadataManager.markAsUsedNonOvershadowedSegments(EasyMock.eq("datasource1"), EasyMock.eq(segmentIds)); + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegments( + EasyMock.eq("datasource1"), EasyMock.eq(segmentIds) + ); EasyMock.expect(numUpdatedSegments).andReturn(3).once(); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(null, segmentIds) + new DataSourcesResource.SegmentsToUpdateFilter(null, segmentIds, null) ); Assert.assertEquals(200, response.getStatus()); EasyMock.verify(segmentsMetadataManager, inventoryView, server); @@ -798,20 +835,18 @@ public void testMarkAsUsedNonOvershadowedSegmentsSet() @Test public void testMarkAsUsedNonOvershadowedSegmentsIntervalException() { - DruidDataSource dataSource = new DruidDataSource("datasource1", new HashMap<>()); Interval interval = Intervals.of("2010-01-22/P1D"); - int numUpdatedSegments = - segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval(EasyMock.eq("datasource1"), EasyMock.eq(interval)); + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + EasyMock.eq("datasource1"), EasyMock.eq(interval), EasyMock.isNull() + ); EasyMock.expect(numUpdatedSegments).andThrow(new RuntimeException("Error!")).once(); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(interval, null) + new DataSourcesResource.SegmentsToUpdateFilter(interval, null, null) ); Assert.assertEquals(500, response.getStatus()); EasyMock.verify(segmentsMetadataManager, inventoryView, server); @@ -820,65 +855,164 @@ public void testMarkAsUsedNonOvershadowedSegmentsIntervalException() @Test public void testMarkAsUsedNonOvershadowedSegmentsNoDataSource() { - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(null).once(); + Interval interval = Intervals.of("2010-01-22/P1D"); + int numUpdatedSegments = segmentsMetadataManager.markAsUsedNonOvershadowedSegmentsInInterval( + EasyMock.eq("datasource1"), EasyMock.eq(interval), EasyMock.isNull() + ); + EasyMock.expect(numUpdatedSegments).andReturn(0).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(Intervals.of("2010-01-22/P1D"), null) + new DataSourcesResource.SegmentsToUpdateFilter(Intervals.of("2010-01-22/P1D"), null, null) ); - Assert.assertEquals(204, response.getStatus()); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); EasyMock.verify(segmentsMetadataManager); } @Test - public void testMarkAsUsedNonOvershadowedSegmentsInvalidPayloadNoArguments() + public void testMarkAsUsedNonOvershadowedSegmentsWithNullIntervalAndSegmentIdsAndVersions() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(null, null, null) + ); + Assert.assertEquals(400, response.getStatus()); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullIntervalAndEmptySegmentIds() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter( + Intervals.of("2010-01-22/P1D"), ImmutableSet.of(), null + ) + ); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullInterval() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(Intervals.of("2010-01-22/P1D"), null, null) + ); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullIntervalAndSegmentIds() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(Intervals.of("2010-01-22/P1D"), ImmutableSet.of("segment1"), null) + ); + Assert.assertEquals(400, response.getStatus()); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullIntervalAndSegmentIdsAndVersions() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter( + Intervals.of("2020/2030"), ImmutableSet.of("seg1"), ImmutableList.of("v1", "v2") + ) + ); + Assert.assertEquals(400, response.getStatus()); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithEmptySegmentIds() { DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(null, null) + new DataSourcesResource.SegmentsToUpdateFilter(null, ImmutableSet.of(), null) ); Assert.assertEquals(400, response.getStatus()); } @Test - public void testMarkAsUsedNonOvershadowedSegmentsInvalidPayloadBothArguments() + public void testMarkAsUsedNonOvershadowedSegmentsWithEmptyVersions() { DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(Intervals.of("2010-01-22/P1D"), ImmutableSet.of()) + new DataSourcesResource.SegmentsToUpdateFilter(null, null, ImmutableList.of()) ); Assert.assertEquals(400, response.getStatus()); } @Test - public void testMarkAsUsedNonOvershadowedSegmentsInvalidPayloadEmptyArray() + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullVersions() { DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( "datasource1", - new DataSourcesResource.MarkDataSourceSegmentsPayload(null, ImmutableSet.of()) + new DataSourcesResource.SegmentsToUpdateFilter(null, null, ImmutableList.of("v1", "v2")) ); Assert.assertEquals(400, response.getStatus()); } @Test - public void testMarkAsUsedNonOvershadowedSegmentsNoPayload() + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullSegmentIdsAndVersions() { DataSourcesResource dataSourcesResource = createResource(); - Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments("datasource1", null); + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(null, ImmutableSet.of("segment1"), ImmutableList.of("v1", "v2")) + ); Assert.assertEquals(400, response.getStatus()); } + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullIntervalAndVersions() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(Intervals.ETERNITY, null, ImmutableList.of("v1", "v2")) + ); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); + } + + @Test + public void testMarkAsUsedNonOvershadowedSegmentsWithNonNullIntervalAndEmptyVersions() + { + DataSourcesResource dataSourcesResource = createResource(); + + Response response = dataSourcesResource.markAsUsedNonOvershadowedSegments( + "datasource1", + new DataSourcesResource.SegmentsToUpdateFilter(Intervals.ETERNITY, null, ImmutableList.of()) + ); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); + } + @Test public void testSegmentLoadChecksForVersion() { @@ -1019,17 +1153,16 @@ public void testMarkSegmentsAsUnused() .map(DataSegment::getId) .collect(Collectors.toSet()); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource1).once(); EasyMock.expect(segmentsMetadataManager.markSegmentsAsUnused(segmentIds)).andReturn(1).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload( + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter( null, segmentIds.stream() .map(SegmentId::toString) - .collect(Collectors.toSet()) + .collect(Collectors.toSet()), + null ); DataSourcesResource dataSourcesResource = createResource(); @@ -1050,17 +1183,16 @@ public void testMarkSegmentsAsUnusedNoChanges() .map(DataSegment::getId) .collect(Collectors.toSet()); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource1).once(); EasyMock.expect(segmentsMetadataManager.markSegmentsAsUnused(segmentIds)).andReturn(0).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload( + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter( null, segmentIds.stream() .map(SegmentId::toString) - .collect(Collectors.toSet()) + .collect(Collectors.toSet()), + null ); DataSourcesResource dataSourcesResource = createResource(); @@ -1081,23 +1213,21 @@ public void testMarkSegmentsAsUnusedException() .map(DataSegment::getId) .collect(Collectors.toSet()); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource1).once(); EasyMock.expect(segmentsMetadataManager.markSegmentsAsUnused(segmentIds)) .andThrow(new RuntimeException("Exception occurred")) .once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload( + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter( null, segmentIds.stream() .map(SegmentId::toString) - .collect(Collectors.toSet()) + .collect(Collectors.toSet()), + null ); - DataSourcesResource dataSourcesResource = - createResource(); + DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); Assert.assertEquals(500, response.getStatus()); Assert.assertNotNull(response.getEntity()); @@ -1108,15 +1238,12 @@ public void testMarkSegmentsAsUnusedException() public void testMarkAsUnusedSegmentsInInterval() { final Interval theInterval = Intervals.of("2010-01-01/P1D"); - final DruidDataSource dataSource1 = new DruidDataSource("datasource1", new HashMap<>()); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource1).once(); - EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval)).andReturn(1).once(); + EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval, null)).andReturn(1).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload(theInterval, null); + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(theInterval, null, null); DataSourcesResource dataSourcesResource = createResource(); prepareRequestForAudit(); @@ -1131,15 +1258,12 @@ public void testMarkAsUnusedSegmentsInInterval() public void testMarkAsUnusedSegmentsInIntervalNoChanges() { final Interval theInterval = Intervals.of("2010-01-01/P1D"); - final DruidDataSource dataSource1 = new DruidDataSource("datasource1", new HashMap<>()); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource1).once(); - EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval)).andReturn(0).once(); + EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval, null)).andReturn(0).once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload(theInterval, null); + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(theInterval, null, null); DataSourcesResource dataSourcesResource = createResource(); prepareRequestForAudit(); @@ -1153,49 +1277,116 @@ public void testMarkAsUnusedSegmentsInIntervalNoChanges() public void testMarkAsUnusedSegmentsInIntervalException() { final Interval theInterval = Intervals.of("2010-01-01/P1D"); - final DruidDataSource dataSource1 = new DruidDataSource("datasource1", new HashMap<>()); - EasyMock.expect(inventoryView.getInventory()).andReturn(ImmutableList.of(server)).once(); - EasyMock.expect(server.getDataSource("datasource1")).andReturn(dataSource1).once(); - EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval)) + EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval, null)) .andThrow(new RuntimeException("Exception occurred")) .once(); EasyMock.replay(segmentsMetadataManager, inventoryView, server); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload(theInterval, null); + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(theInterval, null, null); - DataSourcesResource dataSourcesResource = - createResource(); + DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); Assert.assertEquals(500, response.getStatus()); Assert.assertNotNull(response.getEntity()); EasyMock.verify(segmentsMetadataManager, inventoryView, server); } + @Test + public void testMarkAsUnusedSegmentsInIntervalNoDataSource() + { + final Interval theInterval = Intervals.of("2010-01-01/P1D"); + EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval, null)) + .andReturn(0).once(); + EasyMock.replay(segmentsMetadataManager, inventoryView, server); + + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(theInterval, null, null); + DataSourcesResource dataSourcesResource = createResource(); + prepareRequestForAudit(); + + Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); + EasyMock.verify(segmentsMetadataManager); + } + + @Test + public void testMarkAsUnusedSegmentsInIntervalWithVersions() + { + final Interval theInterval = Intervals.of("2010-01-01/P1D"); + EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval, ImmutableList.of("v1"))) + .andReturn(2).once(); + EasyMock.replay(segmentsMetadataManager, inventoryView, server); + + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(theInterval, null, ImmutableList.of("v1")); + DataSourcesResource dataSourcesResource = createResource(); + prepareRequestForAudit(); + + Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 2), response.getEntity()); + EasyMock.verify(segmentsMetadataManager); + } + + @Test + public void testMarkAsUnusedSegmentsInIntervalWithNonExistentVersion() + { + final Interval theInterval = Intervals.of("2010-01-01/P1D"); + EasyMock.expect(segmentsMetadataManager.markAsUnusedSegmentsInInterval("datasource1", theInterval, ImmutableList.of("foo"))) + .andReturn(0).once(); + EasyMock.replay(segmentsMetadataManager, inventoryView, server); + + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(theInterval, null, ImmutableList.of("foo")); + DataSourcesResource dataSourcesResource = createResource(); + prepareRequestForAudit(); + + Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); + EasyMock.verify(segmentsMetadataManager); + } + + @Test + public void testSegmentsToUpdateFilterSerde() throws JsonProcessingException + { + final ObjectMapper mapper = new DefaultObjectMapper(); + final String payload = "{\"interval\":\"2023-01-01T00:00:00.000Z/2024-01-01T00:00:00.000Z\",\"segmentIds\":null,\"versions\":[\"v1\"]}"; + + final DataSourcesResource.SegmentsToUpdateFilter obj = + mapper.readValue(payload, DataSourcesResource.SegmentsToUpdateFilter.class); + Assert.assertEquals(Intervals.of("2023/2024"), obj.getInterval()); + Assert.assertEquals(ImmutableList.of("v1"), obj.getVersions()); + Assert.assertNull(obj.getSegmentIds()); + + Assert.assertEquals(payload, mapper.writeValueAsString(obj)); + } + @Test public void testMarkSegmentsAsUnusedNullPayload() { - DataSourcesResource dataSourcesResource = - createResource(); + DataSourcesResource dataSourcesResource = createResource(); Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", null, request); Assert.assertEquals(400, response.getStatus()); Assert.assertNotNull(response.getEntity()); Assert.assertEquals( - "Invalid request payload, either interval or segmentIds array must be specified", + "Invalid request payload. Specify either 'interval' or 'segmentIds', but not both." + + " Optionally, include 'versions' only when 'interval' is provided.", response.getEntity() ); } @Test - public void testMarkSegmentsAsUnusedInvalidPayload() + public void testMarkSegmentsAsUnusedWithNullIntervalAndSegmentIdsAndVersions() { - DataSourcesResource dataSourcesResource = - createResource(); + DataSourcesResource dataSourcesResource = createResource(); - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload(null, null); + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(null, null, null); Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); Assert.assertEquals(400, response.getStatus()); @@ -1203,17 +1394,16 @@ public void testMarkSegmentsAsUnusedInvalidPayload() } @Test - public void testMarkSegmentsAsUnusedInvalidPayloadBothArguments() + public void testMarkSegmentsAsUnusedWithNonNullIntervalAndEmptySegmentIds() { - DataSourcesResource dataSourcesResource = - createResource(); - - final DataSourcesResource.MarkDataSourceSegmentsPayload payload = - new DataSourcesResource.MarkDataSourceSegmentsPayload(Intervals.of("2010-01-01/P1D"), ImmutableSet.of()); - + DataSourcesResource dataSourcesResource = createResource(); + prepareRequestForAudit(); + final DataSourcesResource.SegmentsToUpdateFilter payload = + new DataSourcesResource.SegmentsToUpdateFilter(Intervals.of("2010-01-01/P1D"), ImmutableSet.of(), null); Response response = dataSourcesResource.markSegmentsAsUnused("datasource1", payload, request); - Assert.assertEquals(400, response.getStatus()); - Assert.assertNotNull(response.getEntity()); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(ImmutableMap.of("numChangedSegments", 0), response.getEntity()); } @Test diff --git a/processing/src/test/java/org/apache/druid/common/utils/ServletResourceUtilsTest.java b/server/src/test/java/org/apache/druid/server/http/ServletResourceUtilsTest.java similarity index 61% rename from processing/src/test/java/org/apache/druid/common/utils/ServletResourceUtilsTest.java rename to server/src/test/java/org/apache/druid/server/http/ServletResourceUtilsTest.java index 2469f599a585..0af66bf0df58 100644 --- a/processing/src/test/java/org/apache/druid/common/utils/ServletResourceUtilsTest.java +++ b/server/src/test/java/org/apache/druid/server/http/ServletResourceUtilsTest.java @@ -17,11 +17,18 @@ * under the License. */ -package org.apache.druid.common.utils; +package org.apache.druid.server.http; +import org.apache.druid.error.DruidException; +import org.apache.druid.error.DruidExceptionMatcher; +import org.apache.druid.error.ErrorResponse; +import org.apache.druid.error.InvalidInput; +import org.hamcrest.MatcherAssert; import org.junit.Assert; import org.junit.Test; +import javax.ws.rs.core.Response; + public class ServletResourceUtilsTest { @@ -40,4 +47,19 @@ public String toString() } }).get("error")); } + + @Test + public void testBuildErrorReponseFrom() + { + DruidException exception = InvalidInput.exception("Invalid value of [%s]", "inputKey"); + Response response = ServletResourceUtils.buildErrorResponseFrom(exception); + Assert.assertEquals(exception.getStatusCode(), response.getStatus()); + + Object entity = response.getEntity(); + Assert.assertTrue(entity instanceof ErrorResponse); + MatcherAssert.assertThat( + ((ErrorResponse) entity).getUnderlyingException(), + DruidExceptionMatcher.invalidInput().expectMessageIs("Invalid value of [inputKey]") + ); + } } diff --git a/sql/pom.xml b/sql/pom.xml index 1941df02be58..e4d920dfba03 100644 --- a/sql/pom.xml +++ b/sql/pom.xml @@ -203,8 +203,8 @@ test - org.junit.vintage - junit-vintage-engine + org.junit.jupiter + junit-jupiter-migrationsupport test @@ -212,6 +212,17 @@ junit-jupiter-params test + + org.junit.vintage + junit-vintage-engine + test + + + org.opentest4j + opentest4j + 1.3.0 + test + org.apache.commons commons-lang3 diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayContainsOperatorConversion.java b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayContainsOperatorConversion.java index 8bfc3aecb3e7..3d902ac068a6 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayContainsOperatorConversion.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayContainsOperatorConversion.java @@ -67,7 +67,7 @@ public class ArrayContainsOperatorConversion extends BaseExpressionDimFilterOper ) ) ) - .returnTypeInference(ReturnTypes.BOOLEAN) + .returnTypeInference(ReturnTypes.BOOLEAN_NULLABLE) .build(); public ArrayContainsOperatorConversion() @@ -191,6 +191,6 @@ public DimFilter toDruidFilter( } } } - return toExpressionFilter(plannerContext, getDruidFunctionName(), druidExpressions); + return toExpressionFilter(plannerContext, druidExpressions); } } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayOverlapOperatorConversion.java b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayOverlapOperatorConversion.java index 23cfcfaa4a45..be01d4beb3b6 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayOverlapOperatorConversion.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ArrayOverlapOperatorConversion.java @@ -67,7 +67,7 @@ public class ArrayOverlapOperatorConversion extends BaseExpressionDimFilterOpera ) ) ) - .returnTypeInference(ReturnTypes.BOOLEAN) + .returnTypeInference(ReturnTypes.BOOLEAN_NULLABLE) .build(); public ArrayOverlapOperatorConversion() @@ -111,7 +111,7 @@ public DimFilter toDruidFilter( complexExpr = leftExpr; } } else { - return toExpressionFilter(plannerContext, getDruidFunctionName(), druidExpressions); + return toExpressionFilter(plannerContext, druidExpressions); } final Expr expr = plannerContext.parseExpression(complexExpr.getExpression()); @@ -202,6 +202,6 @@ public DimFilter toDruidFilter( } } - return toExpressionFilter(plannerContext, getDruidFunctionName(), druidExpressions); + return toExpressionFilter(plannerContext, druidExpressions); } } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/BaseExpressionDimFilterOperatorConversion.java b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/BaseExpressionDimFilterOperatorConversion.java index d24ea8ea8911..afadf79770f8 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/BaseExpressionDimFilterOperatorConversion.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/BaseExpressionDimFilterOperatorConversion.java @@ -40,13 +40,17 @@ public BaseExpressionDimFilterOperatorConversion( super(operator, druidFunctionName); } - protected static DimFilter toExpressionFilter( + protected String getFilterExpression(List druidExpressions) + { + return DruidExpression.functionCall(getDruidFunctionName()).compile(druidExpressions); + } + + protected DimFilter toExpressionFilter( PlannerContext plannerContext, - String druidFunctionName, List druidExpressions ) { - final String filterExpr = DruidExpression.functionCall(druidFunctionName, druidExpressions); + final String filterExpr = getFilterExpression(druidExpressions); return new ExpressionDimFilter( filterExpr, diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/MultiValueStringOperatorConversions.java b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/MultiValueStringOperatorConversions.java index 5a32b06c544d..da07083774ce 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/MultiValueStringOperatorConversions.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/MultiValueStringOperatorConversions.java @@ -19,6 +19,7 @@ package org.apache.druid.sql.calcite.expression.builtin; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexNode; @@ -39,11 +40,13 @@ import org.apache.druid.sql.calcite.expression.DruidExpression; import org.apache.druid.sql.calcite.expression.Expressions; import org.apache.druid.sql.calcite.expression.OperatorConversions; +import org.apache.druid.sql.calcite.expression.PostAggregatorVisitor; import org.apache.druid.sql.calcite.expression.SqlOperatorConversion; import org.apache.druid.sql.calcite.planner.Calcites; import org.apache.druid.sql.calcite.planner.PlannerContext; import javax.annotation.Nullable; +import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -156,7 +159,7 @@ private static class Contains extends ArrayContainsOperatorConversion ) ) ) - .returnTypeInference(ReturnTypes.BOOLEAN) + .returnTypeInference(ReturnTypes.BOOLEAN_NULLABLE) .build(); @Override @@ -164,6 +167,53 @@ public SqlOperator calciteOperator() { return SQL_FUNCTION; } + + @Override + protected String getFilterExpression(List druidExpressions) + { + return super.getFilterExpression(harmonizeNullsMvdArg0OperandList(druidExpressions)); + } + + @Override + public DruidExpression toDruidExpression( + PlannerContext plannerContext, + RowSignature rowSignature, + RexNode rexNode + ) + { + return OperatorConversions.convertCall( + plannerContext, + rowSignature, + rexNode, + druidExpressions -> DruidExpression.ofFunctionCall( + Calcites.getColumnTypeForRelDataType(rexNode.getType()), + getDruidFunctionName(), + harmonizeNullsMvdArg0OperandList(druidExpressions) + ) + ); + } + + @Nullable + @Override + public DruidExpression toDruidExpressionWithPostAggOperands( + PlannerContext plannerContext, + RowSignature rowSignature, + RexNode rexNode, + PostAggregatorVisitor postAggregatorVisitor + ) + { + return OperatorConversions.convertCallWithPostAggOperands( + plannerContext, + rowSignature, + rexNode, + operands -> DruidExpression.ofFunctionCall( + Calcites.getColumnTypeForRelDataType(rexNode.getType()), + getDruidFunctionName(), + harmonizeNullsMvdArg0OperandList(operands) + ), + postAggregatorVisitor + ); + } } public static class Offset extends ArrayOffsetOperatorConversion @@ -309,11 +359,81 @@ public OrdinalOf() /** * Private: use singleton {@link #OVERLAP}. */ - private static class Overlap extends AliasedOperatorConversion + private static class Overlap extends ArrayOverlapOperatorConversion { - public Overlap() + private static final SqlFunction SQL_FUNCTION = OperatorConversions + .operatorBuilder("MV_OVERLAP") + .operandTypeChecker( + OperandTypes.sequence( + "'MV_OVERLAP(array, array)'", + OperandTypes.or( + OperandTypes.family(SqlTypeFamily.ARRAY), + OperandTypes.family(SqlTypeFamily.STRING) + ), + OperandTypes.or( + OperandTypes.family(SqlTypeFamily.ARRAY), + OperandTypes.family(SqlTypeFamily.STRING), + OperandTypes.family(SqlTypeFamily.NUMERIC) + ) + ) + ) + .returnTypeInference(ReturnTypes.BOOLEAN_NULLABLE) + .build(); + + @Override + public SqlOperator calciteOperator() + { + return SQL_FUNCTION; + } + + @Override + protected String getFilterExpression(List druidExpressions) { - super(new ArrayOverlapOperatorConversion(), "MV_OVERLAP"); + return super.getFilterExpression(harmonizeNullsMvdArg0OperandList(druidExpressions)); + } + + @Override + public DruidExpression toDruidExpression( + PlannerContext plannerContext, + RowSignature rowSignature, + RexNode rexNode + ) + { + return OperatorConversions.convertCall( + plannerContext, + rowSignature, + rexNode, + druidExpressions -> { + final List newArgs = harmonizeNullsMvdArg0OperandList(druidExpressions); + return DruidExpression.ofFunctionCall( + Calcites.getColumnTypeForRelDataType(rexNode.getType()), + getDruidFunctionName(), + newArgs + ); + } + ); + } + + @Nullable + @Override + public DruidExpression toDruidExpressionWithPostAggOperands( + PlannerContext plannerContext, + RowSignature rowSignature, + RexNode rexNode, + PostAggregatorVisitor postAggregatorVisitor + ) + { + return OperatorConversions.convertCallWithPostAggOperands( + plannerContext, + rowSignature, + rexNode, + operands -> DruidExpression.ofFunctionCall( + Calcites.getColumnTypeForRelDataType(rexNode.getType()), + getDruidFunctionName(), + harmonizeNullsMvdArg0OperandList(operands) + ), + postAggregatorVisitor + ); } } @@ -458,6 +578,28 @@ boolean isAllowList() } } + + private static List harmonizeNullsMvdArg0OperandList(List druidExpressions) + { + final List newArgs; + if (druidExpressions.get(0).isDirectColumnAccess()) { + // rewrite first argument to wrap with mv_harmonize_nulls function + newArgs = Lists.newArrayListWithCapacity(2); + newArgs.add( + 0, + DruidExpression.ofFunctionCall( + druidExpressions.get(0).getDruidType(), + "mv_harmonize_nulls", + Collections.singletonList(druidExpressions.get(0)) + ) + ); + newArgs.add(1, druidExpressions.get(1)); + } else { + newArgs = druidExpressions; + } + return newArgs; + } + private MultiValueStringOperatorConversions() { // no instantiation diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java index a51a3d713757..3fe049bdcb4d 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java @@ -253,7 +253,7 @@ public static class JsonQueryArrayOperatorConversion extends DirectOperatorConve SqlTypeFamily.CHARACTER ) ) - .returnTypeInference(NESTED_ARRAY_RETURN_TYPE_INFERENCE) + .returnTypeInference(NESTED_ARRAY_RETURN_TYPE_INFERENCE.andThen(SqlTypeTransforms.FORCE_NULLABLE)) .functionCategory(SqlFunctionCategory.SYSTEM) .build(); diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java index 4bed8c83fc54..715c5caaadc1 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidRexExecutor.java @@ -84,7 +84,11 @@ public void reduce( final RexNode literal; if (sqlTypeName == SqlTypeName.BOOLEAN) { - literal = rexBuilder.makeLiteral(exprResult.asBoolean(), constExp.getType(), true); + if (exprResult.valueOrDefault() == null) { + literal = rexBuilder.makeNullLiteral(constExp.getType()); + } else { + literal = rexBuilder.makeLiteral(exprResult.asBoolean(), constExp.getType(), true); + } } else if (sqlTypeName == SqlTypeName.DATE) { // It is possible for an expression to have a non-null String value but it can return null when parsed // as a primitive long/float/double. diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java index feefdbcca894..020d8b0ff880 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java @@ -574,9 +574,8 @@ public SqlEngine getEngine() /** * Checks if the current {@link SqlEngine} supports a particular feature. * - * When executing a specific query, use this method instead of - * {@link SqlEngine#featureAvailable(EngineFeature, PlannerContext)}, because it also verifies feature flags such as - * {@link #CTX_ENABLE_WINDOW_FNS}. + * When executing a specific query, use this method instead of {@link SqlEngine#featureAvailable(EngineFeature)}, + * because it also verifies feature flags such as {@link #CTX_ENABLE_WINDOW_FNS}. */ public boolean featureAvailable(final EngineFeature feature) { @@ -585,7 +584,11 @@ public boolean featureAvailable(final EngineFeature feature) // Short-circuit: feature requires context flag. return false; } - return engine.featureAvailable(feature, this); + if (feature == EngineFeature.TIME_BOUNDARY_QUERY && !queryContext().isTimeBoundaryPlanningEnabled()) { + // Short-circuit: feature requires context flag. + return false; + } + return engine.featureAvailable(feature); } public QueryMaker getQueryMaker() diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java index 0047cc0ad4d2..001d34639192 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java @@ -92,7 +92,7 @@ private Vertex processNodeWithInputs(Stack stack, List return newVertex.get(); } inputVertex = vertexFactory.createVertex( - PartialDruidQuery.createOuterQuery(((PDQVertex) inputVertex).partialDruidQuery), + PartialDruidQuery.createOuterQuery(((PDQVertex) inputVertex).partialDruidQuery, vertexFactory.plannerContext), ImmutableList.of(inputVertex) ); newVertex = inputVertex.extendWith(stack); diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidCorrelateUnnestRel.java b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidCorrelateUnnestRel.java index e9abd16f461f..ee58446b5ab3 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidCorrelateUnnestRel.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidCorrelateUnnestRel.java @@ -137,7 +137,7 @@ public DruidCorrelateUnnestRel withPartialQuery(PartialDruidQuery newQueryBuilde { return new DruidCorrelateUnnestRel( getCluster(), - newQueryBuilder.getTraitSet(getConvention()), + newQueryBuilder.getTraitSet(getConvention(), getPlannerContext()), correlateRel, newQueryBuilder, getPlannerContext() diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java index 677a697a52ae..e62b38696109 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java @@ -136,7 +136,7 @@ public DruidJoinQueryRel withPartialQuery(final PartialDruidQuery newQueryBuilde { return new DruidJoinQueryRel( getCluster(), - newQueryBuilder.getTraitSet(getConvention()), + newQueryBuilder.getTraitSet(getConvention(), getPlannerContext()), joinRel, leftFilter, newQueryBuilder, diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidOuterQueryRel.java b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidOuterQueryRel.java index da828ce61ba6..fd9bc03c0b75 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidOuterQueryRel.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidOuterQueryRel.java @@ -83,7 +83,7 @@ public static DruidOuterQueryRel create( { return new DruidOuterQueryRel( sourceRel.getCluster(), - partialQuery.getTraitSet(sourceRel.getConvention()), + partialQuery.getTraitSet(sourceRel.getConvention(), sourceRel.getPlannerContext()), sourceRel, partialQuery, sourceRel.getPlannerContext() @@ -101,7 +101,7 @@ public DruidOuterQueryRel withPartialQuery(final PartialDruidQuery newQueryBuild { return new DruidOuterQueryRel( getCluster(), - newQueryBuilder.getTraitSet(getConvention()), + newQueryBuilder.getTraitSet(getConvention(), getPlannerContext()), sourceRel, newQueryBuilder, getPlannerContext() diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQueryRel.java b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQueryRel.java index 364e3620bc91..c9d3e4f7360b 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQueryRel.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQueryRel.java @@ -171,7 +171,7 @@ public DruidQueryRel withPartialQuery(final PartialDruidQuery newQueryBuilder) { return new DruidQueryRel( getCluster(), - newQueryBuilder.getTraitSet(getConvention()), + newQueryBuilder.getTraitSet(getConvention(), getPlannerContext()), table, druidTable, getPlannerContext(), diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidUnionDataSourceRel.java b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidUnionDataSourceRel.java index f9304d19bf5e..c7e69cfe66d2 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidUnionDataSourceRel.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidUnionDataSourceRel.java @@ -106,7 +106,7 @@ public DruidUnionDataSourceRel withPartialQuery(final PartialDruidQuery newQuery { return new DruidUnionDataSourceRel( getCluster(), - newQueryBuilder.getTraitSet(getConvention()), + newQueryBuilder.getTraitSet(getConvention(), getPlannerContext()), unionRel, unionColumnNames, newQueryBuilder, diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/PartialDruidQuery.java b/sql/src/main/java/org/apache/druid/sql/calcite/rel/PartialDruidQuery.java index 1775cce3ae62..171bdeaa69d8 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/PartialDruidQuery.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/PartialDruidQuery.java @@ -47,6 +47,7 @@ import org.apache.druid.query.DataSource; import org.apache.druid.segment.column.RowSignature; import org.apache.druid.sql.calcite.planner.PlannerContext; +import org.apache.druid.sql.calcite.run.EngineFeature; import javax.annotation.Nullable; import java.util.ArrayList; @@ -172,12 +173,12 @@ public static PartialDruidQuery create(final RelNode inputRel) return new PartialDruidQuery(builderSupplier, inputRel, null, null, null, null, null, null, null, null, null); } - public static PartialDruidQuery createOuterQuery(final PartialDruidQuery inputQuery) + public static PartialDruidQuery createOuterQuery(final PartialDruidQuery inputQuery, PlannerContext plannerContext) { final RelNode inputRel = inputQuery.leafRel(); return create( inputRel.copy( - inputQuery.getTraitSet(inputRel.getConvention()), + inputQuery.getTraitSet(inputRel.getConvention(), plannerContext), inputRel.getInputs() ) ); @@ -457,7 +458,7 @@ public RelDataType getRowType() * * @param convention convention to include in the returned array */ - public RelTraitSet getTraitSet(final Convention convention) + public RelTraitSet getTraitSet(final Convention convention, final PlannerContext plannerContext) { final RelTraitSet leafRelTraits = leafRel().getTraitSet(); @@ -467,7 +468,9 @@ public RelTraitSet getTraitSet(final Convention convention) case AGGREGATE: case AGGREGATE_PROJECT: final RelCollation collation = leafRelTraits.getTrait(RelCollationTraitDef.INSTANCE); - if ((collation == null || collation.getFieldCollations().isEmpty()) && aggregate.getGroupSets().size() == 1) { + if (plannerContext.featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + && (collation == null || collation.getFieldCollations().isEmpty()) + && aggregate.getGroupSets().size() == 1) { // Druid sorts by grouping keys when grouping. Add the collation. // Note: [aggregate.getGroupSets().size() == 1] above means that collation isn't added for GROUPING SETS. final List sortFields = new ArrayList<>(); diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidRules.java b/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidRules.java index dfcf1652c0de..bb6c76fb5e9a 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidRules.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidRules.java @@ -200,7 +200,7 @@ public void onMatch(final RelOptRuleCall call) final DruidOuterQueryRel outerQueryRel = DruidOuterQueryRel.create( druidRel, - PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery()) + PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery(), druidRel.getPlannerContext()) .withAggregate(aggregate) ); if (outerQueryRel.isValidDruidQuery()) { @@ -223,7 +223,7 @@ public void onMatch(final RelOptRuleCall call) final DruidOuterQueryRel outerQueryRel = DruidOuterQueryRel.create( druidRel, - PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery()) + PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery(), druidRel.getPlannerContext()) .withWhereFilter(filter) ); if (outerQueryRel.isValidDruidQuery()) { @@ -246,7 +246,7 @@ public void onMatch(final RelOptRuleCall call) final DruidOuterQueryRel outerQueryRel = DruidOuterQueryRel.create( druidRel, - PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery()) + PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery(), druidRel.getPlannerContext()) .withSelectProject(filter) ); if (outerQueryRel.isValidDruidQuery()) { @@ -269,7 +269,7 @@ public void onMatch(final RelOptRuleCall call) final DruidOuterQueryRel outerQueryRel = DruidOuterQueryRel.create( druidRel, - PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery()) + PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery(), druidRel.getPlannerContext()) .withSort(sort) ); if (outerQueryRel.isValidDruidQuery()) { @@ -292,7 +292,7 @@ public void onMatch(final RelOptRuleCall call) final DruidOuterQueryRel outerQueryRel = DruidOuterQueryRel.create( druidRel, - PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery()) + PartialDruidQuery.createOuterQuery(druidRel.getPartialDruidQuery(), druidRel.getPlannerContext()) .withWindow(window) ); if (outerQueryRel.isValidDruidQuery()) { diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/run/EngineFeature.java b/sql/src/main/java/org/apache/druid/sql/calcite/run/EngineFeature.java index d68c8228406c..75f6de64442b 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/run/EngineFeature.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/run/EngineFeature.java @@ -20,10 +20,9 @@ package org.apache.druid.sql.calcite.run; import org.apache.druid.sql.calcite.external.ExternalDataSource; -import org.apache.druid.sql.calcite.planner.PlannerContext; /** - * Arguments to {@link SqlEngine#featureAvailable(EngineFeature, PlannerContext)}. + * Arguments to {@link SqlEngine#featureAvailable(EngineFeature)}. */ public enum EngineFeature { @@ -125,5 +124,16 @@ public enum EngineFeature /** * Queries can write to an external datasource using {@link org.apache.druid.sql.destination.ExportDestination} */ - WRITE_EXTERNAL_DATA; + WRITE_EXTERNAL_DATA, + + /** + * Whether GROUP BY implies an ORDER BY on the same fields. + * There are two reasons we need this: + * (1) We may want MSQ to hash-partition for GROUP BY instead of using a global sort, which would mean MSQ would not + * implicitly ORDER BY when there is a GROUP BY. + * (2) When doing REPLACE with MSQ, CLUSTERED BY is transformed to ORDER BY. We need to retain that ORDER BY, as it + * may be a subset of the GROUP BY, and it is important to remember which fields the user wanted to include in + * {@link org.apache.druid.timeline.partition.DimensionRangeShardSpec}. + */ + GROUPBY_IMPLICITLY_SORTS } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeSqlEngine.java b/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeSqlEngine.java index e7fdf9f7c33f..f53d5a38bc1d 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeSqlEngine.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeSqlEngine.java @@ -95,7 +95,7 @@ public RelDataType resultTypeForInsert(RelDataTypeFactory typeFactory, RelDataTy } @Override - public boolean featureAvailable(EngineFeature feature, PlannerContext plannerContext) + public boolean featureAvailable(EngineFeature feature) { switch (feature) { case CAN_SELECT: @@ -107,9 +107,9 @@ public boolean featureAvailable(EngineFeature feature, PlannerContext plannerCon case UNNEST: case ALLOW_BROADCAST_RIGHTY_JOIN: case ALLOW_TOP_LEVEL_UNION_ALL: - return true; case TIME_BOUNDARY_QUERY: - return plannerContext.queryContext().isTimeBoundaryPlanningEnabled(); + case GROUPBY_IMPLICITLY_SORTS: + return true; case CAN_INSERT: case CAN_REPLACE: case READ_EXTERNAL_DATA: diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlEngine.java b/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlEngine.java index 1ff52f84d0c5..fec7660e44ef 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlEngine.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlEngine.java @@ -39,9 +39,11 @@ public interface SqlEngine String name(); /** - * Whether a feature applies to this engine or not. + * Whether a feature applies to this engine or not. Most callers should use + * {@link PlannerContext#featureAvailable(EngineFeature)} instead, which also checks feature flags in context + * parameters. */ - boolean featureAvailable(EngineFeature feature, PlannerContext plannerContext); + boolean featureAvailable(EngineFeature feature); /** * Validates a provided query context. Returns quietly if the context is OK; throws {@link ValidationException} diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlResults.java b/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlResults.java index 649d27a1f3a9..486c23f67c9a 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlResults.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/run/SqlResults.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -70,7 +71,7 @@ public static Object coerce( } else if (value instanceof Boolean) { coercedValue = String.valueOf(value); } else { - final Object maybeList = maybeCoerceArrayToList(value, false); + final Object maybeList = coerceArrayToList(value, false); // Check if "maybeList" was originally a Collection of some kind, or was able to be coerced to one. // Then Iterate through the collection, coercing each value. Useful for handling multi-value dimensions. @@ -152,10 +153,7 @@ public static Object coerce( // the protobuf jdbc handler prefers lists (it actually can't handle java arrays as sql arrays, only java lists) // the json handler could handle this just fine, but it handles lists as sql arrays as well so just convert // here if needed - coercedValue = maybeCoerceArrayToList(value, true); - if (coercedValue == null) { - throw cannotCoerce(value, sqlTypeName, fieldName); - } + coercedValue = coerceArrayToList(value, true); } } else { throw cannotCoerce(value, sqlTypeName, fieldName); @@ -166,11 +164,11 @@ public static Object coerce( /** * Attempt to coerce a value to {@link List}. If it cannot be coerced, either return the original value (if mustCoerce - * is false) or return null (if mustCoerce is true). + * is false) or return the value as a single element list (if mustCoerce is true). */ @VisibleForTesting @Nullable - static Object maybeCoerceArrayToList(Object value, boolean mustCoerce) + static Object coerceArrayToList(Object value, boolean mustCoerce) { if (value instanceof List) { return value; @@ -184,7 +182,7 @@ static Object maybeCoerceArrayToList(Object value, boolean mustCoerce) final Object[] array = (Object[]) value; final ArrayList lst = new ArrayList<>(array.length); for (Object o : array) { - lst.add(maybeCoerceArrayToList(o, false)); + lst.add(coerceArrayToList(o, false)); } return lst; } else if (value instanceof long[]) { @@ -199,7 +197,7 @@ static Object maybeCoerceArrayToList(Object value, boolean mustCoerce) } return lst; } else if (mustCoerce) { - return null; + return Collections.singletonList(value); } return value; } diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/table/RowSignatures.java b/sql/src/main/java/org/apache/druid/sql/calcite/table/RowSignatures.java index 528a0a4a0b56..0234d6b5319f 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/table/RowSignatures.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/table/RowSignatures.java @@ -96,8 +96,8 @@ public static StringComparator getNaturalStringComparator( } /** - * Returns a Calcite RelDataType corresponding to a row signature. It will typecast __time column to TIMESTAMP - * irrespective of the type present in the row signature + * Returns a Calcite {@link RelDataType} corresponding to a {@link RowSignature}. It will typecast __time column to + * TIMESTAMP irrespective of the type present in the row signature */ public static RelDataType toRelDataType(final RowSignature rowSignature, final RelDataTypeFactory typeFactory) { @@ -105,8 +105,8 @@ public static RelDataType toRelDataType(final RowSignature rowSignature, final R } /** - * Returns a Calcite RelDataType corresponding to a row signature. - * For columns that are named "__time", it automatically casts it to TIMESTAMP if typecastTimeColumn is set to true + * Returns a Calcite {@link RelDataType} corresponding to a {@link RowSignature}. For columns that are named + * "__time", it automatically casts it to TIMESTAMP if typecastTimeColumn is set to true */ public static RelDataType toRelDataType( final RowSignature rowSignature, @@ -126,44 +126,7 @@ public static RelDataType toRelDataType( rowSignature.getColumnType(columnName) .orElseThrow(() -> new ISE("Encountered null type for column[%s]", columnName)); - switch (columnType.getType()) { - case STRING: - // Note that there is no attempt here to handle multi-value in any special way. Maybe one day... - type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.VARCHAR, true); - break; - case LONG: - type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.BIGINT, nullNumeric); - break; - case FLOAT: - type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.FLOAT, nullNumeric); - break; - case DOUBLE: - type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.DOUBLE, nullNumeric); - break; - case ARRAY: - switch (columnType.getElementType().getType()) { - case STRING: - type = Calcites.createSqlArrayTypeWithNullability(typeFactory, SqlTypeName.VARCHAR, true); - break; - case LONG: - type = Calcites.createSqlArrayTypeWithNullability(typeFactory, SqlTypeName.BIGINT, nullNumeric); - break; - case DOUBLE: - type = Calcites.createSqlArrayTypeWithNullability(typeFactory, SqlTypeName.DOUBLE, nullNumeric); - break; - case FLOAT: - type = Calcites.createSqlArrayTypeWithNullability(typeFactory, SqlTypeName.FLOAT, nullNumeric); - break; - default: - throw new ISE("valueType[%s] not translatable", columnType); - } - break; - case COMPLEX: - type = makeComplexType(typeFactory, columnType, true); - break; - default: - throw new ISE("valueType[%s] not translatable", columnType); - } + type = columnTypeToRelDataType(typeFactory, columnType, nullNumeric); } builder.add(columnName, type); @@ -172,6 +135,49 @@ public static RelDataType toRelDataType( return builder.build(); } + /** + * Returns a Calcite {@link RelDataType} corresponding to a {@link ColumnType} + */ + public static RelDataType columnTypeToRelDataType( + RelDataTypeFactory typeFactory, + ColumnType columnType, + boolean nullNumeric + ) + { + final RelDataType type; + switch (columnType.getType()) { + case STRING: + // Note that there is no attempt here to handle multi-value in any special way. Maybe one day... + type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.VARCHAR, true); + break; + case LONG: + type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.BIGINT, nullNumeric); + break; + case FLOAT: + type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.FLOAT, nullNumeric); + break; + case DOUBLE: + type = Calcites.createSqlTypeWithNullability(typeFactory, SqlTypeName.DOUBLE, nullNumeric); + break; + case ARRAY: + final RelDataType elementType = columnTypeToRelDataType( + typeFactory, + (ColumnType) columnType.getElementType(), + nullNumeric + ); + type = typeFactory.createTypeWithNullability( + typeFactory.createArrayType(elementType, -1), + true + ); + break; + case COMPLEX: + type = makeComplexType(typeFactory, columnType, true); + break; + default: + throw new ISE("valueType[%s] not translatable", columnType); + } return type; + } + /** * Creates a {@link ComplexSqlType} using the supplied {@link RelDataTypeFactory} to ensure that the * {@link ComplexSqlType} is interned. This is important because Calcite checks that the references are equal diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/view/ViewSqlEngine.java b/sql/src/main/java/org/apache/druid/sql/calcite/view/ViewSqlEngine.java index ae4cf9639549..568c0a5cba97 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/view/ViewSqlEngine.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/view/ViewSqlEngine.java @@ -51,7 +51,7 @@ public String name() } @Override - public boolean featureAvailable(EngineFeature feature, PlannerContext plannerContext) + public boolean featureAvailable(EngineFeature feature) { switch (feature) { // Use most permissive set of SELECT features, since our goal is to get the row type of the view. @@ -77,6 +77,7 @@ public boolean featureAvailable(EngineFeature feature, PlannerContext plannerCon case TIMESERIES_QUERY: case TIME_BOUNDARY_QUERY: case SCAN_NEEDS_SIGNATURE: + case GROUPBY_IMPLICITLY_SORTS: return false; default: diff --git a/sql/src/test/java/org/apache/druid/sql/SqlRowTransformerTest.java b/sql/src/test/java/org/apache/druid/sql/SqlRowTransformerTest.java index 1ca9db2a3c76..5286df34d6b7 100644 --- a/sql/src/test/java/org/apache/druid/sql/SqlRowTransformerTest.java +++ b/sql/src/test/java/org/apache/druid/sql/SqlRowTransformerTest.java @@ -33,8 +33,8 @@ import org.joda.time.DateTimeZone; import org.joda.time.format.ISODateTimeFormat; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -43,7 +43,7 @@ public class SqlRowTransformerTest extends CalciteTestBase { private RelDataType rowType; - @Before + @BeforeEach public void setup() { final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(DruidTypeSystem.INSTANCE); diff --git a/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java b/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java index 9b28df2c0f1b..b332b6dabfa2 100644 --- a/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java +++ b/sql/src/test/java/org/apache/druid/sql/SqlStatementTest.java @@ -58,7 +58,6 @@ import org.apache.druid.sql.calcite.planner.PrepareResult; import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.QueryLogHook; import org.apache.druid.sql.http.SqlQuery; import org.easymock.EasyMock; import org.hamcrest.MatcherAssert; @@ -68,7 +67,6 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -92,8 +90,6 @@ public class SqlStatementTest private static Closer resourceCloser; @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Rule - public QueryLogHook queryLogHook = QueryLogHook.create(); private TestRequestLogger testRequestLogger; private ListeningExecutorService executorService; private SqlStatementFactory sqlStatementFactory; diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java index 193ba0c7f1b1..cccaab75b6f0 100644 --- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java @@ -88,24 +88,23 @@ import org.apache.druid.sql.calcite.schema.NamedSchema; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.QueryLogHook; import org.apache.druid.sql.guice.SqlModule; import org.eclipse.jetty.server.Server; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.ResultIterator; +import java.io.File; import java.io.IOException; import java.sql.Array; import java.sql.Connection; @@ -129,6 +128,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * Tests the Avatica-based JDBC implementation using JSON serialization. See @@ -153,33 +153,27 @@ public class DruidAvaticaHandlerTest extends CalciteTestBase private static final String DUMMY_SQL_QUERY_ID = "dummy"; - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - private static QueryRunnerFactoryConglomerate conglomerate; private static SpecificSegmentsQuerySegmentWalker walker; private static Closer resourceCloser; private final boolean nullNumeric = !NullHandling.replaceWithDefault(); - @BeforeClass - public static void setUpClass() throws Exception + @BeforeAll + public static void setUpClass(@TempDir File tempDir) { resourceCloser = Closer.create(); conglomerate = QueryStackTests.createQueryRunnerFactoryConglomerate(resourceCloser); - walker = CalciteTests.createMockWalker(conglomerate, temporaryFolder.newFolder()); + walker = CalciteTests.createMockWalker(conglomerate, tempDir); resourceCloser.register(walker); } - @AfterClass + @AfterAll public static void tearDownClass() throws IOException { resourceCloser.close(); } - @Rule - public QueryLogHook queryLogHook = QueryLogHook.create(); - private final PlannerConfig plannerConfig = new PlannerConfig(); private final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable(); private final ExprMacroTable macroTable = CalciteTests.createExprMacroTable(); @@ -266,7 +260,7 @@ protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta) ); } - @Before + @BeforeEach public void setUp() throws Exception { final DruidSchemaCatalog rootSchema = makeRootSchema(); @@ -328,7 +322,7 @@ public void setUp() throws Exception clientLosAngeles = DriverManager.getConnection(server.url, propertiesLosAngeles); } - @After + @AfterEach public void tearDown() throws Exception { if (server != null) { @@ -524,6 +518,12 @@ public void testDatabaseMetaDataTables() throws SQLException final DatabaseMetaData metaData = client.getMetaData(); Assert.assertEquals( ImmutableList.of( + row( + Pair.of("TABLE_CAT", "druid"), + Pair.of("TABLE_NAME", CalciteTests.ARRAYS_DATASOURCE), + Pair.of("TABLE_SCHEM", "druid"), + Pair.of("TABLE_TYPE", "TABLE") + ), row( Pair.of("TABLE_CAT", "druid"), Pair.of("TABLE_NAME", CalciteTests.BROADCAST_DATASOURCE), @@ -605,6 +605,12 @@ public void testDatabaseMetaDataTablesAsSuperuser() throws SQLException final DatabaseMetaData metaData = superuserClient.getMetaData(); Assert.assertEquals( ImmutableList.of( + row( + Pair.of("TABLE_CAT", "druid"), + Pair.of("TABLE_NAME", CalciteTests.ARRAYS_DATASOURCE), + Pair.of("TABLE_SCHEM", "druid"), + Pair.of("TABLE_TYPE", "TABLE") + ), row( Pair.of("TABLE_CAT", "druid"), Pair.of("TABLE_NAME", CalciteTests.BROADCAST_DATASOURCE), @@ -846,49 +852,45 @@ public void testDatabaseMetaDataColumnsWithSuperuser() throws SQLException ); } - @Test(timeout = 90_000L) + @Test + @Timeout(value = 90_000L, unit = TimeUnit.MILLISECONDS) public void testConcurrentQueries() { - queryLogHook.withSkippedLog( - v -> { - final List> futures = new ArrayList<>(); - final ListeningExecutorService exec = MoreExecutors.listeningDecorator( - Execs.multiThreaded(AVATICA_CONFIG.getMaxStatementsPerConnection(), "DruidAvaticaHandlerTest-%d") - ); - for (int i = 0; i < 2000; i++) { - final String query = StringUtils.format("SELECT COUNT(*) + %s AS ci FROM foo", i); - futures.add( - exec.submit(() -> { - try ( - final Statement statement = client.createStatement(); - final ResultSet resultSet = statement.executeQuery(query) - ) { - final List> rows = getRows(resultSet); - return ((Number) Iterables.getOnlyElement(rows).get("ci")).intValue(); - } - catch (SQLException e) { - throw new RuntimeException(e); - } - }) - ); - } - - final List integers; - try { - integers = Futures.allAsList(futures).get(); - } - catch (InterruptedException e) { - throw new RE(e); - } - catch (ExecutionException e) { - throw new RE(e); - } - for (int i = 0; i < 2000; i++) { - Assert.assertEquals(i + 6, (int) integers.get(i)); - } - exec.shutdown(); - } + final List> futures = new ArrayList<>(); + final ListeningExecutorService exec = MoreExecutors.listeningDecorator( + Execs.multiThreaded(AVATICA_CONFIG.getMaxStatementsPerConnection(), "DruidAvaticaHandlerTest-%d") ); + for (int i = 0; i < 2000; i++) { + final String query = StringUtils.format("SELECT COUNT(*) + %s AS ci FROM foo", i); + futures.add( + exec.submit(() -> { + try ( + final Statement statement = client.createStatement(); + final ResultSet resultSet = statement.executeQuery(query)) { + final List> rows = getRows(resultSet); + return ((Number) Iterables.getOnlyElement(rows).get("ci")).intValue(); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + }) + ); + } + + final List integers; + try { + integers = Futures.allAsList(futures).get(); + } + catch (InterruptedException e) { + throw new RE(e); + } + catch (ExecutionException e) { + throw new RE(e); + } + for (int i = 0; i < 2000; i++) { + Assert.assertEquals(i + 6, (int) integers.get(i)); + } + exec.shutdown(); } @Test diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java index c9dd67b5c7da..81cd1c692318 100644 --- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidStatementTest.java @@ -47,17 +47,15 @@ import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.QueryLogHook; -import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -73,26 +71,20 @@ public class DruidStatementTest extends CalciteTestBase private static String SELECT_STAR_FROM_FOO = "SELECT * FROM druid.foo"; - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Rule - public QueryLogHook queryLogHook = QueryLogHook.create(); - private static SpecificSegmentsQuerySegmentWalker walker; private static QueryRunnerFactoryConglomerate conglomerate; private static Closer resourceCloser; - @BeforeClass - public static void setUpClass() throws Exception + @BeforeAll + public static void setUpClass(@TempDir File tempDir) { resourceCloser = Closer.create(); conglomerate = QueryStackTests.createQueryRunnerFactoryConglomerate(resourceCloser); - walker = CalciteTests.createMockWalker(conglomerate, temporaryFolder.newFolder()); + walker = CalciteTests.createMockWalker(conglomerate, tempDir); resourceCloser.register(walker); } - @AfterClass + @AfterAll public static void tearDownClass() throws IOException { resourceCloser.close(); @@ -100,7 +92,7 @@ public static void tearDownClass() throws IOException private SqlStatementFactory sqlStatementFactory; - @Before + @BeforeEach public void setUp() { final PlannerConfig plannerConfig = new PlannerConfig(); @@ -128,7 +120,7 @@ public void setUp() ); } - @After + @AfterEach public void tearDown() { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java index c53f5684d255..d83a9da4c8dd 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java @@ -26,9 +26,9 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; import com.google.inject.Injector; import org.apache.commons.text.StringEscapeUtils; -import org.apache.druid.annotations.UsedByJUnitParamsRunner; import org.apache.druid.common.config.NullHandling; import org.apache.druid.error.DruidException; import org.apache.druid.error.DruidException.Category; @@ -38,6 +38,7 @@ import org.apache.druid.hll.VersionOneHyperLogLogCollector; import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.java.util.common.Intervals; +import org.apache.druid.java.util.common.RE; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.granularity.Granularity; import org.apache.druid.java.util.common.io.Closer; @@ -94,7 +95,6 @@ import org.apache.druid.sql.SqlStatementFactory; import org.apache.druid.sql.calcite.QueryTestRunner.QueryResults; import org.apache.druid.sql.calcite.expression.DruidExpression; -import org.apache.druid.sql.calcite.expression.ExpressionTestHelper; import org.apache.druid.sql.calcite.planner.Calcites; import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.planner.PlannerContext; @@ -105,7 +105,6 @@ import org.apache.druid.sql.calcite.schema.DruidSchemaManager; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.QueryLogHook; import org.apache.druid.sql.calcite.util.SqlTestFramework; import org.apache.druid.sql.calcite.util.SqlTestFramework.Builder; import org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier; @@ -117,21 +116,21 @@ import org.apache.druid.sql.http.SqlParameter; import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; import org.joda.time.chrono.ISOChronology; import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; import org.junit.internal.matchers.ThrowableMessageMatcher; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nullable; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -144,10 +143,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * A base class for SQL query testing. It sets up query execution environment, provides useful helper methods, @@ -162,7 +162,7 @@ public class BaseCalciteQueryTest extends CalciteTestBase public static Long NULL_LONG; public static final String HLLC_STRING = VersionOneHyperLogLogCollector.class.getName(); - @BeforeClass + @BeforeAll public static void setupNullValues() { NULL_STRING = NullHandling.defaultStringValue(); @@ -298,14 +298,9 @@ public static Map getTimeseriesContextWithFloorTime( public final SqlEngine engine0; final boolean useDefault = NullHandling.replaceWithDefault(); - @Rule(order = 2) - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - public boolean cannotVectorize = false; public boolean skipVectorize = false; - public QueryLogHook queryLogHook; - private QueryComponentSupplier baseComponentSupplier; public PlannerComponentSupplier basePlannerComponentSupplier = new StandardPlannerComponentSupplier(); @@ -632,22 +627,8 @@ protected static DruidExceptionMatcher invalidSqlContains(String s) return DruidExceptionMatcher.invalidSqlInput().expectMessageContains(s); } - @Rule - public QueryLogHook getQueryLogHook() - { - // Indirection for the JSON mapper. Otherwise, this rule method is called - // before Setup is called, causing the query framework to be built before - // tests have done their setup. The indirection means we access the query - // framework only when we log the first query. By then, the query framework - // will have been created via the normal path. - return queryLogHook = new QueryLogHook(() -> queryFramework().queryJsonMapper()); - } - - @ClassRule - public static SqlTestFrameworkConfig.ClassRule queryFrameworkClassRule = new SqlTestFrameworkConfig.ClassRule(); - - @Rule(order = 3) - public SqlTestFrameworkConfig.MethodRule queryFrameworkRule = queryFrameworkClassRule.methodRule(this); + @RegisterExtension + static SqlTestFrameworkConfig.Rule queryFrameworkRule = new SqlTestFrameworkConfig.Rule(); public SqlTestFramework queryFramework() { @@ -659,7 +640,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { return baseComponentSupplier.createQuerySegmentWalker(conglomerate, joinableFactory, injector); } @@ -681,14 +662,7 @@ public SqlEngine createEngine( @Override public void gatherProperties(Properties properties) { - try { - baseComponentSupplier = new StandardComponentSupplier( - temporaryFolder.newFolder() - ); - } - catch (IOException e) { - throw new RuntimeException(e); - } + baseComponentSupplier = new StandardComponentSupplier(newTempFolder()); baseComponentSupplier.gatherProperties(properties); } @@ -754,9 +728,8 @@ public void finalizePlanner(PlannerFixture plannerFixture) public void assumeFeatureAvailable(EngineFeature feature) { - boolean featureAvailable = queryFramework().engine() - .featureAvailable(feature, ExpressionTestHelper.PLANNER_CONTEXT); - assumeTrue(StringUtils.format("test disabled; feature [%s] is not available!", feature), featureAvailable); + boolean featureAvailable = queryFramework().engine().featureAvailable(feature); + assumeTrue(featureAvailable, StringUtils.format("test disabled; feature [%s] is not available!", feature)); } public void assertQueryIsUnplannable(final String sql, String expectedError) @@ -770,7 +743,7 @@ public void assertQueryIsUnplannable(final PlannerConfig plannerConfig, final St testQuery(plannerConfig, sql, CalciteTests.REGULAR_USER_AUTH_RESULT, ImmutableList.of(), ImmutableList.of()); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, buildUnplannableExceptionMatcher().expectMessageContains(expectedError) ); @@ -1016,12 +989,6 @@ public CalciteTestConfig(Map baseQueryContext) ); } - @Override - public QueryLogHook queryLogHook() - { - return queryLogHook; - } - @Override public PlannerFixture plannerFixture(PlannerConfig plannerConfig, AuthConfig authConfig) { @@ -1258,7 +1225,7 @@ public void testQueryThrows( .build() .run() ); - MatcherAssert.assertThat(e, exceptionMatcher); + assertThat(e, exceptionMatcher); } public void analyzeResources( @@ -1342,7 +1309,7 @@ protected void skipVectorize() protected void msqIncompatible() { - assumeFalse("test case is not MSQ compatible", testBuilder().config.isRunningMSQ()); + assumeFalse(testBuilder().config.isRunningMSQ(), "test case is not MSQ compatible"); } protected static boolean isRewriteJoinToFilter(final Map queryContext) @@ -1395,59 +1362,56 @@ private static DataSource recursivelyClearContext(final DataSource dataSource, O * It tests various configs that can be passed to join queries. All the configs provided by this provider should * have the join query engine return the same results. */ - public static class QueryContextForJoinProvider - { - @UsedByJUnitParamsRunner - public static Object[] provideQueryContexts() - { - return new Object[]{ - // default behavior - QUERY_CONTEXT_DEFAULT, - // all rewrites enabled - new ImmutableMap.Builder() - .putAll(QUERY_CONTEXT_DEFAULT) - .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, true) - .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true) - .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) - .build(), - // filter-on-value-column rewrites disabled, everything else enabled - new ImmutableMap.Builder() - .putAll(QUERY_CONTEXT_DEFAULT) - .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, false) - .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true) - .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) - .build(), - // filter rewrites fully disabled, join-to-filter enabled - new ImmutableMap.Builder() - .putAll(QUERY_CONTEXT_DEFAULT) - .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, false) - .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, false) - .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) - .build(), - // filter rewrites disabled, but value column filters still set to true (it should be ignored and this should - // behave the same as the previous context) - new ImmutableMap.Builder() - .putAll(QUERY_CONTEXT_DEFAULT) - .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, true) - .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, false) - .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) - .build(), - // filter rewrites fully enabled, join-to-filter disabled - new ImmutableMap.Builder() - .putAll(QUERY_CONTEXT_DEFAULT) - .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, true) - .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true) - .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, false) - .build(), - // all rewrites disabled - new ImmutableMap.Builder() - .putAll(QUERY_CONTEXT_DEFAULT) - .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, false) - .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, false) - .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, false) - .build(), - }; - } + public static Object[] provideQueryContexts() + { + return new Object[] { + // default behavior + QUERY_CONTEXT_DEFAULT, + // all rewrites enabled + new ImmutableMap.Builder() + .putAll(QUERY_CONTEXT_DEFAULT) + .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, true) + .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true) + .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) + .build(), + // filter-on-value-column rewrites disabled, everything else enabled + new ImmutableMap.Builder() + .putAll(QUERY_CONTEXT_DEFAULT) + .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, false) + .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true) + .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) + .build(), + // filter rewrites fully disabled, join-to-filter enabled + new ImmutableMap.Builder() + .putAll(QUERY_CONTEXT_DEFAULT) + .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, false) + .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, false) + .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) + .build(), + // filter rewrites disabled, but value column filters still set to true + // (it should be ignored and this should + // behave the same as the previous context) + new ImmutableMap.Builder() + .putAll(QUERY_CONTEXT_DEFAULT) + .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, true) + .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, false) + .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, true) + .build(), + // filter rewrites fully enabled, join-to-filter disabled + new ImmutableMap.Builder() + .putAll(QUERY_CONTEXT_DEFAULT) + .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, true) + .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, true) + .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, false) + .build(), + // all rewrites disabled + new ImmutableMap.Builder() + .putAll(QUERY_CONTEXT_DEFAULT) + .put(QueryContexts.JOIN_FILTER_REWRITE_VALUE_COLUMN_FILTERS_ENABLE_KEY, false) + .put(QueryContexts.JOIN_FILTER_REWRITE_ENABLE_KEY, false) + .put(QueryContexts.REWRITE_JOIN_TO_FILTER_ENABLE_KEY, false) + .build(), + }; } protected Map withLeftDirectAccessEnabled(Map context) @@ -1631,4 +1595,25 @@ private void outprint(Object post) sb.append(post); } } + + /** + * Helper method that copies a resource to a temporary file, then returns it. + */ + public File getResourceAsTemporaryFile(final String resource) + { + final File file = newTempFile("resourceAsTempFile"); + final InputStream stream = getClass().getResourceAsStream(resource); + + if (stream == null) { + throw new RE(StringUtils.format("No such resource [%s]", resource)); + } + + try { + ByteStreams.copy(stream, Files.newOutputStream(file.toPath())); + } + catch (IOException e) { + throw new RuntimeException(e); + } + return file; + } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java index ce1f992a2ad2..d7b0a1d45ee1 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java @@ -22,9 +22,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.inject.Injector; import org.apache.druid.common.config.NullHandling; -import org.apache.druid.data.input.ResourceInputSource; import org.apache.druid.guice.DruidInjectorBuilder; import org.apache.druid.guice.NestedDataModule; import org.apache.druid.java.util.common.HumanReadableBytes; @@ -34,17 +32,13 @@ import org.apache.druid.math.expr.ExprEval; import org.apache.druid.math.expr.ExprMacroTable; import org.apache.druid.math.expr.ExpressionType; -import org.apache.druid.query.DataSource; import org.apache.druid.query.Druids; import org.apache.druid.query.FilteredDataSource; -import org.apache.druid.query.FrameBasedInlineDataSource; import org.apache.druid.query.InlineDataSource; import org.apache.druid.query.LookupDataSource; -import org.apache.druid.query.NestedDataTestUtils; import org.apache.druid.query.Query; import org.apache.druid.query.QueryContexts; import org.apache.druid.query.QueryDataSource; -import org.apache.druid.query.QueryRunnerFactoryConglomerate; import org.apache.druid.query.TableDataSource; import org.apache.druid.query.UnnestDataSource; import org.apache.druid.query.aggregation.CountAggregatorFactory; @@ -65,37 +59,20 @@ import org.apache.druid.query.groupby.orderby.DefaultLimitSpec; import org.apache.druid.query.groupby.orderby.NoopLimitSpec; import org.apache.druid.query.groupby.orderby.OrderByColumnSpec; -import org.apache.druid.query.lookup.LookupExtractorFactoryContainerProvider; import org.apache.druid.query.ordering.StringComparators; import org.apache.druid.query.scan.ScanQuery; import org.apache.druid.query.spec.MultipleIntervalSegmentSpec; import org.apache.druid.query.topn.DimensionTopNMetricSpec; import org.apache.druid.query.topn.TopNQueryBuilder; -import org.apache.druid.segment.FrameBasedInlineSegmentWrangler; -import org.apache.druid.segment.IndexBuilder; -import org.apache.druid.segment.InlineSegmentWrangler; -import org.apache.druid.segment.LookupSegmentWrangler; -import org.apache.druid.segment.MapSegmentWrangler; -import org.apache.druid.segment.QueryableIndex; -import org.apache.druid.segment.SegmentWrangler; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.segment.column.RowSignature; -import org.apache.druid.segment.incremental.IncrementalIndexSchema; import org.apache.druid.segment.join.JoinType; -import org.apache.druid.segment.join.JoinableFactoryWrapper; import org.apache.druid.segment.virtual.ExpressionVirtualColumn; -import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory; -import org.apache.druid.server.QueryStackTests; -import org.apache.druid.server.SpecificSegmentsQuerySegmentWalker; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.TestDataBuilder; -import org.apache.druid.timeline.DataSegment; -import org.apache.druid.timeline.partition.LinearShardSpec; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -112,9 +89,6 @@ public class CalciteArraysQueryTest extends BaseCalciteQueryTest .put(QueryContexts.CTX_SQL_STRINGIFY_ARRAYS, false) .build(); - - public static final String DATA_SOURCE_ARRAYS = "arrays"; - public static void assertResultsDeepEquals(String sql, List expected, List results) { for (int row = 0; row < results.size(); row++) { @@ -146,121 +120,6 @@ public void configureGuice(DruidInjectorBuilder builder) builder.addModule(new NestedDataModule()); } - @SuppressWarnings("resource") - @Override - public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( - final QueryRunnerFactoryConglomerate conglomerate, - final JoinableFactoryWrapper joinableFactory, - final Injector injector - ) throws IOException - { - NestedDataModule.registerHandlersAndSerde(); - - final QueryableIndex foo = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(TestDataBuilder.INDEX_SCHEMA) - .rows(TestDataBuilder.ROWS1) - .buildMMappedIndex(); - - final QueryableIndex numfoo = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(TestDataBuilder.INDEX_SCHEMA_NUMERIC_DIMS) - .rows(TestDataBuilder.ROWS1_WITH_NUMERIC_DIMS) - .buildMMappedIndex(); - - final QueryableIndex indexLotsOfColumns = IndexBuilder - .create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema(TestDataBuilder.INDEX_SCHEMA_LOTS_O_COLUMNS) - .rows(TestDataBuilder.ROWS_LOTS_OF_COLUMNS) - .buildMMappedIndex(); - - final QueryableIndex indexArrays = - IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) - .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) - .schema( - new IncrementalIndexSchema.Builder() - .withTimestampSpec(NestedDataTestUtils.AUTO_SCHEMA.getTimestampSpec()) - .withDimensionsSpec(NestedDataTestUtils.AUTO_SCHEMA.getDimensionsSpec()) - .withMetrics( - new CountAggregatorFactory("cnt") - ) - .withRollup(false) - .build() - ) - .inputSource( - ResourceInputSource.of( - NestedDataTestUtils.class.getClassLoader(), - NestedDataTestUtils.ARRAY_TYPES_DATA_FILE - ) - ) - .inputFormat(TestDataBuilder.DEFAULT_JSON_INPUT_FORMAT) - .inputTmpDir(temporaryFolder.newFolder()) - .buildMMappedIndex(); - - SpecificSegmentsQuerySegmentWalker walker = SpecificSegmentsQuerySegmentWalker.createWalker( - injector, - conglomerate, - new MapSegmentWrangler( - ImmutableMap., SegmentWrangler>builder() - .put(InlineDataSource.class, new InlineSegmentWrangler()) - .put(FrameBasedInlineDataSource.class, new FrameBasedInlineSegmentWrangler()) - .put( - LookupDataSource.class, - new LookupSegmentWrangler(injector.getInstance(LookupExtractorFactoryContainerProvider.class)) - ) - .build() - ), - joinableFactory, - QueryStackTests.DEFAULT_NOOP_SCHEDULER - ); - walker.add( - DataSegment.builder() - .dataSource(CalciteTests.DATASOURCE1) - .interval(foo.getDataInterval()) - .version("1") - .shardSpec(new LinearShardSpec(0)) - .size(0) - .build(), - foo - ).add( - DataSegment.builder() - .dataSource(CalciteTests.DATASOURCE3) - .interval(numfoo.getDataInterval()) - .version("1") - .shardSpec(new LinearShardSpec(0)) - .size(0) - .build(), - numfoo - ).add( - DataSegment.builder() - .dataSource(CalciteTests.DATASOURCE5) - .interval(indexLotsOfColumns.getDataInterval()) - .version("1") - .shardSpec(new LinearShardSpec(0)) - .size(0) - .build(), - indexLotsOfColumns - ).add( - DataSegment.builder() - .dataSource(DATA_SOURCE_ARRAYS) - .version("1") - .interval(indexArrays.getDataInterval()) - .shardSpec(new LinearShardSpec(1)) - .size(0) - .build(), - indexArrays - ); - - return walker; - } - // test some query stuffs, sort of limited since no native array column types so either need to use constructor or // array aggregator @Test @@ -323,7 +182,7 @@ public void testGroupByArrayColumnFromCase() QUERY_CONTEXT_NO_STRINGIFY_ARRAY, ImmutableList.of( GroupByQuery.builder() - .setDataSource(DATA_SOURCE_ARRAYS) + .setDataSource(CalciteTests.ARRAYS_DATASOURCE) .setInterval(querySegmentSpec(Filtration.eternity())) .setVirtualColumns(expressionVirtualColumn( "v0", @@ -648,7 +507,7 @@ public void testSomeArrayFunctionsWithScanQueryArrayColumns() + " FROM druid.arrays", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .virtualColumns( // these report as strings even though they are not, someday this will not be so @@ -864,7 +723,7 @@ public void testArrayOverlapFilterStringArrayColumn() "SELECT arrayStringNulls FROM druid.arrays WHERE ARRAY_OVERLAP(arrayStringNulls, ARRAY['a','b']) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( or( @@ -895,7 +754,7 @@ public void testArrayOverlapFilterLongArrayColumn() "SELECT arrayLongNulls FROM druid.arrays WHERE ARRAY_OVERLAP(arrayLongNulls, ARRAY[1, 2]) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( or( @@ -926,7 +785,7 @@ public void testArrayOverlapFilterDoubleArrayColumn() "SELECT arrayDoubleNulls FROM druid.arrays WHERE ARRAY_OVERLAP(arrayDoubleNulls, ARRAY[1.1, 2.2]) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( or( @@ -1004,7 +863,7 @@ public void testArrayOverlapFilterArrayStringColumns() "SELECT arrayStringNulls, arrayString FROM druid.arrays WHERE ARRAY_OVERLAP(arrayStringNulls, arrayString) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters(expressionFilter("array_overlap(\"arrayStringNulls\",\"arrayString\")")) .columns("arrayString", "arrayStringNulls") @@ -1030,7 +889,7 @@ public void testArrayOverlapFilterArrayLongColumns() "SELECT arrayLongNulls, arrayLong FROM druid.arrays WHERE ARRAY_OVERLAP(arrayLongNulls, arrayLong) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters(expressionFilter("array_overlap(\"arrayLongNulls\",\"arrayLong\")")) .columns("arrayLong", "arrayLongNulls") @@ -1056,7 +915,7 @@ public void testArrayOverlapFilterArrayDoubleColumns() "SELECT arrayDoubleNulls, arrayDouble FROM druid.arrays WHERE ARRAY_OVERLAP(arrayDoubleNulls, arrayDouble) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters(expressionFilter("array_overlap(\"arrayDoubleNulls\",\"arrayDouble\")")) .columns("arrayDouble", "arrayDoubleNulls") @@ -1108,7 +967,7 @@ public void testArrayContainsFilterArrayStringColumn() "SELECT arrayStringNulls FROM druid.arrays WHERE ARRAY_CONTAINS(arrayStringNulls, ARRAY['a','b']) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( and( @@ -1138,7 +997,7 @@ public void testArrayContainsFilterArrayLongColumn() "SELECT arrayLongNulls FROM druid.arrays WHERE ARRAY_CONTAINS(arrayLongNulls, ARRAY[1, null]) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( and( @@ -1166,7 +1025,7 @@ public void testArrayContainsFilterArrayDoubleColumn() "SELECT arrayDoubleNulls FROM druid.arrays WHERE ARRAY_CONTAINS(arrayDoubleNulls, ARRAY[1.1, null]) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( and( @@ -1276,7 +1135,7 @@ public void testArrayContainsFilterArrayStringColumns() "SELECT arrayStringNulls, arrayString FROM druid.arrays WHERE ARRAY_CONTAINS(arrayStringNulls, arrayString) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( expressionFilter("array_contains(\"arrayStringNulls\",\"arrayString\")") @@ -1293,6 +1152,36 @@ public void testArrayContainsFilterArrayStringColumns() ); } + @Test + public void testArrayContainsArrayStringColumns() + { + cannotVectorize(); + testQuery( + "SELECT ARRAY_CONTAINS(arrayStringNulls, ARRAY['a', 'b']), ARRAY_CONTAINS(arrayStringNulls, arrayString) FROM druid.arrays LIMIT 5", + ImmutableList.of( + newScanQueryBuilder() + .dataSource(CalciteTests.ARRAYS_DATASOURCE) + .intervals(querySegmentSpec(Filtration.eternity())) + .columns("v0", "v1") + .virtualColumns( + expressionVirtualColumn("v0", "array_contains(\"arrayStringNulls\",array('a','b'))", ColumnType.LONG), + expressionVirtualColumn("v1", "array_contains(\"arrayStringNulls\",\"arrayString\")", ColumnType.LONG) + ) + .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) + .limit(5) + .context(QUERY_CONTEXT_DEFAULT) + .build() + ), + ImmutableList.of( + new Object[]{NullHandling.sqlCompatible() ? null : false, NullHandling.sqlCompatible() ? null : false}, + new Object[]{true, false}, + new Object[]{false, false}, + new Object[]{NullHandling.sqlCompatible() ? null : false, NullHandling.sqlCompatible() ? null : false}, + new Object[]{true, true} + ) + ); + } + @Test public void testArrayContainsFilterArrayLongColumns() { @@ -1300,7 +1189,7 @@ public void testArrayContainsFilterArrayLongColumns() "SELECT arrayLong, arrayLongNulls FROM druid.arrays WHERE ARRAY_CONTAINS(arrayLong, arrayLongNulls) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( expressionFilter("array_contains(\"arrayLong\",\"arrayLongNulls\")") @@ -1327,7 +1216,7 @@ public void testArrayContainsFilterArrayDoubleColumns() "SELECT arrayDoubleNulls, arrayDouble FROM druid.arrays WHERE ARRAY_CONTAINS(arrayDoubleNulls, arrayDouble) LIMIT 5", ImmutableList.of( newScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .filters( expressionFilter("array_contains(\"arrayDoubleNulls\",\"arrayDouble\")") @@ -1342,6 +1231,45 @@ public void testArrayContainsFilterArrayDoubleColumns() ); } + @Test + public void testArrayContainsConstantNull() + { + testQuery( + "SELECT ARRAY_CONTAINS(null, ARRAY['a','b'])", + ImmutableList.of( + NullHandling.sqlCompatible() + ? newScanQueryBuilder() + .dataSource( + InlineDataSource.fromIterable( + ImmutableList.of(new Object[]{NullHandling.defaultLongValue()}), + RowSignature.builder().add("EXPR$0", ColumnType.LONG).build() + ) + ) + .intervals(querySegmentSpec(Filtration.eternity())) + .columns("EXPR$0") + .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) + .context(QUERY_CONTEXT_DEFAULT) + .build() + : newScanQueryBuilder() + .dataSource( + InlineDataSource.fromIterable( + ImmutableList.of(new Object[]{0L}), + RowSignature.builder().add("ZERO", ColumnType.LONG).build() + ) + ) + .virtualColumns(expressionVirtualColumn("v0", "0", ColumnType.LONG)) + .intervals(querySegmentSpec(Filtration.eternity())) + .columns("v0") + .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) + .context(QUERY_CONTEXT_DEFAULT) + .build() + ), + ImmutableList.of( + new Object[]{NullHandling.sqlCompatible() ? null : false} + ) + ); + } + @Test public void testArraySlice() { @@ -1378,7 +1306,7 @@ public void testArraySliceArrayColumns() QUERY_CONTEXT_NO_STRINGIFY_ARRAY, ImmutableList.of( new Druids.ScanQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .virtualColumns( expressionVirtualColumn("v0", "array_slice(\"arrayString\",1)", ColumnType.STRING_ARRAY), @@ -1463,7 +1391,7 @@ public void testArrayLengthArrayColumn() "SELECT arrayStringNulls, ARRAY_LENGTH(arrayStringNulls), SUM(cnt) FROM druid.arrays GROUP BY 1, 2 ORDER BY 2 DESC", ImmutableList.of( GroupByQuery.builder() - .setDataSource(DATA_SOURCE_ARRAYS) + .setDataSource(CalciteTests.ARRAYS_DATASOURCE) .setInterval(querySegmentSpec(Filtration.eternity())) .setGranularity(Granularities.ALL) .setVirtualColumns(expressionVirtualColumn("v0", "array_length(\"arrayStringNulls\")", ColumnType.LONG)) @@ -1843,7 +1771,7 @@ public void testArrayGroupAsLongArrayColumn() QUERY_CONTEXT_NO_STRINGIFY_ARRAY, ImmutableList.of( GroupByQuery.builder() - .setDataSource(DATA_SOURCE_ARRAYS) + .setDataSource(CalciteTests.ARRAYS_DATASOURCE) .setInterval(querySegmentSpec(Filtration.eternity())) .setGranularity(Granularities.ALL) .setDimensions( @@ -1940,7 +1868,7 @@ public void testArrayGroupAsDoubleArrayColumn() QUERY_CONTEXT_NO_STRINGIFY_ARRAY, ImmutableList.of( GroupByQuery.builder() - .setDataSource(DATA_SOURCE_ARRAYS) + .setDataSource(CalciteTests.ARRAYS_DATASOURCE) .setInterval(querySegmentSpec(Filtration.eternity())) .setGranularity(Granularities.ALL) .setDimensions( @@ -2984,7 +2912,7 @@ public void testArrayAggArrayColumns() "SELECT ARRAY_AGG(arrayLongNulls), ARRAY_AGG(DISTINCT arrayDouble), ARRAY_AGG(DISTINCT arrayStringNulls) FILTER(WHERE arrayLong = ARRAY[2,3]) FROM arrays WHERE arrayDoubleNulls is not null", ImmutableList.of( Druids.newTimeseriesQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .granularity(Granularities.ALL) .filters(notNull("arrayDoubleNulls")) @@ -3068,7 +2996,7 @@ public void testArrayConcatAggArrayColumns() "SELECT ARRAY_CONCAT_AGG(arrayLongNulls), ARRAY_CONCAT_AGG(DISTINCT arrayDouble), ARRAY_CONCAT_AGG(DISTINCT arrayStringNulls) FILTER(WHERE arrayLong = ARRAY[2,3]) FROM arrays WHERE arrayDoubleNulls is not null", ImmutableList.of( Druids.newTimeseriesQueryBuilder() - .dataSource(DATA_SOURCE_ARRAYS) + .dataSource(CalciteTests.ARRAYS_DATASOURCE) .intervals(querySegmentSpec(Filtration.eternity())) .granularity(Granularities.ALL) .filters(notNull("arrayDoubleNulls")) @@ -3926,7 +3854,7 @@ public void testUnnestArrayColumnsString() ImmutableList.of( Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayString\"", ColumnType.STRING_ARRAY), null )) @@ -3974,7 +3902,7 @@ public void testUnnestArrayColumnsStringNulls() ImmutableList.of( Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayStringNulls\"", ColumnType.STRING_ARRAY), null )) @@ -4021,7 +3949,7 @@ public void testUnnestArrayColumnsLong() ImmutableList.of( Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayLong\"", ColumnType.LONG_ARRAY), null )) @@ -4075,7 +4003,7 @@ public void testUnnestArrayColumnsLongNulls() ImmutableList.of( Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayLongNulls\"", ColumnType.LONG_ARRAY), null )) @@ -4125,7 +4053,7 @@ public void testUnnestArrayColumnsDouble() ImmutableList.of( Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayDouble\"", ColumnType.DOUBLE_ARRAY), null )) @@ -4179,7 +4107,7 @@ public void testUnnestArrayColumnsDoubleNulls() ImmutableList.of( Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayDoubleNulls\"", ColumnType.DOUBLE_ARRAY), null )) @@ -4315,7 +4243,7 @@ public void testUnnestTwiceArrayColumns() .dataSource( UnnestDataSource.create( UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn( "j0.unnest", "\"arrayStringNulls\"", @@ -4632,7 +4560,7 @@ public void testUnnestThriceWithFiltersOnDimAndAllUnnestColumnsArrayColumns() UnnestDataSource.create( FilteredDataSource.create( UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn( "j0.unnest", "\"arrayLongNulls\"", @@ -4782,7 +4710,7 @@ public void testUnnestThriceWithFiltersOnDimAndAllUnnestColumnsArrayColumnsOrFil UnnestDataSource.create( FilteredDataSource.create( UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn( "j0.unnest", "\"arrayLongNulls\"", @@ -4893,7 +4821,7 @@ public void testUnnestWithGroupByArrayColumn() ImmutableList.of( GroupByQuery.builder() .setDataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayStringNulls\"", ColumnType.STRING_ARRAY), null )) @@ -6497,7 +6425,7 @@ public void testUnnestWithSumOnUnnestedArrayColumn() ImmutableList.of( Druids.newTimeseriesQueryBuilder() .dataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayDoubleNulls\"", ColumnType.DOUBLE_ARRAY), null )) @@ -6584,7 +6512,7 @@ public void testUnnestWithGroupByWithWhereOnUnnestArrayCol() ImmutableList.of( GroupByQuery.builder() .setDataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayLongNulls\"", ColumnType.LONG_ARRAY), NullHandling.sqlCompatible() ? or( @@ -6621,7 +6549,7 @@ public void testUnnestWithGroupByHavingWithWhereOnUnnestArrayCol() ImmutableList.of( GroupByQuery.builder() .setDataSource(UnnestDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), expressionVirtualColumn("j0.unnest", "\"arrayLongNulls\"", ColumnType.LONG_ARRAY), NullHandling.sqlCompatible() ? or( @@ -6739,7 +6667,7 @@ public void testUnnestWithTimeFilterOnlyArrayColumn() Druids.newScanQueryBuilder() .dataSource(UnnestDataSource.create( FilteredDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), range("__time", ColumnType.LONG, 1672617600000L, 1672704600000L, false, false) ), expressionVirtualColumn("j0.unnest", "\"arrayStringNulls\"", ColumnType.STRING_ARRAY), @@ -6998,7 +6926,7 @@ public void testUnnestWithTimeFilterInsideSubqueryArrayColumns() .dataSource( UnnestDataSource.create( FilteredDataSource.create( - new TableDataSource(DATA_SOURCE_ARRAYS), + new TableDataSource(CalciteTests.ARRAYS_DATASOURCE), range("__time", ColumnType.LONG, 1672617600000L, 1672704600000L, false, false) ), expressionVirtualColumn("j0.unnest", "\"arrayLongNulls\"", ColumnType.LONG_ARRAY), @@ -7399,4 +7327,164 @@ public void testBooleanConstExprArray() ) ); } + + @Test + public void testGroupByNestedArrayInline() + { + cannotVectorize(); + // msq does not support nested arrays currently + msqIncompatible(); + testQuery( + "SELECT c1, ARRAY_PREPEND('1', ARRAY_AGG(ARRAY[1,c2], 100000)) c5 \n" + + "FROM (VALUES (1,1),(2,2),(3,3)) t(c1,c2)\n" + + "GROUP BY 1 \n" + + "HAVING ARRAY_PREPEND('1', ARRAY_AGG(ARRAY[1,c2], 100000)) <> ARRAY_PREPEND('0', ARRAY_AGG(ARRAY[1,c2], 100000))", + QUERY_CONTEXT_NO_STRINGIFY_ARRAY, + ImmutableList.of( + GroupByQuery.builder() + .setDataSource( + InlineDataSource.fromIterable( + ImmutableList.of( + new Object[]{1L, 1L}, + new Object[]{2L, 2L}, + new Object[]{3L, 3L} + ), + RowSignature.builder() + .add("c1", ColumnType.LONG) + .add("c2", ColumnType.LONG) + .build() + ) + ) + .setInterval(querySegmentSpec(Filtration.eternity())) + .setGranularity(Granularities.ALL) + .setVirtualColumns( + expressionVirtualColumn( + "v0", + "array(1,\"c2\")", + ColumnType.LONG_ARRAY + ) + ) + .setDimensions(new DefaultDimensionSpec("c1", "d0", ColumnType.LONG)) + .setAggregatorSpecs( + new ExpressionLambdaAggregatorFactory( + "a0", + ImmutableSet.of("v0"), + "__acc", + "ARRAY>[]", + "ARRAY>[]", + true, + true, + false, + "array_append(\"__acc\", \"v0\")", + "array_concat(\"__acc\", \"a0\")", + null, + null, + HumanReadableBytes.valueOf(100000), + TestExprMacroTable.INSTANCE + ) + ) + .setPostAggregatorSpecs( + expressionPostAgg( + "p0", + "array_prepend('1',\"a0\")", + ColumnType.ofArray(ColumnType.LONG_ARRAY) + ) + ) + .setHavingSpec( + new DimFilterHavingSpec( + expressionFilter("(array_prepend('1',\"a0\") != array_prepend('0',\"a0\"))"), + true + ) + ) + .setContext(QUERY_CONTEXT_DEFAULT) + .build() + ), + ImmutableList.of( + new Object[]{1, ImmutableList.of(ImmutableList.of(1L), ImmutableList.of(1L, 1L))}, + new Object[]{2, ImmutableList.of(ImmutableList.of(1L), ImmutableList.of(1L, 2L))}, + new Object[]{3, ImmutableList.of(ImmutableList.of(1L), ImmutableList.of(1L, 3L))} + ) + ); + } + + @Test + public void testGroupByNestedArrayInlineCount() + { + cannotVectorize(); + // msq does not support nested arrays currently + msqIncompatible(); + testQuery( + "SELECT COUNT(*) c FROM (\n" + + "SELECT c1, ARRAY_PREPEND('1', ARRAY_AGG(ARRAY[1,c2], 100000)) c5 \n" + + "FROM (VALUES (1,1),(2,2),(3,3)) t(c1,c2)\n" + + "GROUP BY 1 \n" + + "HAVING ARRAY_PREPEND('1', ARRAY_AGG(ARRAY[1,c2], 100000)) <> ARRAY_PREPEND('0', ARRAY_AGG(ARRAY[1,c2], 100000))\n" + + ")", + QUERY_CONTEXT_NO_STRINGIFY_ARRAY, + ImmutableList.of( + GroupByQuery.builder() + .setDataSource( + GroupByQuery.builder() + .setDataSource( + InlineDataSource.fromIterable( + ImmutableList.of( + new Object[]{1L, 1L}, + new Object[]{2L, 2L}, + new Object[]{3L, 3L} + ), + RowSignature.builder() + .add("c1", ColumnType.LONG) + .add("c2", ColumnType.LONG) + .build() + ) + ) + .setInterval(querySegmentSpec(Filtration.eternity())) + .setGranularity(Granularities.ALL) + .setVirtualColumns( + expressionVirtualColumn( + "v0", + "array(1,\"c2\")", + ColumnType.LONG_ARRAY + ) + ) + .setDimensions(new DefaultDimensionSpec("c1", "d0", ColumnType.LONG)) + .setAggregatorSpecs( + new ExpressionLambdaAggregatorFactory( + "a0", + ImmutableSet.of("v0"), + "__acc", + "ARRAY>[]", + "ARRAY>[]", + true, + true, + false, + "array_append(\"__acc\", \"v0\")", + "array_concat(\"__acc\", \"a0\")", + null, + null, + HumanReadableBytes.valueOf(100000), + TestExprMacroTable.INSTANCE + ) + ) + .setHavingSpec( + new DimFilterHavingSpec( + expressionFilter( + "(array_prepend('1',\"a0\") != array_prepend('0',\"a0\"))"), + true + ) + ) + .setContext(QUERY_CONTEXT_DEFAULT) + .build() + ) + .setInterval(querySegmentSpec(Filtration.eternity())) + .setGranularity(Granularities.ALL) + .setAggregatorSpecs(new CountAggregatorFactory("_a0")) + .setContext(QUERY_CONTEXT_DEFAULT) + .build() + ), + ImmutableList.of( + new Object[]{3L} + ) + ); + } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogInsertTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogInsertTest.java index ca10b7f42d72..af45896011c6 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogInsertTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogInsertTest.java @@ -30,7 +30,7 @@ import org.apache.druid.sql.calcite.external.Externals; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Test for INSERT DML statements for tables defined in catalog. diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogReplaceTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogReplaceTest.java index 323758dd7d39..f4c6a908ca7d 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogReplaceTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCatalogReplaceTest.java @@ -30,7 +30,7 @@ import org.apache.druid.sql.calcite.external.Externals; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Test for REPLACE DML statements for tables defined in catalog. diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCorrelatedQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCorrelatedQueryTest.java index a7a5222d8889..e0d71b6cfbaf 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCorrelatedQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteCorrelatedQueryTest.java @@ -20,8 +20,6 @@ package org.apache.druid.sql.calcite; import com.google.common.collect.ImmutableList; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; import org.apache.druid.java.util.common.Intervals; import org.apache.druid.java.util.common.granularity.AllGranularity; import org.apache.druid.java.util.common.granularity.Granularities; @@ -45,18 +43,16 @@ import org.apache.druid.segment.join.JoinType; import org.apache.druid.segment.virtual.ExpressionVirtualColumn; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; -import org.junit.runner.RunWith; - +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; import java.util.Collections; import java.util.Map; -@RunWith(JUnitParamsRunner.class) public class CalciteCorrelatedQueryTest extends BaseCalciteQueryTest { - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testCorrelatedSubquery(Map queryContext) { cannotVectorize(); @@ -172,8 +168,8 @@ public void testCorrelatedSubquery(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testCorrelatedSubqueryWithLeftFilter(Map queryContext) { cannotVectorize(); @@ -261,8 +257,8 @@ public void testCorrelatedSubqueryWithLeftFilter(Map queryContex ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testCorrelatedSubqueryWithLeftFilter_leftDirectAccessDisabled(Map queryContext) { cannotVectorize(); @@ -356,8 +352,8 @@ public void testCorrelatedSubqueryWithLeftFilter_leftDirectAccessDisabled(Map queryContext) { cannotVectorize(); @@ -450,8 +446,8 @@ public void testCorrelatedSubqueryWithCorrelatedQueryFilter(Map ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testCorrelatedSubqueryWithCorrelatedQueryFilter_Scan(Map queryContext) { cannotVectorize(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExplainQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExplainQueryTest.java index 901677044853..3d7c820902bd 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExplainQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExplainQueryTest.java @@ -23,7 +23,7 @@ import org.apache.druid.common.config.NullHandling; import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExportTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExportTest.java index 4a97367fcd19..eaf6af9e77be 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExportTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteExportTest.java @@ -44,9 +44,9 @@ import org.apache.druid.storage.local.LocalFileExportStorageProvider; import org.apache.druid.storage.local.LocalFileStorageConnectorProvider; import org.hamcrest.CoreMatchers; -import org.junit.Ignore; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.List; @@ -80,7 +80,7 @@ public void configure(Binder binder) // Disabled until replace supports external destinations. To be enabled after that point. @Test - @Ignore + @Disabled public void testReplaceIntoExtern() { testIngestionQuery() @@ -207,7 +207,7 @@ public void testInsertIntoExternParameterized() // Disabled until replace supports external destinations. To be enabled after that point. @Test - @Ignore + @Disabled public void testReplaceIntoExternParameterized() { testIngestionQuery() diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteIngestionDmlTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteIngestionDmlTest.java index 071c8ae04d31..29c375b0269e 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteIngestionDmlTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteIngestionDmlTest.java @@ -65,10 +65,9 @@ import org.apache.druid.sql.http.SqlParameter; import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; -import org.junit.After; import org.junit.Assert; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.AfterEach; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -82,6 +81,8 @@ import java.util.Set; import java.util.stream.Stream; +import static org.hamcrest.MatcherAssert.assertThat; + public class CalciteIngestionDmlTest extends BaseCalciteQueryTest { protected static final Map DEFAULT_CONTEXT = @@ -181,7 +182,7 @@ public void configure(Binder binder) }); } - @After + @AfterEach public void tearDown() { // Catch situations where tests forgot to call "verify" on their tester. @@ -378,7 +379,6 @@ private void verifyValidationError() throw new ISE("Test must not have expectedQuery"); } - queryLogHook.clearRecordedQueries(); final Throwable e = Assert.assertThrows( Throwable.class, () -> { @@ -386,8 +386,7 @@ private void verifyValidationError() } ); - MatcherAssert.assertThat(e, validationErrorMatcher); - Assert.assertTrue(queryLogHook.getRecordedQueries().isEmpty()); + assertThat(e, validationErrorMatcher); } private void verifySuccess() diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteInsertDmlTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteInsertDmlTest.java index 6f3b9d8d6ebc..8b62bd668053 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteInsertDmlTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteInsertDmlTest.java @@ -52,10 +52,9 @@ import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.util.CalciteTests; import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; import org.junit.Assert; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; @@ -69,6 +68,7 @@ import static org.apache.druid.segment.column.ColumnType.FLOAT; import static org.apache.druid.segment.column.ColumnType.LONG; import static org.apache.druid.segment.column.ColumnType.STRING; +import static org.hamcrest.MatcherAssert.assertThat; public class CalciteInsertDmlTest extends CalciteIngestionDmlTest { @@ -1197,7 +1197,7 @@ public void testInsertWithClusteredByAndOrderBy() Assert.fail("Exception should be thrown"); } catch (DruidException e) { - MatcherAssert.assertThat(e, invalidSqlIs( + assertThat(e, invalidSqlIs( "Cannot use an ORDER BY clause on a Query of type [INSERT], use CLUSTERED BY instead" )); } @@ -1216,7 +1216,7 @@ public void testInsertWithPartitionedByContainingInvalidGranularity() Assert.fail("Exception should be thrown"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs( "Invalid granularity['invalid_granularity'] specified after PARTITIONED BY clause." @@ -1243,7 +1243,7 @@ public void testInsertWithOrderBy() Assert.fail("Exception should be thrown"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("Cannot use an ORDER BY clause on a Query of type [INSERT], use CLUSTERED BY instead") ); @@ -1266,7 +1266,7 @@ public void testInsertWithoutPartitionedBy() ) ); - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("Operation [INSERT] requires a PARTITIONED BY to be explicitly defined, but none was found.") ); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java index 9b683cc0388a..04727d7f86a7 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java @@ -22,8 +22,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; import org.apache.druid.common.config.NullHandling; import org.apache.druid.error.DruidException; import org.apache.druid.error.DruidExceptionMatcher; @@ -72,6 +70,7 @@ import org.apache.druid.query.groupby.orderby.DefaultLimitSpec; import org.apache.druid.query.groupby.orderby.NoopLimitSpec; import org.apache.druid.query.groupby.orderby.OrderByColumnSpec; +import org.apache.druid.query.groupby.orderby.OrderByColumnSpec.Direction; import org.apache.druid.query.ordering.StringComparators; import org.apache.druid.query.scan.ScanQuery; import org.apache.druid.query.timeboundary.TimeBoundaryQuery; @@ -90,15 +89,16 @@ import org.apache.druid.sql.calcite.expression.DruidExpression; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.planner.PlannerConfig; +import org.apache.druid.sql.calcite.run.EngineFeature; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.hamcrest.MatcherAssert; import org.joda.time.DateTimeZone; import org.joda.time.Period; import org.junit.Assert; -import org.junit.Assume; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.Collections; @@ -106,25 +106,19 @@ import java.util.List; import java.util.Map; -@RunWith(JUnitParamsRunner.class) +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class CalciteJoinQueryTest extends BaseCalciteQueryTest { /** - * Used for testing {@link org.apache.druid.sql.calcite.planner.JoinAlgorithm#SORT_MERGE}. - */ - private final boolean sortBasedJoin; - - public CalciteJoinQueryTest() - { - this(false); - } - - /** - * Constructor used by MSQ subclasses. Necessary because results come in a different order when using sort-based join. + * Used by MSQ subclasses. + * + * Necessary because results come in a different order when using sort-based join. */ - protected CalciteJoinQueryTest(boolean sortBasedJoin) + public boolean isSortBasedJoin() { - this.sortBasedJoin = sortBasedJoin; + return false; } @SqlTestFrameworkConfig(minTopNThreshold = 1) @@ -325,8 +319,8 @@ public void testJoinOuterGroupByAndSubqueryHasLimit() ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") @NotYetSupported(Modes.JOIN_TABLE_TABLE) public void testJoinOuterGroupByAndSubqueryNoLimit(Map queryContext) { @@ -686,8 +680,8 @@ public void testJoinOnGroupByInsteadOfTimeseriesWithFloorOnTimeWithNoAggregateMu ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") @NotYetSupported(Modes.CANNOT_JOIN_LOOKUP_NON_KEY) public void testFilterAndGroupByLookupUsingJoinOperatorWithValueFilterPushdownMatchesNothing(Map queryContext) @@ -724,8 +718,8 @@ public void testFilterAndGroupByLookupUsingJoinOperatorWithValueFilterPushdownMa ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testFilterAndGroupByLookupUsingJoinOperatorAllowNulls(Map queryContext) { // Cannot vectorize JOIN operator. @@ -768,8 +762,8 @@ public void testFilterAndGroupByLookupUsingJoinOperatorAllowNulls(Map queryContext) { @@ -824,9 +818,9 @@ public void testFilterAndGroupByLookupUsingJoinOperatorBackwards(Map queryContext) { @@ -870,8 +864,8 @@ public void testFilterAndGroupByLookupUsingJoinOperatorWithNotFilter(Map queryContext) { // MSQ does not support UNION ALL. @@ -922,9 +916,9 @@ public void testJoinUnionTablesOnLookup(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.CANNOT_JOIN_LOOKUP_NON_KEY) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testFilterAndGroupByLookupUsingJoinOperator(Map queryContext) { // Cannot vectorize JOIN operator. @@ -961,8 +955,8 @@ public void testFilterAndGroupByLookupUsingJoinOperator(Map quer ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testFilterAndGroupByLookupUsingPostAggregationJoinOperator(Map queryContext) { @@ -1018,8 +1012,8 @@ public void testFilterAndGroupByLookupUsingPostAggregationJoinOperator(Map queryContext) { // Cannot vectorize JOIN operator. @@ -1054,8 +1048,8 @@ public void testGroupByInnerJoinOnLookupUsingJoinOperator(Map qu ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testSelectOnLookupUsingInnerJoinOperator(Map queryContext) { testQuery( @@ -1086,8 +1080,8 @@ public void testSelectOnLookupUsingInnerJoinOperator(Map queryCo ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinTwoLookupsUsingJoinOperator(Map queryContext) { testQuery( @@ -1132,9 +1126,9 @@ public void testLeftJoinTwoLookupsUsingJoinOperator(Map queryCon ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinTableLookupLookupWithFilterWithOuterLimit(Map queryContext) { testQuery( @@ -1176,9 +1170,9 @@ public void testInnerJoinTableLookupLookupWithFilterWithOuterLimit(Map queryContext) { testQuery( @@ -1218,9 +1212,9 @@ public void testInnerJoinTableLookupLookupWithFilterWithoutLimit(Map queryContext) { @@ -1263,9 +1257,9 @@ public void testInnerJoinTableLookupLookupWithFilterWithOuterLimitWithAllColumns ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinTableLookupLookupWithFilterWithoutLimitWithAllColumns(Map queryContext) { testQuery( @@ -1305,9 +1299,9 @@ public void testInnerJoinTableLookupLookupWithFilterWithoutLimitWithAllColumns(M ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testManyManyInnerJoinOnManyManyLookup(Map queryContext) { testQuery( @@ -1535,9 +1529,9 @@ public void testManyManyInnerJoinOnManyManyLookup(Map queryConte ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.FINALIZING_FIELD_ACCESS) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinQueryOfLookup(Map queryContext) { // Cannot vectorize the subquery. @@ -1588,8 +1582,8 @@ public void testInnerJoinQueryOfLookup(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testTimeColumnAggregationsOnLookups(Map queryContext) { try { @@ -1602,7 +1596,7 @@ public void testTimeColumnAggregationsOnLookups(Map queryContext Assert.fail("Expected exception to be thrown."); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, new DruidExceptionMatcher(DruidException.Persona.ADMIN, DruidException.Category.INVALID_INPUT, "general") .expectMessageIs( @@ -1615,9 +1609,9 @@ public void testTimeColumnAggregationsOnLookups(Map queryContext } } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.DEFINETLY_WORSE_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinQueryOfLookupRemovable(Map queryContext) { // Like "testInnerJoinQueryOfLookup", but the subquery is removable. @@ -1654,9 +1648,9 @@ public void testInnerJoinQueryOfLookupRemovable(Map queryContext ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinTwoLookupsToTableUsingNumericColumn(Map queryContext) { // Regression test for https://github.com/apache/druid/issues/9646. @@ -1716,9 +1710,9 @@ public void testInnerJoinTwoLookupsToTableUsingNumericColumn(Map ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_TABLE_TABLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinTwoLookupsToTableUsingNumericColumnInReverse(Map queryContext) { @@ -1774,9 +1768,9 @@ public void testInnerJoinTwoLookupsToTableUsingNumericColumnInReverse(Map queryContext) { // Regression test for https://github.com/apache/druid/issues/9646. @@ -1857,9 +1851,9 @@ public void testInnerJoinLookupTableTable(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_TABLE_TABLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinLookupTableTableChained(Map queryContext) { // Cannot vectorize JOIN operator. @@ -2019,9 +2013,9 @@ public void testCommaJoinLeftFunction() // This SQL currently does not result in an optimum plan. // Unfortunately, we have disabled pushing down predicates (conditions and filters) due to https://github.com/apache/druid/pull/9773 // Hence, comma join will result in a cross join with filter on outermost - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testCommaJoinTableLookupTableMismatchedTypes(Map queryContext) { // Regression test for https://github.com/apache/druid/issues/9646. @@ -2086,9 +2080,9 @@ public void testCommaJoinTableLookupTableMismatchedTypes(Map que ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_TABLE_TABLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testJoinTableLookupTableMismatchedTypesWithoutComma(Map queryContext) { // Empty-dataset aggregation queries in MSQ return an empty row, rather than a single row as SQL requires. @@ -2157,9 +2151,9 @@ public void testJoinTableLookupTableMismatchedTypesWithoutComma(Map queryContext) { // foo.m1 is FLOAT, l.k is STRING. @@ -2192,8 +2186,8 @@ public void testInnerJoinCastLeft(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinCastRight(Map queryContext) { // foo.m1 is FLOAT, l.k is STRING. @@ -2239,8 +2233,8 @@ public void testInnerJoinCastRight(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinMismatchedTypes(Map queryContext) { // foo.m1 is FLOAT, l.k is STRING. Comparing them generates a CAST. @@ -2286,9 +2280,9 @@ public void testInnerJoinMismatchedTypes(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.JOIN_FILTER_LOCATIONS) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinLeftFunction(Map queryContext) { testQuery( @@ -2323,8 +2317,8 @@ public void testInnerJoinLeftFunction(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinRightFunction(Map queryContext) { testQuery( @@ -2366,8 +2360,8 @@ public void testInnerJoinRightFunction(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinLookupOntoLookupUsingJoinOperator(Map queryContext) { testQuery( @@ -2412,8 +2406,8 @@ public void testLeftJoinLookupOntoLookupUsingJoinOperator(Map qu ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinThreeLookupsUsingJoinOperator(Map queryContext) { testQuery( @@ -2466,8 +2460,8 @@ public void testLeftJoinThreeLookupsUsingJoinOperator(Map queryC ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testSelectOnLookupUsingLeftJoinOperator(Map queryContext) { testQuery( @@ -2511,8 +2505,8 @@ public void testSelectOnLookupUsingLeftJoinOperator(Map queryCon ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testSelectOnLookupUsingRightJoinOperator(Map queryContext) { // MSQ refuses to do RIGHT join with broadcast. @@ -2554,8 +2548,8 @@ public void testSelectOnLookupUsingRightJoinOperator(Map queryCo ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testSelectOnLookupUsingFullJoinOperator(Map queryContext) { // MSQ refuses to do FULL join with broadcast. @@ -2603,8 +2597,8 @@ public void testSelectOnLookupUsingFullJoinOperator(Map queryCon } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInAggregationSubquery(Map queryContext) { // Fully removing the join allows this query to vectorize. @@ -2653,8 +2647,8 @@ public void testInAggregationSubquery(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testNotInAggregationSubquery(Map queryContext) { // Cannot vectorize JOIN operator. @@ -2739,9 +2733,9 @@ public void testNotInAggregationSubquery(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.JOIN_FILTER_LOCATIONS) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testUsingSubqueryWithExtractionFns(Map queryContext) { // Cannot vectorize JOIN operator. @@ -2800,9 +2794,9 @@ public void testUsingSubqueryWithExtractionFns(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinWithIsNullFilter(Map queryContext) { testQuery( @@ -2833,9 +2827,9 @@ public void testInnerJoinWithIsNullFilter(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) - @Ignore // regression test for https://github.com/apache/druid/issues/9924 + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") + @Disabled // regression test for https://github.com/apache/druid/issues/9924 public void testInnerJoinOnMultiValueColumn(Map queryContext) { cannotVectorize(); @@ -2874,8 +2868,8 @@ public void testInnerJoinOnMultiValueColumn(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinOnTwoInlineDataSourcesWithTimeFilter(Map queryContext) { testQuery( @@ -2943,9 +2937,9 @@ public void testLeftJoinOnTwoInlineDataSourcesWithTimeFilter(Map ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinOnTwoInlineDataSourcesWithTimeFilter_withLeftDirectAccess(Map queryContext) { queryContext = withLeftDirectAccessEnabled(queryContext); @@ -3002,8 +2996,8 @@ public void testLeftJoinOnTwoInlineDataSourcesWithTimeFilter_withLeftDirectAcces ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinOnTwoInlineDataSourcesWithOuterWhere(Map queryContext) { testQuery( @@ -3055,9 +3049,9 @@ public void testLeftJoinOnTwoInlineDataSourcesWithOuterWhere(Map ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinOnTwoInlineDataSourcesWithOuterWhere_withLeftDirectAccess(Map queryContext) { queryContext = withLeftDirectAccessEnabled(queryContext); @@ -3104,8 +3098,8 @@ public void testLeftJoinOnTwoInlineDataSourcesWithOuterWhere_withLeftDirectAcces ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinOnTwoInlineDataSources(Map queryContext) { testQuery( @@ -3157,9 +3151,9 @@ public void testLeftJoinOnTwoInlineDataSources(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinOnTwoInlineDataSources_withLeftDirectAccess(Map queryContext) { queryContext = withLeftDirectAccessEnabled(queryContext); @@ -3206,8 +3200,8 @@ public void testLeftJoinOnTwoInlineDataSources_withLeftDirectAccess(Map queryContext) { Druids.ScanQueryBuilder baseScanBuilder = newScanQueryBuilder() @@ -3259,9 +3253,9 @@ public void testInnerJoinOnTwoInlineDataSourcesWithOuterWhere(Map queryContext) { queryContext = withLeftDirectAccessEnabled(queryContext); @@ -3308,8 +3302,8 @@ public void testInnerJoinOnTwoInlineDataSourcesWithOuterWhere_withLeftDirectAcce ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinOnTwoInlineDataSources(Map queryContext) { testQuery( @@ -3361,9 +3355,9 @@ public void testInnerJoinOnTwoInlineDataSources(Map queryContext ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testGroupByOverGroupByOverInnerJoinOnTwoInlineDataSources(Map queryContext) { skipVectorize(); @@ -3447,9 +3441,9 @@ public void testGroupByOverGroupByOverInnerJoinOnTwoInlineDataSources(Map queryContext) { queryContext = withLeftDirectAccessEnabled(queryContext); @@ -3499,8 +3493,9 @@ public void testInnerJoinOnTwoInlineDataSources_withLeftDirectAccess(Map queryContext) { assertQueryIsUnplannable( @@ -3589,13 +3584,13 @@ public void testLeftJoinRightTableCanBeEmpty() ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinSubqueryWithNullKeyFilter(Map queryContext) { // JoinFilterAnalyzer bug causes incorrect results on this test in replace-with-default mode. // This test case was originally added in https://github.com/apache/druid/pull/11434 with a note about this. - Assume.assumeFalse(NullHandling.replaceWithDefault() && QueryContext.of(queryContext).getEnableJoinFilterRewrite()); + Assumptions.assumeFalse(NullHandling.replaceWithDefault() && QueryContext.of(queryContext).getEnableJoinFilterRewrite()); // Cannot vectorize due to 'concat' expression. cannotVectorize(); @@ -3676,9 +3671,9 @@ public void testLeftJoinSubqueryWithNullKeyFilter(Map queryConte ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinSubqueryWithSelectorFilter(Map queryContext) { // Cannot vectorize due to 'concat' expression. @@ -3730,9 +3725,9 @@ public void testLeftJoinSubqueryWithSelectorFilter(Map queryCont ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_TABLE_TABLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testLeftJoinWithNotNullFilter(Map queryContext) { testQuery( @@ -3778,9 +3773,9 @@ public void testLeftJoinWithNotNullFilter(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_TABLE_TABLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoin(Map queryContext) { testQuery( @@ -3833,9 +3828,9 @@ public void testInnerJoin(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_TABLE_TABLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testJoinWithExplicitIsNotDistinctFromCondition(Map queryContext) { // Like "testInnerJoin", but uses IS NOT DISTINCT FROM instead of equals. @@ -3879,12 +3874,12 @@ public void testJoinWithExplicitIsNotDistinctFromCondition(Map q ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinSubqueryWithSelectorFilter(Map queryContext) { - if (sortBasedJoin) { + if (isSortBasedJoin()) { // Cannot handle the [l1.k = 'abc'] condition. msqIncompatible(); } @@ -3988,9 +3983,9 @@ public void testSemiJoinWithOuterTimeExtractScan() ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testTwoSemiJoinsSimultaneously(Map queryContext) { // Fully removing the join allows this query to vectorize. @@ -4054,8 +4049,8 @@ public void testTwoSemiJoinsSimultaneously(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testSemiAndAntiJoinSimultaneouslyUsingWhereInSubquery(Map queryContext) { cannotVectorize(); @@ -4160,9 +4155,9 @@ public void testSemiAndAntiJoinSimultaneouslyUsingWhereInSubquery(Map queryContext) { cannotVectorize(); @@ -4293,7 +4288,20 @@ public void testSemiJoinWithOuterTimeExtractAggregateWithOrderBy() ) ) ) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec( + "d0", + Direction.ASCENDING, + StringComparators.NUMERIC + ) + ), + Integer.MAX_VALUE + ) + ) .setContext(QUERY_CONTEXT_DEFAULT) .build() ), @@ -4306,26 +4314,28 @@ public void testSemiJoinWithOuterTimeExtractAggregateWithOrderBy() // This query is expected to fail as we do not support join on multi valued column // (see issue https://github.com/apache/druid/issues/9924 for more information) // TODO: Remove expected Exception when https://github.com/apache/druid/issues/9924 is fixed - @Test(expected = QueryException.class) - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testJoinOnMultiValuedColumnShouldThrowException(Map queryContext) { // MSQ throws a slightly different error than QueryException. msqIncompatible(); - final String query = "SELECT dim3, l.v from druid.foo f inner join lookup.lookyloo l on f.dim3 = l.k\n"; + assertThrows(QueryException.class, () -> { + final String query = "SELECT dim3, l.v from druid.foo f inner join lookup.lookyloo l on f.dim3 = l.k\n"; - testQuery( - query, - queryContext, - ImmutableList.of(), - ImmutableList.of() - ); + testQuery( + query, + queryContext, + ImmutableList.of(), + ImmutableList.of() + ); + }); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.UNION_WITH_COMPLEX_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testUnionAllTwoQueriesLeftQueryIsJoin(Map queryContext) { // MSQ does not support UNION ALL. @@ -4368,9 +4378,9 @@ public void testUnionAllTwoQueriesLeftQueryIsJoin(Map queryConte ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.UNION_WITH_COMPLEX_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testUnionAllTwoQueriesRightQueryIsJoin(Map queryContext) { // MSQ does not support UNION ALL. @@ -4462,8 +4472,8 @@ public void testUnionAllTwoQueriesBothQueriesAreJoin() ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testTopNFilterJoin(Map queryContext) { // Fully removing the join allows this query to vectorize. @@ -4527,8 +4537,8 @@ public void testTopNFilterJoin(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testTopNFilterJoinWithProjection(Map queryContext) { // Cannot vectorize JOIN operator. @@ -4598,9 +4608,9 @@ public void testTopNFilterJoinWithProjection(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) - @Ignore("Stopped working after the ability to join on subqueries was added to DruidJoinRule") + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") + @Disabled("Stopped working after the ability to join on subqueries was added to DruidJoinRule") public void testRemovableLeftJoin(Map queryContext) { // LEFT JOIN where the right-hand side can be ignored. @@ -4642,8 +4652,8 @@ public void testRemovableLeftJoin(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testCountDistinctOfLookupUsingJoinOperator(Map queryContext) { // Cannot yet vectorize the JOIN operator. @@ -4684,9 +4694,9 @@ public void testCountDistinctOfLookupUsingJoinOperator(Map query ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testJoinWithNonEquiCondition(Map queryContext) { // Native JOIN operator cannot handle the condition, so a SQL JOIN with greater-than is translated into a @@ -4694,7 +4704,7 @@ public void testJoinWithNonEquiCondition(Map queryContext) cannotVectorize(); // We don't handle non-equi join conditions for non-sql compatible mode. - Assume.assumeFalse(NullHandling.replaceWithDefault()); + Assumptions.assumeFalse(NullHandling.replaceWithDefault()); testQuery( "SELECT x.m1, y.m1 FROM foo x INNER JOIN foo y ON x.m1 > y.m1", @@ -4747,9 +4757,9 @@ public void testJoinWithNonEquiCondition(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testJoinWithEquiAndNonEquiCondition(Map queryContext) { // Native JOIN operator cannot handle the condition, so a SQL JOIN with greater-than is translated into a @@ -4757,7 +4767,7 @@ public void testJoinWithEquiAndNonEquiCondition(Map queryContext cannotVectorize(); // We don't handle non-equi join conditions for non-sql compatible mode. - Assume.assumeFalse(NullHandling.replaceWithDefault()); + Assumptions.assumeFalse(NullHandling.replaceWithDefault()); testQuery( "SELECT x.m1, y.m1 FROM foo x INNER JOIN foo y ON x.m1 = y.m1 AND x.m1 + y.m1 = 6.0", @@ -4793,9 +4803,9 @@ public void testJoinWithEquiAndNonEquiCondition(Map queryContext ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testUsingSubqueryAsPartOfAndFilter(Map queryContext) { // Fully removing the join allows this query to vectorize. @@ -4863,8 +4873,8 @@ public void testUsingSubqueryAsPartOfAndFilter(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testUsingSubqueryAsPartOfOrFilter(Map queryContext) { // Cannot vectorize JOIN operator. @@ -4953,9 +4963,9 @@ public void testUsingSubqueryAsPartOfOrFilter(Map queryContext) } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testNestedGroupByOnInlineDataSourceWithFilter(Map queryContext) { // Cannot vectorize due to virtual columns. @@ -5031,8 +5041,8 @@ public void testNestedGroupByOnInlineDataSourceWithFilter(Map qu ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testGroupByJoinAsNativeQueryWithUnoptimizedFilter(Map queryContext) { // The query below is the same as the inner groupBy on a join datasource from the test @@ -5105,8 +5115,8 @@ public void testGroupByJoinAsNativeQueryWithUnoptimizedFilter(Map queryContext) { testQuery( @@ -5146,9 +5156,9 @@ public void testCountOnSemiJoinSingleColumn(Map queryContext) ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testTopNOnStringWithNonSortedOrUniqueDictionary(Map queryContext) { testQuery( @@ -5187,9 +5197,9 @@ public void testTopNOnStringWithNonSortedOrUniqueDictionary(Map ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testTopNOnStringWithNonSortedOrUniqueDictionaryOrderByDim(Map queryContext) { @@ -5228,9 +5238,9 @@ public void testTopNOnStringWithNonSortedOrUniqueDictionaryOrderByDim(Map queryContext) { // Doesn't work in MSQ, although it's not really MSQ's fault. In MSQ, the second field (foo2.dim3) is returned as @@ -5285,9 +5295,9 @@ public void testVirtualColumnOnMVFilterJoinExpression(Map queryC ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.DEFINETLY_WORSE_PLAN) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testVirtualColumnOnMVFilterMultiJoinExpression(Map queryContext) { // Doesn't work in MSQ, although it's not really MSQ's fault. In MSQ, the second field (foo2.dim3) is returned as @@ -5365,9 +5375,9 @@ public void testVirtualColumnOnMVFilterMultiJoinExpression(Map q ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testInnerJoinWithFilterPushdownAndManyFiltersEmptyResults(Map queryContext) { // create the query we expect @@ -5473,9 +5483,9 @@ public void testInnerJoinWithFilterPushdownAndManyFiltersEmptyResults(Map queryContext) { // create the query we expect @@ -5641,9 +5651,9 @@ public void testPlanWithInFilterMoreThanInSubQueryThreshold() ); } - @Test - @Parameters(source = QueryContextForJoinProvider.class) @NotYetSupported(Modes.SORT_REMOVE_TROUBLE) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") public void testRegressionFilteredAggregatorsSubqueryJoins(Map queryContext) { cannotVectorize(); @@ -5897,7 +5907,7 @@ public void testJoinWithAliasAndOrderByNoGroupBy() @SuppressWarnings({"unchecked", "rawtypes"}) private List sortIfSortBased(final List results, final int... keyColumns) { - if (sortBasedJoin) { + if (isSortBasedJoin()) { final List retVal = new ArrayList<>(results); retVal.sort( (a, b) -> { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteLookupFunctionQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteLookupFunctionQueryTest.java index 41885eabb499..fb9f0c42bddf 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteLookupFunctionQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteLookupFunctionQueryTest.java @@ -54,10 +54,9 @@ import org.apache.druid.sql.calcite.rule.ReverseLookupRule; import org.apache.druid.sql.calcite.util.CalciteTests; import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; import org.junit.Assert; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Test; import javax.annotation.Nullable; import java.util.Arrays; @@ -65,6 +64,8 @@ import java.util.List; import java.util.Map; +import static org.hamcrest.MatcherAssert.assertThat; + public class CalciteLookupFunctionQueryTest extends BaseCalciteQueryTest { private static final Map QUERY_CONTEXT = @@ -772,7 +773,7 @@ public void testFilterMvContainsNullInjective() buildFilterTestSql("MV_CONTAINS(LOOKUP(dim1, 'lookyloo121'), NULL)"), QUERY_CONTEXT, NullHandling.sqlCompatible() - ? buildFilterTestExpectedQuery(expressionFilter("array_contains(\"dim1\",null)")) + ? buildFilterTestExpectedQuery(expressionFilter("array_contains(mv_harmonize_nulls(\"dim1\"),null)")) : buildFilterTestExpectedQueryAlwaysFalse(), ImmutableList.of() ); @@ -856,17 +857,12 @@ public void testFilterMvContainsIsNotTrue() testQuery( buildFilterTestSql("MV_CONTAINS(lookup(dim1, 'lookyloo'), 'xabc') IS NOT TRUE"), QUERY_CONTEXT, - NullHandling.sqlCompatible() - ? buildFilterTestExpectedQuery( - expressionVirtualColumn("v0", "lookup(\"dim1\",'lookyloo')", ColumnType.STRING), - not(equality("v0", "xabc", ColumnType.STRING)) - ) - : buildFilterTestExpectedQuery( - not(equality("dim1", "abc", ColumnType.STRING)) + buildFilterTestExpectedQuery( + NullHandling.sqlCompatible() + ? not(istrue(equality("dim1", "abc", ColumnType.STRING))) + : not(equality("dim1", "abc", ColumnType.STRING)) ), - NullHandling.sqlCompatible() - ? ImmutableList.of() - : ImmutableList.of(new Object[]{"", 5L}) + ImmutableList.of(new Object[]{NullHandling.defaultStringValue(), 5L}) ); } @@ -912,12 +908,10 @@ public void testFilterMvOverlapIsNotTrue() QUERY_CONTEXT, buildFilterTestExpectedQuery( NullHandling.sqlCompatible() - ? not(in("dim1", ImmutableList.of("x6", "xabc", "nonexistent"), EXTRACTION_FN)) + ? not(istrue(in("dim1", ImmutableList.of("6", "abc"), null))) : not(in("dim1", ImmutableList.of("6", "abc"), null)) ), - NullHandling.sqlCompatible() - ? Collections.emptyList() - : ImmutableList.of(new Object[]{NullHandling.defaultStringValue(), 5L}) + ImmutableList.of(new Object[]{NullHandling.defaultStringValue(), 5L}) ); } @@ -1591,7 +1585,7 @@ public void testFilterMaxUnapplyCount() ) ); - MatcherAssert.assertThat( + assertThat( e, ThrowableMessageMatcher.hasMessage(CoreMatchers.startsWith("Too many optimize calls[2]")) ); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteMultiValueStringQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteMultiValueStringQueryTest.java index a93a02a543d2..7a319dba3b0e 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteMultiValueStringQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteMultiValueStringQueryTest.java @@ -50,8 +50,8 @@ import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; import org.hamcrest.CoreMatchers; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.HashMap; @@ -327,14 +327,17 @@ public void testMultiValueStringOverlapFilterNonLiteral() newScanQueryBuilder() .dataSource(CalciteTests.DATASOURCE3) .eternityInterval() - .filters(expressionFilter("array_overlap(\"dim3\",array(\"dim2\"))")) + .filters(expressionFilter("array_overlap(mv_harmonize_nulls(\"dim3\"),array(\"dim2\"))")) .columns("dim3") .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) .limit(5) .context(QUERY_CONTEXT_DEFAULT) .build() ), - ImmutableList.of(new Object[]{"[\"a\",\"b\"]"}) + ImmutableList.of( + new Object[]{"[\"a\",\"b\"]"}, + new Object[]{NullHandling.defaultStringValue()} + ) ); } @@ -427,7 +430,7 @@ public void testMultiValueStringContainsArrayOfNonLiteral() newScanQueryBuilder() .dataSource(CalciteTests.DATASOURCE3) .eternityInterval() - .filters(expressionFilter("array_contains(\"dim3\",array(\"dim2\"))")) + .filters(expressionFilter("array_contains(mv_harmonize_nulls(\"dim3\"),array(\"dim2\"))")) .columns("dim3") .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) .limit(5) @@ -435,7 +438,8 @@ public void testMultiValueStringContainsArrayOfNonLiteral() .build() ), ImmutableList.of( - new Object[]{"[\"a\",\"b\"]"} + new Object[]{"[\"a\",\"b\"]"}, + new Object[]{NullHandling.defaultStringValue()} ) ); } @@ -2315,4 +2319,34 @@ public void testMvContainsFilterWithExtractionFn() ) ); } + + @Test + public void testMvContainsSelectColumns() + { + cannotVectorize(); + testQuery( + "SELECT MV_CONTAINS(dim3, ARRAY['a', 'b']), MV_OVERLAP(dim3, ARRAY['a', 'b']) FROM druid.numfoo LIMIT 5", + ImmutableList.of( + newScanQueryBuilder() + .dataSource(CalciteTests.DATASOURCE3) + .intervals(querySegmentSpec(Filtration.eternity())) + .columns("v0", "v1") + .virtualColumns( + expressionVirtualColumn("v0", "array_contains(mv_harmonize_nulls(\"dim3\"),array('a','b'))", ColumnType.LONG), + expressionVirtualColumn("v1", "array_overlap(mv_harmonize_nulls(\"dim3\"),array('a','b'))", ColumnType.LONG) + ) + .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) + .limit(5) + .context(QUERY_CONTEXT_DEFAULT) + .build() + ), + ImmutableList.of( + new Object[]{true, true}, + new Object[]{false, true}, + new Object[]{false, false}, + new Object[]{false, false}, + new Object[]{false, false} + ) + ); + } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java index ed0c0f97eae7..98a547b92394 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java @@ -75,10 +75,9 @@ import org.apache.druid.timeline.DataSegment; import org.apache.druid.timeline.partition.LinearShardSpec; import org.hamcrest.CoreMatchers; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -190,12 +189,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryRunnerFactoryConglomerate conglomerate, final JoinableFactoryWrapper joinableFactory, final Injector injector - ) throws IOException + ) { NestedDataModule.registerHandlersAndSerde(); final QueryableIndex index = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -211,7 +210,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryableIndex indexMix11 = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -228,7 +227,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryableIndex indexMix12 = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -244,7 +243,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryableIndex indexMix21 = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -260,7 +259,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryableIndex indexMix22 = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -276,7 +275,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( final QueryableIndex indexArrays = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -295,12 +294,12 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( ) ) .inputFormat(TestDataBuilder.DEFAULT_JSON_INPUT_FORMAT) - .inputTmpDir(temporaryFolder.newFolder()) + .inputTmpDir(newTempFolder()) .buildMMappedIndex(); final QueryableIndex indexAllTypesAuto = IndexBuilder.create() - .tmpDir(temporaryFolder.newFolder()) + .tmpDir(newTempFolder()) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) .schema( new IncrementalIndexSchema.Builder() @@ -319,7 +318,7 @@ public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( ) ) .inputFormat(TestDataBuilder.DEFAULT_JSON_INPUT_FORMAT) - .inputTmpDir(temporaryFolder.newFolder()) + .inputTmpDir(newTempFolder()) .buildMMappedIndex(); @@ -6626,6 +6625,46 @@ public void testJsonQueryArrays() .run(); } + @Test + public void testJsonQueryArrayNullArray() + { + cannotVectorize(); + testBuilder() + .sql("SELECT JSON_QUERY_ARRAY(arrayObject, '$.') FROM druid.arrays where arrayObject is null limit 1") + .queryContext(QUERY_CONTEXT_DEFAULT) + .expectedQueries( + ImmutableList.of( + Druids.newScanQueryBuilder() + .dataSource(DATA_SOURCE_ARRAYS) + .intervals(querySegmentSpec(Filtration.eternity())) + .virtualColumns( + expressionVirtualColumn( + "v0", + "null", + ColumnType.ofArray(ColumnType.NESTED_DATA) + ) + ) + .filters(isNull("arrayObject")) + .columns("v0") + .limit(1) + .context(QUERY_CONTEXT_DEFAULT) + .legacy(false) + .resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST) + .build() + ) + ) + .expectedResults( + NullHandling.replaceWithDefault() ? + ImmutableList.of(new Object[]{null}) : ImmutableList.of() + ) + .expectedSignature( + RowSignature.builder() + .add("EXPR$0", ColumnType.ofArray(ColumnType.NESTED_DATA)) + .build() + ) + .run(); + } + @Test public void testUnnestJsonQueryArrays() { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteParameterQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteParameterQueryTest.java index 5b4c9cd17b47..3f5f73bb98a4 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteParameterQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteParameterQueryTest.java @@ -42,7 +42,7 @@ import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.http.SqlParameter; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java index 8515fb8f9ac8..a48ce3df6b4a 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java @@ -123,16 +123,15 @@ import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.calcite.util.TestDataBuilder; import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; import org.joda.time.Period; import org.junit.Assert; -import org.junit.Assume; -import org.junit.Ignore; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; @@ -143,10 +142,10 @@ import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertThrows; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; public class CalciteQueryTest extends BaseCalciteQueryTest { @@ -177,6 +176,7 @@ public void testInformationSchemaTables() + "WHERE TABLE_TYPE IN ('SYSTEM_TABLE', 'TABLE', 'VIEW')", ImmutableList.of(), ImmutableList.builder() + .add(new Object[]{"druid", CalciteTests.ARRAYS_DATASOURCE, "TABLE", "NO", "NO"}) .add(new Object[]{"druid", CalciteTests.BROADCAST_DATASOURCE, "TABLE", "YES", "YES"}) .add(new Object[]{"druid", CalciteTests.DATASOURCE1, "TABLE", "NO", "NO"}) .add(new Object[]{"druid", CalciteTests.DATASOURCE2, "TABLE", "NO", "NO"}) @@ -217,6 +217,7 @@ public void testInformationSchemaTables() CalciteTests.SUPER_USER_AUTH_RESULT, ImmutableList.of(), ImmutableList.builder() + .add(new Object[]{"druid", CalciteTests.ARRAYS_DATASOURCE, "TABLE", "NO", "NO"}) .add(new Object[]{"druid", CalciteTests.BROADCAST_DATASOURCE, "TABLE", "YES", "YES"}) .add(new Object[]{"druid", CalciteTests.DATASOURCE1, "TABLE", "NO", "NO"}) .add(new Object[]{"druid", CalciteTests.DATASOURCE2, "TABLE", "NO", "NO"}) @@ -350,7 +351,7 @@ public void testCannotInsertWithNativeEngine() ) ); - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("INSERT operations are not supported by requested SQL engine [native], consider using MSQ.") ); @@ -369,7 +370,7 @@ public void testCannotReplaceWithNativeEngine() ) ); - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("REPLACE operations are not supported by the requested SQL engine [native]. Consider using MSQ.") ); @@ -2181,7 +2182,7 @@ public void testGroupByOrdinal() } @Test - @Ignore("Disabled since GROUP BY alias can confuse the validator; see DruidConformance::isGroupByAlias") + @Disabled("Disabled since GROUP BY alias can confuse the validator; see DruidConformance::isGroupByAlias") public void testGroupByAndOrderByAlias() { msqIncompatible(); @@ -2252,7 +2253,20 @@ public void testGroupByAndOrderByOrdinalOfAlias() .setGranularity(Granularities.ALL) .setDimensions(dimensions(new DefaultDimensionSpec("cnt", "d0", ColumnType.LONG))) .setAggregatorSpecs(aggregators(new CountAggregatorFactory("a0"))) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec( + "d0", + Direction.ASCENDING, + StringComparators.NUMERIC + ) + ), + Integer.MAX_VALUE + ) + ) .setContext(QUERY_CONTEXT_DEFAULT) .build() ), @@ -5776,7 +5790,7 @@ public void testUnplannableJoinQueriesInNonSQLCompatibleMode() { msqIncompatible(); - Assume.assumeFalse(NullHandling.sqlCompatible()); + Assumptions.assumeFalse(NullHandling.sqlCompatible()); assertQueryIsUnplannable( // JOIN condition with not-equals (<>). @@ -6292,7 +6306,7 @@ public void testCountStarWithTimeFilterUsingStringLiteralsInvalid_isUnplannable( testBuilder().sql(sql).run(); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("Illegal TIMESTAMP constant [CAST('z2000-01-01 00:00:00'):TIMESTAMP(3) NOT NULL]") ); @@ -7594,8 +7608,8 @@ public void testHighestMaxNumericInFilter() public void testQueryWithMoreThanMaxNumericInFilter() { assumeFalse( - "skip in sql compatible mode, this plans to an OR filter with equality filter children", - NullHandling.sqlCompatible() + NullHandling.sqlCompatible(), + "skip in sql compatible mode, this plans to an OR filter with equality filter children" ); msqIncompatible(); @@ -9276,7 +9290,20 @@ public void testTimeseriesUsingTimeFloorWithTimeShift() ) .setDimensions(dimensions(new DefaultDimensionSpec("v0", "d0", ColumnType.LONG))) .setAggregatorSpecs(aggregators(new LongSumAggregatorFactory("a0", "cnt"))) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec( + "d0", + Direction.ASCENDING, + StringComparators.NUMERIC + ) + ), + Integer.MAX_VALUE + ) + ) .setContext(QUERY_CONTEXT_DEFAULT) .build() ), @@ -9313,7 +9340,20 @@ public void testTimeseriesUsingTimeFloorWithTimestampAdd() ) .setDimensions(dimensions(new DefaultDimensionSpec("v0", "d0", ColumnType.LONG))) .setAggregatorSpecs(aggregators(new LongSumAggregatorFactory("a0", "cnt"))) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec( + "d0", + Direction.ASCENDING, + StringComparators.NUMERIC + ) + ), + Integer.MAX_VALUE + ) + ) .setContext(QUERY_CONTEXT_DEFAULT) .build() ), @@ -10207,7 +10247,20 @@ public void testGroupByExtractYear() ) .setDimensions(dimensions(new DefaultDimensionSpec("v0", "d0", ColumnType.LONG))) .setAggregatorSpecs(aggregators(new LongSumAggregatorFactory("a0", "cnt"))) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec( + "d0", + Direction.ASCENDING, + StringComparators.NUMERIC + ) + ), + Integer.MAX_VALUE + ) + ) .setContext(QUERY_CONTEXT_DEFAULT) .build() ), @@ -10245,7 +10298,20 @@ public void testGroupByFormatYearAndMonth() ) .setDimensions(dimensions(new DefaultDimensionSpec("v0", "d0", ColumnType.STRING))) .setAggregatorSpecs(aggregators(new LongSumAggregatorFactory("a0", "cnt"))) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec( + "d0", + Direction.ASCENDING, + StringComparators.LEXICOGRAPHIC + ) + ), + Integer.MAX_VALUE + ) + ) .setContext(QUERY_CONTEXT_DEFAULT) .build() ), @@ -10479,7 +10545,17 @@ public void testGroupByTimeAndOtherDimension() ) ) .setAggregatorSpecs(aggregators(new LongSumAggregatorFactory("a0", "cnt"))) - .setLimitSpec(NoopLimitSpec.instance()) + .setLimitSpec( + queryFramework().engine().featureAvailable(EngineFeature.GROUPBY_IMPLICITLY_SORTS) + ? NoopLimitSpec.instance() + : new DefaultLimitSpec( + ImmutableList.of( + new OrderByColumnSpec("d0", Direction.ASCENDING, StringComparators.LEXICOGRAPHIC), + new OrderByColumnSpec("d1", Direction.ASCENDING, StringComparators.NUMERIC) + ), + Integer.MAX_VALUE + ) + ) .setContext(withTimestampResultContext(QUERY_CONTEXT_DEFAULT, "d1", 1, Granularities.MONTH)) .build() ), @@ -11342,7 +11418,7 @@ public void testTimeExtractWithTooFewArguments() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs( "Invalid number of arguments to function 'TIME_EXTRACT'. Was expecting 2 arguments (line [1], column [8])" @@ -11536,7 +11612,7 @@ public void testProjectAfterSort2() } @Test - @Ignore("In Calcite 1.17, this test worked, but after upgrading to Calcite 1.21, this query fails with:" + @Disabled("In Calcite 1.17, this test worked, but after upgrading to Calcite 1.21, this query fails with:" + " org.apache.calcite.sql.validate.SqlValidatorException: Column 'dim1' is ambiguous") public void testProjectAfterSort3() { @@ -12354,7 +12430,6 @@ public void testRequireTimeConditionSemiJoinNegative() { msqIncompatible(); Throwable exception = assertThrows(CannotBuildQueryException.class, () -> { - testQuery( PLANNER_CONFIG_REQUIRE_TIME_CONDITION, "SELECT COUNT(*) FROM druid.foo\n" @@ -13912,14 +13987,16 @@ public void testStringAggExpression() ); } - @Test(expected = DruidException.class) + @Test public void testStringAggExpressionNonConstantSeparator() { - testQuery( - "SELECT STRING_AGG(DISTINCT CONCAT(dim1, dim2), CONCAT('|', dim1)) FROM foo", - ImmutableList.of(), - ImmutableList.of() - ); + assertThrows(DruidException.class, () -> { + testQuery( + "SELECT STRING_AGG(DISTINCT CONCAT(dim1, dim2), CONCAT('|', dim1)) FROM foo", + ImmutableList.of(), + ImmutableList.of() + ); + }); } @Test diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteReplaceDmlTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteReplaceDmlTest.java index cad3945fbd67..526479ab4567 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteReplaceDmlTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteReplaceDmlTest.java @@ -45,9 +45,8 @@ import org.apache.druid.sql.calcite.parser.DruidSqlReplace; import org.apache.druid.sql.calcite.planner.PlannerContext; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.hamcrest.MatcherAssert; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.Arrays; @@ -61,6 +60,7 @@ import static org.apache.druid.segment.column.ColumnType.LONG; import static org.apache.druid.segment.column.ColumnType.STRING; import static org.apache.druid.segment.column.ColumnType.ofComplex; +import static org.hamcrest.MatcherAssert.assertThat; public class CalciteReplaceDmlTest extends CalciteIngestionDmlTest { @@ -657,7 +657,7 @@ public void testReplaceWithPartitionedByContainingInvalidGranularity() Assert.fail("Exception should be thrown"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs( "Invalid granularity['invalid_granularity'] specified after PARTITIONED BY clause." diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteScanSignatureTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteScanSignatureTest.java index 954eda85b546..1d3a04255c63 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteScanSignatureTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteScanSignatureTest.java @@ -37,7 +37,7 @@ import org.apache.druid.sql.calcite.run.SqlEngine; import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.destination.IngestDestination; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; @@ -138,9 +138,9 @@ public String name() } @Override - public boolean featureAvailable(EngineFeature feature, PlannerContext plannerContext) + public boolean featureAvailable(EngineFeature feature) { - return feature == EngineFeature.SCAN_NEEDS_SIGNATURE || parent.featureAvailable(feature, plannerContext); + return feature == EngineFeature.SCAN_NEEDS_SIGNATURE || parent.featureAvailable(feature); } @Override diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSelectQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSelectQueryTest.java index 0e34c64043f4..75732c714bd9 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSelectQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSelectQueryTest.java @@ -52,7 +52,7 @@ import org.apache.druid.sql.calcite.util.CalciteTests; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.HashMap; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSimpleQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSimpleQueryTest.java index 5dee57dac32b..6fe2205a7ae0 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSimpleQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSimpleQueryTest.java @@ -36,7 +36,7 @@ import org.apache.druid.segment.column.ColumnType; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * This class tests simple aggregation SQL queries, i.e., no joins and no nested queries. diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteStrictInsertTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteStrictInsertTest.java index af72a8099e47..03045ebcaf52 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteStrictInsertTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteStrictInsertTest.java @@ -23,7 +23,7 @@ import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.planner.CatalogResolver; import org.apache.druid.sql.calcite.planner.CatalogResolver.NullCatalogResolver; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Test for the "strict" feature of the catalog which can restrict INSERT statements diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSubqueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSubqueryTest.java index 5a3fac0b0774..e469b4383737 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSubqueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSubqueryTest.java @@ -65,10 +65,9 @@ import org.hamcrest.CoreMatchers; import org.joda.time.DateTimeZone; import org.joda.time.Period; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.Arrays; @@ -88,23 +87,8 @@ * 1. Where the memory limit is not set. The intermediate results are materialized as inline rows * 2. Where the memory limit is set. The intermediate results are materialized as frames */ -@RunWith(Parameterized.class) public class CalciteSubqueryTest extends BaseCalciteQueryTest { - - public String testName; - public Map queryContext; - - public CalciteSubqueryTest( - String testName, - Map queryContext - ) - { - this.testName = testName; - this.queryContext = queryContext; - } - - @Parameterized.Parameters(name = "{0}") public static Iterable constructorFeeder() { final List constructors = new ArrayList<>(); @@ -117,8 +101,9 @@ public static Iterable constructorFeeder() return constructors; } - @Test - public void testExactCountDistinctUsingSubqueryWithWhereToOuterFilter() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testExactCountDistinctUsingSubqueryWithWhereToOuterFilter(String testName, Map queryContext) { // Cannot vectorize topN operator. cannotVectorize(); @@ -165,8 +150,9 @@ public void testExactCountDistinctUsingSubqueryWithWhereToOuterFilter() ); } - @Test - public void testExactCountDistinctOfSemiJoinResult() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testExactCountDistinctOfSemiJoinResult(String testName, Map queryContext) { // Cannot vectorize due to extraction dimension spec. cannotVectorize(); @@ -240,8 +226,9 @@ public void testExactCountDistinctOfSemiJoinResult() ); } - @Test - public void testTwoExactCountDistincts() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testTwoExactCountDistincts(String testName, Map queryContext) { testQuery( PLANNER_CONFIG_NO_HLL, @@ -317,8 +304,9 @@ public void testTwoExactCountDistincts() ); } - @Test - public void testViewAndJoin() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testViewAndJoin(String testName, Map queryContext) { cannotVectorize(); Map queryContextModified = withLeftDirectAccessEnabled(queryContext); @@ -384,8 +372,9 @@ public void testViewAndJoin() ); } - @Test - public void testGroupByWithPostAggregatorReferencingTimeFloorColumnOnTimeseries() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testGroupByWithPostAggregatorReferencingTimeFloorColumnOnTimeseries(String testName, Map queryContext) { cannotVectorize(); @@ -431,8 +420,9 @@ public void testGroupByWithPostAggregatorReferencingTimeFloorColumnOnTimeseries( ); } - @Test - public void testUsingSubqueryAsFilterWithInnerSort() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testUsingSubqueryAsFilterWithInnerSort(String testName, Map queryContext) { // Regression test for https://github.com/apache/druid/issues/4208 @@ -484,8 +474,9 @@ public void testUsingSubqueryAsFilterWithInnerSort() ); } - @Test - public void testUsingSubqueryAsFilterOnTwoColumns() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testUsingSubqueryAsFilterOnTwoColumns(String testName, Map queryContext) { testQuery( "SELECT __time, cnt, dim1, dim2 FROM druid.foo " @@ -544,8 +535,9 @@ public void testUsingSubqueryAsFilterOnTwoColumns() ); } - @Test - public void testMinMaxAvgDailyCountWithLimit() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testMinMaxAvgDailyCountWithLimit(String testName, Map queryContext) { // Cannot vectorize due to virtual columns. cannotVectorize(); @@ -613,8 +605,9 @@ public void testMinMaxAvgDailyCountWithLimit() ); } - @Test - public void testEmptyGroupWithOffsetDoesntInfiniteLoop() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testEmptyGroupWithOffsetDoesntInfiniteLoop(String testName, Map queryContext) { testQuery( "SELECT r0.c, r1.c\n" @@ -676,17 +669,18 @@ public void testEmptyGroupWithOffsetDoesntInfiniteLoop() } - @Test - public void testMaxSubqueryRows() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testMaxSubqueryRows(String testName, Map queryContext) { if ("without memory limit".equals(testName)) { - testMaxSubqueryRowsWithoutMemoryLimit(); + testMaxSubqueryRowsWithoutMemoryLimit(testName, queryContext); } else { - testMaxSubQueryRowsWithLimit(); + testMaxSubQueryRowsWithLimit(testName, queryContext); } } - private void testMaxSubqueryRowsWithoutMemoryLimit() + private void testMaxSubqueryRowsWithoutMemoryLimit(String testName, Map queryContext) { Map modifiedQueryContext = new HashMap<>(queryContext); modifiedQueryContext.put(QueryContexts.MAX_SUBQUERY_ROWS_KEY, 1); @@ -711,7 +705,7 @@ private void testMaxSubqueryRowsWithoutMemoryLimit() ); } - private void testMaxSubQueryRowsWithLimit() + private void testMaxSubQueryRowsWithLimit(String testName, Map queryContext) { // Since the results are materializable as frames, we are able to use the memory limit and donot rely on the // row limit for the subquery @@ -760,8 +754,9 @@ private void testMaxSubQueryRowsWithLimit() ); } - @Test - public void testZeroMaxNumericInFilter() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testZeroMaxNumericInFilter(String testName, Map queryContext) { Throwable exception = assertThrows(UOE.class, () -> { @@ -785,8 +780,9 @@ public void testZeroMaxNumericInFilter() assertTrue(exception.getMessage().contains("[maxNumericInFilters] must be greater than 0")); } - @Test - public void testUseTimeFloorInsteadOfGranularityOnJoinResult() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testUseTimeFloorInsteadOfGranularityOnJoinResult(String testName, Map queryContext) { cannotVectorize(); @@ -867,8 +863,9 @@ public void testUseTimeFloorInsteadOfGranularityOnJoinResult() ); } - @Test - public void testJoinWithTimeDimension() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testJoinWithTimeDimension(String testName, Map queryContext) { testQuery( PLANNER_CONFIG_DEFAULT, @@ -903,8 +900,9 @@ public void testJoinWithTimeDimension() ); } - @Test - public void testUsingSubqueryWithLimit() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testUsingSubqueryWithLimit(String testName, Map queryContext) { // Cannot vectorize scan query. cannotVectorize(); @@ -935,8 +933,9 @@ public void testUsingSubqueryWithLimit() ); } - @Test - public void testSelfJoin() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSelfJoin(String testName, Map queryContext) { // Cannot vectorize due to virtual columns. cannotVectorize(); @@ -986,8 +985,9 @@ public void testSelfJoin() ); } - @Test - public void testJoinWithSubqueries() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testJoinWithSubqueries(String testName, Map queryContext) { cannotVectorize(); @@ -1072,8 +1072,9 @@ public void testJoinWithSubqueries() ); } - @Test - public void testSingleValueFloatAgg() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSingleValueFloatAgg(String testName, Map queryContext) { skipVectorize(); cannotVectorize(); @@ -1130,8 +1131,9 @@ public void testSingleValueFloatAgg() ); } - @Test - public void testSingleValueDoubleAgg() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSingleValueDoubleAgg(String testName, Map queryContext) { skipVectorize(); cannotVectorize(); @@ -1188,8 +1190,9 @@ public void testSingleValueDoubleAgg() ); } - @Test - public void testSingleValueLongAgg() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSingleValueLongAgg(String testName, Map queryContext) { skipVectorize(); cannotVectorize(); @@ -1249,8 +1252,9 @@ public void testSingleValueLongAgg() ); } - @Test - public void testSingleValueStringAgg() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSingleValueStringAgg(String testName, Map queryContext) { skipVectorize(); cannotVectorize(); @@ -1311,8 +1315,9 @@ public void testSingleValueStringAgg() ); } - @Test - public void testSingleValueStringMultipleRowsAgg() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSingleValueStringMultipleRowsAgg(String testName, Map queryContext) { skipVectorize(); cannotVectorize(); @@ -1323,8 +1328,9 @@ public void testSingleValueStringMultipleRowsAgg() ); } - @Test - public void testSingleValueEmptyInnerAgg() + @MethodSource("constructorFeeder") + @ParameterizedTest(name = "{0}") + public void testSingleValueEmptyInnerAgg(String testName, Map queryContext) { skipVectorize(); cannotVectorize(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSysQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSysQueryTest.java index 0dc74f9a1bc3..f03b13d8e262 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSysQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteSysQueryTest.java @@ -24,14 +24,12 @@ import org.apache.druid.sql.calcite.NotYetSupported.Modes; import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor; import org.apache.druid.sql.calcite.planner.PlannerContext; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(NotYetSupportedProcessor.class) public class CalciteSysQueryTest extends BaseCalciteQueryTest { - @Rule(order = 0) - public NotYetSupportedProcessor negativeTestProcessor = new NotYetSupportedProcessor(); - @Test public void testTasksSum() { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTableAppendTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTableAppendTest.java index f15786c3fb9f..8b6170cbfaac 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTableAppendTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTableAppendTest.java @@ -24,19 +24,15 @@ import org.apache.druid.query.Druids; import org.apache.druid.query.scan.ScanQuery.ResultFormat; import org.apache.druid.segment.column.ColumnType; -import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.hamcrest.MatcherAssert; import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; public class CalciteTableAppendTest extends BaseCalciteQueryTest { - @Rule(order = 0) - public NotYetSupportedProcessor negativeTestProcessor = new NotYetSupportedProcessor(); - @Test public void testUnion() { @@ -230,7 +226,7 @@ public void testAppendNoTableIsInvalid() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("No match found for function signature APPEND() (line [1], column [24])") ); @@ -247,7 +243,7 @@ public void testAppendtInvalidIntegerArg() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs( "All arguments to APPEND should be literal strings. Argument #2 is not string (line [1], column [37])" @@ -266,7 +262,7 @@ public void testAppendtNullArg() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs( "All arguments to APPEND should be literal strings. Argument #2 is not string (line [1], column [37])" @@ -285,7 +281,7 @@ public void testAppendtNonExistentTable() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("Table [nonexistent] not found (line [1], column [37])") ); @@ -303,7 +299,7 @@ public void testAppendCTE() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat( + assertThat( e, invalidSqlIs("Table [t0] not found (line [1], column [62])") ); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTimeBoundaryQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTimeBoundaryQueryTest.java index c4cd002fbeac..ff0555cb3588 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTimeBoundaryQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteTimeBoundaryQueryTest.java @@ -33,7 +33,7 @@ import org.apache.druid.segment.join.JoinType; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.HashMap; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java index af0066bba051..54adbc7d9001 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java @@ -31,9 +31,10 @@ import org.apache.druid.sql.calcite.NotYetSupported.Modes; import org.apache.druid.sql.calcite.filtration.Filtration; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.hamcrest.MatcherAssert; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; public class CalciteUnionQueryTest extends BaseCalciteQueryTest { @@ -137,7 +138,7 @@ public void testUnionAllTablesColumnCountMismatch() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [42])")); + assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [42])")); } } @@ -365,7 +366,7 @@ public void testUnionAllThreeTablesColumnCountMismatch1() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [45])")); + assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [45])")); } } @@ -385,7 +386,7 @@ public void testUnionAllThreeTablesColumnCountMismatch2() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [45])")); + assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [45])")); } } @@ -405,7 +406,7 @@ public void testUnionAllThreeTablesColumnCountMismatch3() Assert.fail("query execution should fail"); } catch (DruidException e) { - MatcherAssert.assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [70])")); + assertThat(e, invalidSqlIs("Column count mismatch in UNION ALL (line [3], column [70])")); } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteWindowQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteWindowQueryTest.java index eb008a43296a..2d5492da5ee5 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteWindowQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteWindowQueryTest.java @@ -37,11 +37,9 @@ import org.apache.druid.sql.calcite.QueryTestRunner.QueryResults; import org.apache.druid.sql.calcite.QueryVerification.QueryResultsVerifier; import org.apache.druid.sql.calcite.planner.PlannerContext; -import org.hamcrest.Matchers; import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.io.File; import java.net.URL; @@ -53,12 +51,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * These tests are file-based, look in resources -> calcite/tests/window for the set of test specifications. */ -@RunWith(Parameterized.class) public class CalciteWindowQueryTest extends BaseCalciteQueryTest { @@ -72,8 +69,7 @@ public class CalciteWindowQueryTest extends BaseCalciteQueryTest private static final ObjectMapper YAML_JACKSON = new DefaultObjectMapper(new YAMLFactory(), "tests"); - @Parameterized.Parameters(name = "{0}") - public static Object parametersForWindowQueryTest() throws Exception + public static Object[] parametersForWindowQueryTest() throws Exception { final URL windowFolderUrl = ClassLoader.getSystemResource("calcite/tests/window"); File windowFolder = new File(windowFolderUrl.toURI()); @@ -86,13 +82,6 @@ public static Object parametersForWindowQueryTest() throws Exception .toArray(); } - private final String filename; - - public CalciteWindowQueryTest(String filename) - { - this.filename = filename; - } - class TestCase implements QueryResultsVerifier { private WindowQueryTestInputClass input; @@ -200,13 +189,14 @@ private void maybeDumpActualResults(List results) throws Exception } } - @Test + @MethodSource("parametersForWindowQueryTest") + @ParameterizedTest(name = "{0}") @SuppressWarnings("unchecked") - public void windowQueryTest() throws Exception + public void windowQueryTest(String filename) throws Exception { TestCase testCase = new TestCase(filename); - assumeThat(testCase.getType(), Matchers.not(TestType.failingTest)); + assumeTrue(testCase.getType() != TestType.failingTest); if (testCase.getType() == TestType.operatorValidation) { testBuilder() @@ -222,13 +212,14 @@ public void windowQueryTest() throws Exception } } - @Test + @MethodSource("parametersForWindowQueryTest") + @ParameterizedTest(name = "{0}") @SuppressWarnings("unchecked") - public void windowQueryTestWithCustomContextMaxSubqueryBytes() throws Exception + public void windowQueryTestWithCustomContextMaxSubqueryBytes(String filename) throws Exception { TestCase testCase = new TestCase(filename); - assumeThat(testCase.getType(), Matchers.not(TestType.failingTest)); + assumeTrue(testCase.getType() != TestType.failingTest); if (testCase.getType() == TestType.operatorValidation) { testBuilder() diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java index 05fb6761a412..7c73b1125063 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java @@ -20,32 +20,28 @@ package org.apache.druid.sql.calcite; import com.google.common.collect.ImmutableMap; -import junitparams.Parameters; import org.apache.druid.query.QueryContexts; import org.apache.druid.server.security.AuthConfig; +import org.apache.druid.sql.calcite.DisableUnless.DisableUnlessRule; import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor; import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.SqlTestFramework; import org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; - +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Map; import static org.junit.Assert.assertNotNull; +@ExtendWith(NotYetSupportedProcessor.class) public class DecoupledPlanningCalciteJoinQueryTest extends CalciteJoinQueryTest { + @RegisterExtension + public DisableUnlessRule sqlCompatOnly = DisableUnless.SQL_COMPATIBLE; - @Rule(order = 0) - public NotYetSupportedProcessor decoupledIgnoreProcessor = new NotYetSupportedProcessor(); - - @Rule - public TestRule sqlCompatOnly = DisableUnless.SQL_COMPATIBLE; - - private static final ImmutableMap CONTEXT_OVERRIDES = - ImmutableMap.builder() + private static final ImmutableMap CONTEXT_OVERRIDES = ImmutableMap.builder() .putAll(BaseCalciteQueryTest.QUERY_CONTEXT_DEFAULT) .put(PlannerConfig.CTX_NATIVE_QUERY_SQL_PLANNING_MODE, PlannerConfig.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED) .put(QueryContexts.ENABLE_DEBUG, true) @@ -78,8 +74,8 @@ public SqlTestFramework.PlannerFixture plannerFixture(PlannerConfig plannerConfi return builder; } - @Test - @Parameters(source = QueryContextForJoinProvider.class) + @MethodSource("provideQueryContexts") + @ParameterizedTest(name = "{0}") @DecoupledTestConfig public void ensureDecoupledTestConfigAnnotationWorks(Map queryContext) { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java index cf7d47ee084b..1a0ae448319f 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java @@ -26,14 +26,11 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.SqlTestFramework; import org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier; -import org.junit.Rule; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(NotYetSupportedProcessor.class) public class DecoupledPlanningCalciteQueryTest extends CalciteQueryTest { - - @Rule(order = 0) - public NotYetSupportedProcessor decoupledIgnoreProcessor = new NotYetSupportedProcessor(); - private static final ImmutableMap CONTEXT_OVERRIDES = ImmutableMap.builder() .putAll(BaseCalciteQueryTest.QUERY_CONTEXT_DEFAULT) diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java index 1e8c3d0b37de..7ddfc59c9bcd 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java @@ -26,14 +26,11 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.SqlTestFramework; import org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier; -import org.junit.Rule; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(NotYetSupportedProcessor.class) public class DecoupledPlanningCalciteUnionQueryTest extends CalciteUnionQueryTest { - - @Rule(order = 0) - public NotYetSupportedProcessor decoupledIgnoreProcessor = new NotYetSupportedProcessor(); - private static final ImmutableMap CONTEXT_OVERRIDES = ImmutableMap.builder() .putAll(BaseCalciteQueryTest.QUERY_CONTEXT_DEFAULT) diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DisableUnless.java b/sql/src/test/java/org/apache/druid/sql/calcite/DisableUnless.java index 81f864f7edf3..eead4c61a6a8 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DisableUnless.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DisableUnless.java @@ -21,20 +21,20 @@ import com.google.common.base.Supplier; import org.apache.druid.common.config.NullHandling; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import static org.junit.Assume.assumeTrue; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; /** * Collection of conditional disabler rules. */ class DisableUnless { - public static final TestRule SQL_COMPATIBLE = new DisableUnlessRule("NullHandling::sqlCompatible", NullHandling::sqlCompatible); + public static final DisableUnlessRule SQL_COMPATIBLE = new DisableUnlessRule( + "NullHandling::sqlCompatible", NullHandling::sqlCompatible + ); - public static class DisableUnlessRule implements TestRule + public static class DisableUnlessRule implements ExecutionCondition { private Supplier predicate; private String message; @@ -46,10 +46,13 @@ public DisableUnlessRule(String message, Supplier predicate) } @Override - public Statement apply(Statement base, Description description) + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - assumeTrue("Testcase disabled; because condition not met: " + message, predicate.get()); - return base; + if (predicate.get()) { + return ConditionEvaluationResult.enabled("condition not met"); + } else { + return ConditionEvaluationResult.disabled(message); + } } } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DrillWindowQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/DrillWindowQueryTest.java index 078eb8c9834d..1cf08165c8e4 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DrillWindowQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DrillWindowQueryTest.java @@ -52,6 +52,7 @@ import org.apache.druid.segment.join.JoinableFactoryWrapper; import org.apache.druid.segment.writeout.OnHeapMemorySegmentWriteOutMediumFactory; import org.apache.druid.server.SpecificSegmentsQuerySegmentWalker; +import org.apache.druid.sql.calcite.DisableUnless.DisableUnlessRule; import org.apache.druid.sql.calcite.NotYetSupported.Modes; import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor; import org.apache.druid.sql.calcite.QueryTestRunner.QueryResults; @@ -62,14 +63,14 @@ import org.joda.time.DateTime; import org.joda.time.LocalTime; import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; import javax.annotation.Nullable; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -111,19 +112,18 @@ public class DrillWindowQueryTest extends BaseCalciteQueryTest { private static final ObjectMapper MAPPER = new DefaultObjectMapper(); - private DrillTestCase testCase = null; static { NullHandling.initializeForTests(); } - @Rule - public TestRule disableWhenNonSqlCompat = DisableUnless.SQL_COMPATIBLE; + @RegisterExtension + public DisableUnlessRule disableWhenNonSqlCompat = DisableUnless.SQL_COMPATIBLE; - @Rule + @RegisterExtension public NotYetSupportedProcessor ignoreProcessor = new NotYetSupportedProcessor(); - @Rule + @RegisterExtension public DrillTestCaseLoaderRule drillTestCaseRule = new DrillTestCaseLoaderRule(); @Test @@ -174,15 +174,16 @@ public void ensureAllDeclared() throws Exception String value(); } - class DrillTestCaseLoaderRule implements TestRule + static class DrillTestCaseLoaderRule implements BeforeEachCallback { + public DrillTestCase testCase = null; @Override - public Statement apply(Statement base, Description description) + public void beforeEach(ExtensionContext context) { - DrillTest annotation = description.getAnnotation(DrillTest.class); + Method method = context.getTestMethod().get(); + DrillTest annotation = method.getAnnotation(DrillTest.class); testCase = (annotation == null) ? null : new DrillTestCase(annotation.value()); - return base; } } @@ -241,7 +242,8 @@ private String readStringFromResource(String s) throws IOException public SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( QueryRunnerFactoryConglomerate conglomerate, JoinableFactoryWrapper joinableFactory, - Injector injector) throws IOException + Injector injector + ) { final SpecificSegmentsQuerySegmentWalker retVal = super.createQuerySegmentWalker( conglomerate, @@ -473,6 +475,7 @@ public void windowQueryTest() try { thread = Thread.currentThread(); oldName = thread.getName(); + DrillTestCase testCase = drillTestCaseRule.testCase; thread.setName("drillWindowQuery-" + testCase.filename); testBuilder() @@ -495,14 +498,13 @@ public void windowQueryTest() @SuppressWarnings({"rawtypes", "unchecked"}) private void attachIndex(SpecificSegmentsQuerySegmentWalker texasRanger, String dataSource, DimensionSchema... dims) - throws IOException { ArrayList dimensionNames = new ArrayList<>(dims.length); for (DimensionSchema dimension : dims) { dimensionNames.add(dimension.getName()); } - final File tmpFolder = temporaryFolder.newFolder(); + final File tmpFolder = newTempFolder(); final QueryableIndex queryableIndex = IndexBuilder .create() .tmpDir(new File(tmpFolder, dataSource)) diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java index 6423aae3af1b..ed7c40217cb2 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/DruidPlannerResourceAnalyzeTest.java @@ -28,7 +28,7 @@ import org.apache.druid.server.security.ResourceType; import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/IngestTableFunctionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/IngestTableFunctionTest.java index 2472736719f5..97556b16fb20 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/IngestTableFunctionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/IngestTableFunctionTest.java @@ -46,9 +46,8 @@ import org.apache.druid.sql.calcite.util.CalciteTests; import org.apache.druid.sql.http.SqlParameter; import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.Test; import org.junit.internal.matchers.ThrowableMessageMatcher; +import org.junit.jupiter.api.Test; import java.io.File; import java.net.URI; @@ -56,6 +55,7 @@ import java.util.Arrays; import java.util.Collections; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -363,8 +363,7 @@ public void testExplainHttpFnUnauthorized() .authResult(CalciteTests.REGULAR_USER_AUTH_RESULT) .run() ); - MatcherAssert.assertThat(e, ThrowableMessageMatcher.hasMessage(CoreMatchers.equalTo(Access.DEFAULT_ERROR_MESSAGE))); - + assertThat(e, ThrowableMessageMatcher.hasMessage(CoreMatchers.equalTo(Access.DEFAULT_ERROR_MESSAGE))); } @Test diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/IngestionTestSqlEngine.java b/sql/src/test/java/org/apache/druid/sql/calcite/IngestionTestSqlEngine.java index 0a18eb47f46a..1c0598187348 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/IngestionTestSqlEngine.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/IngestionTestSqlEngine.java @@ -75,7 +75,7 @@ public RelDataType resultTypeForInsert(RelDataTypeFactory typeFactory, RelDataTy } @Override - public boolean featureAvailable(final EngineFeature feature, final PlannerContext plannerContext) + public boolean featureAvailable(final EngineFeature feature) { switch (feature) { case CAN_SELECT: @@ -85,6 +85,7 @@ public boolean featureAvailable(final EngineFeature feature, final PlannerContex case TIME_BOUNDARY_QUERY: case SCAN_NEEDS_SIGNATURE: case UNNEST: + case GROUPBY_IMPLICITLY_SORTS: return false; case CAN_INSERT: case CAN_REPLACE: diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java b/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java index 358d44267dcf..868d5ecdced8 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java @@ -20,27 +20,21 @@ package org.apache.druid.sql.calcite; import com.google.common.base.Throwables; -import junitparams.JUnitParamsRunner; -import org.apache.commons.lang3.RegExUtils; import org.apache.druid.error.DruidException; import org.apache.druid.java.util.common.ISE; import org.junit.AssumptionViolatedException; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.junit.runners.model.Statement; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.opentest4j.IncompleteExecutionException; -import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.junit.Assert.assertThrows; @@ -48,22 +42,27 @@ * Can be used to mark tests which are not-yet supported for some reason. * * In case a testcase marked with this annotation fails - it means that the - * testcase no longer fails with the annotated expectation. This means that a code change affected this test either + * testcase no longer fails with the annotated expectation. This means that a + * code change affected this test either * *
      - *
    1. it suddenly passes: yay, assuming it makes sense that it suddenly passes, remove the annotation and move on
    2. - *
    3. it suddenly fails with a different error: validate that the new error is expected and either fix to continue failing with the old error or update the expected error.
    4. + *
    5. it suddenly passes: yay, assuming it makes sense that it suddenly passes, + * remove the annotation and move on
    6. + *
    7. it suddenly fails with a different error: validate that the new error is + * expected and either fix to continue failing with the old error or update the + * expected error.
    8. *
    * - * During usage; the annotation process have to be added to the testclass. - * Ensure that it's loaded as the most outer-rule by using order=0 - otherwise - * it may interfere with other rules: + * During usage; the annotation process have to be added to registered with the testclass. + * Ensure that it's loaded as the most outer-rule by using the right ExtendWith order - or by + * specifying Order: * - * @Rule(order = 0) + * @Order(0) + * @RegisterExtension * public TestRule notYetSupportedRule = new NotYetSupportedProcessor(); * * @NotYetSupported(NOT_ENOUGH_RULES) - * @Test + * @Test * public void testA() { * } * @@ -77,6 +76,7 @@ enum Modes { + // @formatter:off NOT_ENOUGH_RULES(DruidException.class, "not enough rules"), ERROR_HANDLING(AssertionError.class, "targetPersona: is <[A-Z]+> and category: is <[A-Z_]+> and errorCode: is"), EXPRESSION_NOT_GROUPED(DruidException.class, "Expression '[a-z]+' is not being grouped"), @@ -103,6 +103,7 @@ enum Modes SORT_REMOVE_TROUBLE(DruidException.class, "Calcite assertion violated.*Sort\\."), STACK_OVERFLOW(StackOverflowError.class, ""), CANNOT_JOIN_LOOKUP_NON_KEY(RuntimeException.class, "Cannot join lookup with condition referring to non-key"); + // @formatter:on public Class throwableClass; public String regex; @@ -125,33 +126,38 @@ Pattern getPattern() * Ensures that test cases disabled with that annotation can still not pass. * If the error is as expected; the testcase is marked as "ignored". */ - class NotYetSupportedProcessor implements TestRule + class NotYetSupportedProcessor implements InvocationInterceptor { @Override - public Statement apply(Statement base, Description description) + public void interceptTestMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { - NotYetSupported annotation = getAnnotation(description, NotYetSupported.class); + Method method = extensionContext.getTestMethod().get(); + NotYetSupported annotation = method.getAnnotation(NotYetSupported.class); if (annotation == null) { - return base; + invocation.proceed(); + return; } - return new Statement() { - @Override - public void evaluate() { Modes ignoreMode = annotation.value(); Throwable e = null; try { - base.evaluate(); + invocation.proceed(); } catch (Throwable t) { e = t; } - // If the base test case is supposed to be ignored already, just skip the further evaluation + // If the base test case is supposed to be ignored already, just skip + // the further evaluation if (e instanceof AssumptionViolatedException) { throw (AssumptionViolatedException) e; } + if (e instanceof IncompleteExecutionException) { + throw (IncompleteExecutionException) e; + } Throwable finalE = e; assertThrows( "Expected that this testcase will fail - it might got fixed; or failure have changed?", @@ -171,40 +177,15 @@ public void evaluate() } throw new AssumptionViolatedException("Test is not-yet supported; ignored with:" + annotation); } - }; - } - - private static Method getMethodForName(Class testClass, String realMethodName) - { - List matches = Stream.of(testClass.getMethods()) - .filter(m -> realMethodName.equals(m.getName())) - .collect(Collectors.toList()); - switch (matches.size()) { - case 0: - throw new IllegalArgumentException("Expected to find method...but there is none?"); - case 1: - return matches.get(0); - default: - throw new IllegalArgumentException("method overrides are not supported"); } } - public static T getAnnotation(Description description, Class annotationType) + @Override + public void interceptTestTemplateMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { - T annotation = description.getAnnotation(annotationType); - if (annotation != null) { - return annotation; - } - Class testClass = description.getTestClass(); - RunWith runWith = testClass.getAnnotation(RunWith.class); - if (runWith == null || !runWith.value().equals(JUnitParamsRunner.class)) { - return null; - } - String mehodName = description.getMethodName(); - String realMethodName = RegExUtils.replaceAll(mehodName, "\\(.*", ""); - - Method m = getMethodForName(testClass, realMethodName); - return m.getAnnotation(annotationType); + interceptTestMethod(invocation, invocationContext, extensionContext); } } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java b/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java index 9ae4d129d8f3..ff7e9b5a6bac 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java @@ -34,7 +34,6 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.QueryLogHook; import org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerFixture; import org.apache.druid.sql.http.SqlParameter; import java.util.ArrayList; @@ -68,8 +67,6 @@ public class QueryTestBuilder */ public interface QueryTestConfig { - QueryLogHook queryLogHook(); - ObjectMapper jsonMapper(); PlannerFixture plannerFixture(PlannerConfig plannerConfig, AuthConfig authConfig); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestRunner.java b/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestRunner.java index ccb2012b94c4..58fa5635e8f5 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestRunner.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestRunner.java @@ -49,12 +49,14 @@ import org.hamcrest.MatcherAssert; import org.junit.Assert; import org.junit.internal.matchers.ThrowableMessageMatcher; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -248,10 +250,7 @@ public void run() vectorizeValues.add("force"); } - final QueryLogHook queryLogHook = builder.config.queryLogHook(); for (final String vectorize : vectorizeValues) { - queryLogHook.clearRecordedQueries(); - final Map theQueryContext = new HashMap<>(builder.queryContext); theQueryContext.put(QueryContexts.VECTORIZE_KEY, vectorize); theQueryContext.put(QueryContexts.VECTORIZE_VIRTUAL_COLUMNS_KEY, vectorize); @@ -278,13 +277,19 @@ public QueryResults runQuery( final PlannerCaptureHook capture = getCaptureHook(); final DirectStatement stmt = sqlStatementFactory.directStatement(query); stmt.setHook(capture); - final Sequence results = stmt.execute().getResults(); + AtomicReference> resultListRef = new AtomicReference<>(); + QueryLogHook queryLogHook = new QueryLogHook(builder().config.jsonMapper()); + queryLogHook.logQueriesFor( + () -> { + resultListRef.set(stmt.execute().getResults().toList()); + } + ); return new QueryResults( query.context(), vectorize, stmt.prepareResult().getReturnedRowType(), - results.toList(), - builder().config.queryLogHook().getRecordedQueries(), + resultListRef.get(), + queryLogHook.getRecordedQueries(), capture ); } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java index cb66572c53ba..df50dd62bec7 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java @@ -20,19 +20,18 @@ package org.apache.druid.sql.calcite; import org.apache.druid.query.topn.TopNQueryConfig; -import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor; import org.apache.druid.sql.calcite.util.CacheTestHelperModule.ResultCacheMode; import org.apache.druid.sql.calcite.util.SqlTestFramework; import org.apache.druid.sql.calcite.util.SqlTestFramework.QueryComponentSupplier; -import org.junit.rules.ExternalResource; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -52,50 +51,41 @@ ResultCacheMode resultCache() default ResultCacheMode.DISABLED; + + /** * @see {@link SqlTestFrameworkConfig} */ - class ClassRule extends ExternalResource + class Rule implements AfterAllCallback, BeforeEachCallback { - Map configMap = new HashMap<>(); - - public MethodRule methodRule(BaseCalciteQueryTest testHost) - { - return new MethodRule(this, testHost); - } + private SqlTestFrameworkConfig config; + private QueryComponentSupplier testHost; + private Method method; @Override - protected void after() + public void afterAll(ExtensionContext context) { for (ConfigurationInstance f : configMap.values()) { f.close(); } configMap.clear(); } - } - /** - * @see {@link SqlTestFrameworkConfig} - */ - class MethodRule extends ExternalResource - { - private SqlTestFrameworkConfig config; - private ClassRule classRule; - private QueryComponentSupplier testHost; - private Description description; - - public MethodRule(ClassRule classRule, QueryComponentSupplier testHost) + @Override + public void beforeEach(ExtensionContext context) { - this.classRule = classRule; - this.testHost = testHost; + testHost = (QueryComponentSupplier) context.getTestInstance().get(); + method = context.getTestMethod().get(); + setConfig(method.getAnnotation(SqlTestFrameworkConfig.class)); + } @SqlTestFrameworkConfig public SqlTestFrameworkConfig defaultConfig() { try { - SqlTestFrameworkConfig annotation = MethodRule.class + SqlTestFrameworkConfig annotation = getClass() .getMethod("defaultConfig") .getAnnotation(SqlTestFrameworkConfig.class); return annotation; @@ -105,15 +95,12 @@ public SqlTestFrameworkConfig defaultConfig() } } - @Override - public Statement apply(Statement base, Description description) + public void setConfig(SqlTestFrameworkConfig annotation) { - this.description = description; - config = description.getAnnotation(SqlTestFrameworkConfig.class); + config = annotation; if (config == null) { config = defaultConfig(); } - return base; } public SqlTestFramework get() @@ -123,12 +110,12 @@ public SqlTestFramework get() public T getAnnotation(Class annotationType) { - return NotYetSupportedProcessor.getAnnotation(description, annotationType); + return method.getAnnotation(annotationType); } private ConfigurationInstance getConfigurationInstance() { - return classRule.configMap.computeIfAbsent(config, this::buildConfiguration); + return configMap.computeIfAbsent(config, this::buildConfiguration); } ConfigurationInstance buildConfiguration(SqlTestFrameworkConfig config) diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/aggregation/SqlAggregationModuleTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/aggregation/SqlAggregationModuleTest.java index d9d62b609849..599dd2fb2616 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/aggregation/SqlAggregationModuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/aggregation/SqlAggregationModuleTest.java @@ -27,20 +27,22 @@ import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.hamcrest.CoreMatchers; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import static org.hamcrest.MatcherAssert.assertThat; + public class SqlAggregationModuleTest extends CalciteTestBase { private SqlAggregationModule target; private Injector injector; - @Before + @BeforeEach public void setUp() { target = new SqlAggregationModule(); @@ -58,7 +60,7 @@ public void testDefaultSqlAggregatorsAreBound() .sorted(Comparator.comparing(o -> o.getClass().getName())) .collect(Collectors.toList()); - Assert.assertThat(aggregators.get(0), CoreMatchers.instanceOf(ApproxCountDistinctSqlAggregator.class)); - Assert.assertThat(aggregators.get(1), CoreMatchers.instanceOf(CountSqlAggregator.class)); + assertThat(aggregators.get(0), CoreMatchers.instanceOf(ApproxCountDistinctSqlAggregator.class)); + assertThat(aggregators.get(1), CoreMatchers.instanceOf(CountSqlAggregator.class)); } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java index 8656f1221f20..2f5f28ad8e7b 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java @@ -69,8 +69,8 @@ import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.joda.time.Period; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.Collections; @@ -132,7 +132,7 @@ public class ExpressionsTest extends CalciteTestBase final DruidOperatorTable operatorTable = new DruidOperatorTable(Collections.emptySet(), Collections.emptySet()); - @Before + @BeforeEach public void setUp() { testHelper = new ExpressionTestHelper(ROW_SIGNATURE, BINDINGS); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java index 816befbdc0d7..f1a5a869a192 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java @@ -29,8 +29,8 @@ import org.apache.druid.segment.column.RowSignature; import org.apache.druid.sql.calcite.expression.builtin.GreatestOperatorConversion; import org.apache.druid.sql.calcite.util.CalciteTestBase; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.Arrays; @@ -61,7 +61,7 @@ public class GreatestExpressionTest extends CalciteTestBase private GreatestOperatorConversion target; private ExpressionTestHelper testHelper; - @Before + @BeforeEach public void setUp() { target = new GreatestOperatorConversion(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java index cf1a221d7ac8..2bb0116825af 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java @@ -27,8 +27,8 @@ import org.apache.druid.sql.calcite.expression.builtin.IPv4AddressMatchOperatorConversion; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; @@ -57,7 +57,7 @@ public class IPv4AddressMatchExpressionTest extends CalciteTestBase private IPv4AddressMatchOperatorConversion target; private ExpressionTestHelper testHelper; - @Before + @BeforeEach public void setUp() { target = new IPv4AddressMatchOperatorConversion(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java index f495b6e95006..65ea14d8f875 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java @@ -27,8 +27,8 @@ import org.apache.druid.sql.calcite.expression.builtin.IPv4AddressParseOperatorConversion; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; @@ -48,7 +48,7 @@ public class IPv4AddressParseExpressionTest extends CalciteTestBase private IPv4AddressParseOperatorConversion target; private ExpressionTestHelper testHelper; - @Before + @BeforeEach public void setUp() { target = new IPv4AddressParseOperatorConversion(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java index fb1b358cb081..f1844fb940e8 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java @@ -27,8 +27,8 @@ import org.apache.druid.sql.calcite.expression.builtin.IPv4AddressStringifyOperatorConversion; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; @@ -49,7 +49,7 @@ public class IPv4AddressStringifyExpressionTest extends CalciteTestBase private IPv4AddressStringifyOperatorConversion target; private ExpressionTestHelper testHelper; - @Before + @BeforeEach public void setUp() { target = new IPv4AddressStringifyOperatorConversion(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java index 496543a5dd78..eaec03c00c87 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java @@ -29,8 +29,8 @@ import org.apache.druid.segment.column.RowSignature; import org.apache.druid.sql.calcite.expression.builtin.LeastOperatorConversion; import org.apache.druid.sql.calcite.util.CalciteTestBase; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.Arrays; @@ -61,7 +61,7 @@ public class LeastExpressionTest extends CalciteTestBase private LeastOperatorConversion target; private ExpressionTestHelper testHelper; - @Before + @BeforeEach public void setUp() { target = new LeastOperatorConversion(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/OperatorConversionsTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/OperatorConversionsTest.java index 5a0c52514f38..20369bfcf051 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/OperatorConversionsTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/OperatorConversionsTest.java @@ -41,9 +41,7 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.mockito.stubbing.Answer; @@ -51,7 +49,6 @@ import java.util.ArrayList; import java.util.List; -@RunWith(Enclosed.class) public class OperatorConversionsTest { public static class DefaultOperandTypeCheckerTest diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java index 021e35795152..21e097cc8c9e 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java @@ -28,12 +28,14 @@ import org.apache.druid.segment.column.RowSignature; import org.apache.druid.sql.calcite.expression.builtin.TimeFormatOperatorConversion; import org.apache.druid.sql.calcite.util.CalciteTestBase; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import javax.annotation.Nullable; import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * Tests for TIME_FORMAT */ @@ -51,7 +53,7 @@ public class TimeFormatOperatorConversionTest extends CalciteTestBase private TimeFormatOperatorConversion target; private ExpressionTestHelper testHelper; - @Before + @BeforeEach public void setUp() { target = new TimeFormatOperatorConversion(); @@ -91,15 +93,17 @@ public void testConversionToTimezone() ); } - @Test(expected = IAE.class) + @Test public void testConversionToUnknownTimezoneShouldThrowException() { - testExpression( - "2000-02-02 20:05:06", - "timestamp_format(\"t\",'yyyy-MM-dd HH:mm:ss','America/NO_TZ')", - "yyyy-MM-dd HH:mm:ss", - "America/NO_TZ" - ); + assertThrows(IAE.class, () -> { + testExpression( + "2000-02-02 20:05:06", + "timestamp_format(\"t\",'yyyy-MM-dd HH:mm:ss','America/NO_TZ')", + "yyyy-MM-dd HH:mm:ss", + "America/NO_TZ" + ); + }); } private void testExpression( diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/filtration/CombineAndSimplifyBoundsTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/filtration/CombineAndSimplifyBoundsTest.java index 3192dc052df7..6088ffb64541 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/filtration/CombineAndSimplifyBoundsTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/filtration/CombineAndSimplifyBoundsTest.java @@ -25,16 +25,13 @@ import org.apache.druid.query.filter.RangeFilter; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.sql.calcite.BaseCalciteQueryTest; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.List; import static org.junit.Assert.assertEquals; -@RunWith(Parameterized.class) public class CombineAndSimplifyBoundsTest extends BaseCalciteQueryTest { @@ -100,7 +97,6 @@ public final DimFilter eq(String name, String literal) public abstract DimFilter range(String lit1, boolean gte, String name, boolean lte, String lit2); } - @Parameters public static List getParameters() { return ImmutableList.of( @@ -109,15 +105,9 @@ public static List getParameters() ); } - final RangeFilterType rangeFilter; - - public CombineAndSimplifyBoundsTest(RangeFilterType rangeFilter) - { - this.rangeFilter = rangeFilter; - } - - @Test - public void testNotAZ() + @MethodSource("getParameters") + @ParameterizedTest + public void testNotAZ(RangeFilterType rangeFilter) { String dim1 = "dim1"; DimFilter inputFilter = or( @@ -129,8 +119,9 @@ public void testNotAZ() check(inputFilter, expected); } - @Test - public void testAZ() + @MethodSource("getParameters") + @ParameterizedTest + public void testAZ(RangeFilterType rangeFilter) { String dim1 = "dim1"; DimFilter inputFilter = and( @@ -142,8 +133,9 @@ public void testAZ() check(inputFilter, expected); } - @Test - public void testNot2() + @MethodSource("getParameters") + @ParameterizedTest + public void testNot2(RangeFilterType rangeFilter) { String dim1 = "dim1"; DimFilter inputFilter = or( diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/filtration/FiltrationTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/filtration/FiltrationTest.java index 901ab17a712f..b88c520e5229 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/filtration/FiltrationTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/filtration/FiltrationTest.java @@ -28,7 +28,7 @@ import org.apache.druid.segment.column.RowSignature; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class FiltrationTest extends CalciteTestBase { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java index 8d0507b4752a..f9c083864f34 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java @@ -30,7 +30,7 @@ import org.apache.druid.sql.http.SqlParameter; import org.apache.druid.sql.http.SqlQuery; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SqlQueryTest extends CalciteTestBase { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/parser/DruidSqlParserUtilsTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/parser/DruidSqlParserUtilsTest.java index 5165f75785d6..bc9d6997536a 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/parser/DruidSqlParserUtilsTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/parser/DruidSqlParserUtilsTest.java @@ -50,13 +50,11 @@ import org.joda.time.Period; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.Arrays; -@RunWith(Enclosed.class) public class DruidSqlParserUtilsTest { /** diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java index 12db32d4f019..06d8cf761ab8 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitePlannerModuleTest.java @@ -50,22 +50,23 @@ import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; import org.easymock.EasyMock; -import org.easymock.EasyMockRunner; +import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import javax.validation.Validation; import javax.validation.Validator; + import java.util.Collections; import java.util.Set; import static org.apache.calcite.plan.RelOptRule.any; import static org.apache.calcite.plan.RelOptRule.operand; -@RunWith(EasyMockRunner.class) +@ExtendWith(EasyMockExtension.class) public class CalcitePlannerModuleTest extends CalciteTestBase { private static final String SCHEMA_1 = "SCHEMA_1"; @@ -98,9 +99,10 @@ public class CalcitePlannerModuleTest extends CalciteTestBase private Injector injector; private RelOptRule customRule; - @Before + @BeforeEach public void setUp() { + EasyMock.expect(druidSchema1.getSchema()).andStubReturn(schema1); EasyMock.expect(druidSchema2.getSchema()).andStubReturn(schema2); EasyMock.expect(druidSchema1.getSchemaName()).andStubReturn(SCHEMA_1); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitesTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitesTest.java index 576a57cc9375..cc033162d08d 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitesTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/planner/CalcitesTest.java @@ -24,7 +24,7 @@ import org.apache.druid.segment.column.ColumnType; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class CalcitesTest extends CalciteTestBase { diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/rule/DruidLogicalValuesRuleTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/rule/DruidLogicalValuesRuleTest.java index 2ad83563e9ef..5a6db426d29b 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/rule/DruidLogicalValuesRuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/rule/DruidLogicalValuesRuleTest.java @@ -41,7 +41,6 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -50,7 +49,6 @@ import java.math.BigDecimal; -@RunWith(Enclosed.class) public class DruidLogicalValuesRuleTest { private static final PlannerContext DEFAULT_CONTEXT = Mockito.mock(PlannerContext.class); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/run/SqlResultsTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/run/SqlResultsTest.java index 7d846f434547..7148adf40fe7 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/run/SqlResultsTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/run/SqlResultsTest.java @@ -62,6 +62,8 @@ public void testCoerceStringArrays() assertCoerceArrayToList(stringList, stringList); assertCoerceArrayToList(stringList, stringArray); assertCoerceArrayToList(stringList, stringArray2); + assertCoerceArrayToList(null, null); + assertCoerceArrayToList(Collections.singletonList("a"), "a"); } @Test @@ -76,6 +78,8 @@ public void testCoerceLongArrays() assertCoerceArrayToList(listWithNull, arrayWithNull); assertCoerceArrayToList(list, list); assertCoerceArrayToList(list, array); + assertCoerceArrayToList(null, null); + assertCoerceArrayToList(Collections.singletonList(1L), 1L); } @Test @@ -90,6 +94,8 @@ public void testCoerceDoubleArrays() assertCoerceArrayToList(listWithNull, arrayWithNull); assertCoerceArrayToList(list, list); assertCoerceArrayToList(list, array); + assertCoerceArrayToList(null, null); + assertCoerceArrayToList(Collections.singletonList(1.1), 1.1); } @Test @@ -104,6 +110,8 @@ public void testCoerceFloatArrays() assertCoerceArrayToList(listWithNull, arrayWithNull); assertCoerceArrayToList(list, list); assertCoerceArrayToList(list, array); + assertCoerceArrayToList(null, null); + assertCoerceArrayToList(Collections.singletonList(1.1f), 1.1f); } @Test @@ -225,11 +233,6 @@ public void testCoerceOfArrayOfPrimitives() Assert.assertEquals("Cannot coerce field [fieldName] from type [Byte Array] to type [BIGINT]", e.getMessage()); } } - @Test - public void testCoerceArrayFails() - { - assertCannotCoerce("xyz", SqlTypeName.ARRAY); - } @Test public void testCoerceUnsupportedType() @@ -238,15 +241,9 @@ public void testCoerceUnsupportedType() } @Test - public void testMustCoerce() - { - Assert.assertNull(SqlResults.maybeCoerceArrayToList("hello", true)); - } - - @Test - public void testMayNotCoerce() + public void testMayNotCoerceList() { - Assert.assertEquals("hello", SqlResults.maybeCoerceArrayToList("hello", false)); + Assert.assertEquals("hello", SqlResults.coerceArrayToList("hello", false)); } private void assertCoerce(Object expected, Object toCoerce, SqlTypeName typeName) @@ -269,9 +266,9 @@ private void assertCannotCoerce(Object toCoerce, SqlTypeName typeName) MatcherAssert.assertThat(e, ThrowableMessageMatcher.hasMessage(CoreMatchers.containsString("Cannot coerce"))); } - private static void assertCoerceArrayToList(Object expected, Object toCoerce) + private void assertCoerceArrayToList(Object expected, Object toCoerce) { - Object coerced = SqlResults.maybeCoerceArrayToList(toCoerce, true); + Object coerced = SqlResults.coerce(jsonMapper, DEFAULT_CONTEXT, toCoerce, SqlTypeName.ARRAY, ""); Assert.assertEquals(expected, coerced); } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java index 0b5fb609fa22..0616dd2a3a34 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidCalciteSchemaModuleTest.java @@ -56,17 +56,17 @@ import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.view.ViewManager; import org.easymock.EasyMock; -import org.easymock.EasyMockRunner; +import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import java.util.Set; import java.util.stream.Collectors; -@RunWith(EasyMockRunner.class) +@ExtendWith(EasyMockExtension.class) public class DruidCalciteSchemaModuleTest extends CalciteTestBase { private static final String DRUID_SCHEMA_NAME = "druid"; @@ -103,7 +103,7 @@ public class DruidCalciteSchemaModuleTest extends CalciteTestBase private DruidCalciteSchemaModule target; private Injector injector; - @Before + @BeforeEach public void setUp() { EasyMock.replay(plannerConfig); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidSchemaNoDataInitTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidSchemaNoDataInitTest.java index a1ad4201c6d4..7dec56c81f2a 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidSchemaNoDataInitTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/DruidSchemaNoDataInitTest.java @@ -37,7 +37,7 @@ import org.apache.druid.sql.calcite.util.TestTimelineServerView; import org.easymock.EasyMock; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Collections; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/InformationSchemaTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/InformationSchemaTest.java index f2ac64826ee8..06e02fe1ae65 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/InformationSchemaTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/InformationSchemaTest.java @@ -46,8 +46,8 @@ import org.apache.druid.sql.calcite.util.QueryFrameworkUtils; import org.apache.druid.sql.calcite.util.SqlTestFramework; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.ArrayList; @@ -62,7 +62,7 @@ public class InformationSchemaTest extends BaseCalciteQueryTest private InformationSchema informationSchema; private SqlTestFramework qf; - @Before + @BeforeEach public void setUp() { qf = queryFramework(); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedDruidSchemaTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedDruidSchemaTest.java index f4af22be088f..15e4e429ef48 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedDruidSchemaTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedDruidSchemaTest.java @@ -20,14 +20,14 @@ package org.apache.druid.sql.calcite.schema; import org.apache.druid.sql.calcite.util.CalciteTestBase; -import org.easymock.EasyMockRunner; +import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; -@RunWith(EasyMockRunner.class) +@ExtendWith(EasyMockExtension.class) public class NamedDruidSchemaTest extends CalciteTestBase { private static final String SCHEMA_NAME = "SCHEMA_NAME"; @@ -37,7 +37,7 @@ public class NamedDruidSchemaTest extends CalciteTestBase private NamedDruidSchema target; - @Before + @BeforeEach public void setUp() { target = new NamedDruidSchema(druidSchema, SCHEMA_NAME); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedLookupSchemaTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedLookupSchemaTest.java index 22aa9e9f638e..1e4bd396b921 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedLookupSchemaTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedLookupSchemaTest.java @@ -20,14 +20,14 @@ package org.apache.druid.sql.calcite.schema; import org.apache.druid.sql.calcite.util.CalciteTestBase; -import org.easymock.EasyMockRunner; +import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; -@RunWith(EasyMockRunner.class) +@ExtendWith(EasyMockExtension.class) public class NamedLookupSchemaTest extends CalciteTestBase { private static final String SCHEMA_NAME = "lookup"; @@ -37,7 +37,7 @@ public class NamedLookupSchemaTest extends CalciteTestBase private NamedLookupSchema target; - @Before + @BeforeEach public void setUp() { target = new NamedLookupSchema(lookupSchema); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java index 17e83a789944..31cdf8d710c3 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/NamedSystemSchemaTest.java @@ -25,8 +25,8 @@ import org.easymock.EasyMock; import org.easymock.Mock; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class NamedSystemSchemaTest extends CalciteTestBase { @@ -39,7 +39,7 @@ public class NamedSystemSchemaTest extends CalciteTestBase private NamedSystemSchema target; - @Before + @BeforeEach public void setUp() { plannerConfig = EasyMock.createMock(PlannerConfig.class); diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java index 6cfe3f2c81a2..a43a3f24f4f3 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/RootSchemaProviderTest.java @@ -24,16 +24,18 @@ import org.apache.druid.java.util.common.ISE; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.easymock.EasyMock; -import org.easymock.EasyMockRunner; +import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import java.util.Set; -@RunWith(EasyMockRunner.class) +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(EasyMockExtension.class) public class RootSchemaProviderTest extends CalciteTestBase { private static final String SCHEMA_1 = "SCHEMA_1"; @@ -54,7 +56,7 @@ public class RootSchemaProviderTest extends CalciteTestBase private RootSchemaProvider target; - @Before + @BeforeEach public void setUp() { EasyMock.expect(druidSchema1.getSchema()).andStubReturn(schema1); @@ -81,10 +83,12 @@ public void testGetShouldReturnRootSchemaWithProvidedSchemasRegistered() Assert.assertEquals(schema2, rootSchema.getSubSchema(SCHEMA_2).unwrap(schema2.getClass())); } - @Test(expected = ISE.class) + @Test public void testGetWithDuplicateSchemasShouldThrowISE() { - target = new RootSchemaProvider(ImmutableSet.of(druidSchema1, druidSchema2, duplicateSchema1)); - target.get(); + assertThrows(ISE.class, () -> { + target = new RootSchemaProvider(ImmutableSet.of(druidSchema1, druidSchema2, duplicateSchema1)); + target.get(); + }); } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/schema/SystemSchemaTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/schema/SystemSchemaTest.java index 407723745373..bc44809b69f5 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/schema/SystemSchemaTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/schema/SystemSchemaTest.java @@ -110,14 +110,12 @@ import org.easymock.EasyMock; import org.jboss.netty.handler.codec.http.HttpResponse; import org.joda.time.DateTime; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.io.IOException; import java.net.URI; @@ -170,24 +168,21 @@ public class SystemSchemaTest extends CalciteTestBase private DruidNodeDiscoveryProvider druidNodeDiscoveryProvider; private FilteredServerInventoryView serverInventoryView; - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @BeforeClass + @BeforeAll public static void setUpClass() { resourceCloser = Closer.create(); conglomerate = QueryStackTests.createQueryRunnerFactoryConglomerate(resourceCloser); } - @AfterClass + @AfterAll public static void tearDownClass() throws IOException { resourceCloser.close(); } - @Before - public void setUp() throws Exception + @BeforeEach + public void setUp(@TempDir File tmpDir) throws Exception { serverView = EasyMock.createNiceMock(TimelineServerView.class); client = EasyMock.createMock(DruidLeaderClient.class); @@ -207,7 +202,6 @@ public void setUp() throws Exception request = EasyMock.createMock(Request.class); authMapper = createAuthMapper(); - final File tmpDir = temporaryFolder.newFolder(); final QueryableIndex index1 = IndexBuilder.create() .tmpDir(new File(tmpDir, "1")) .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTestBase.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTestBase.java index a10a56d31e34..9ec71acc2d92 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTestBase.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTestBase.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import org.apache.druid.common.config.NullHandling; +import org.apache.druid.java.util.common.FileUtils; import org.apache.druid.math.expr.ExpressionProcessing; import org.apache.druid.segment.column.ColumnType; import org.apache.druid.server.security.Action; @@ -30,8 +31,15 @@ import org.apache.druid.sql.calcite.expression.DruidExpression; import org.apache.druid.sql.calcite.expression.SimpleExtraction; import org.apache.druid.sql.http.SqlParameter; -import org.junit.BeforeClass; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.io.TempDir; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -39,13 +47,71 @@ public abstract class CalciteTestBase { public static final List DEFAULT_PARAMETERS = ImmutableList.of(); - @BeforeClass + @BeforeAll public static void setupCalciteProperties() { NullHandling.initializeForTests(); ExpressionProcessing.initializeForTests(); } + /** + * Temporary folder(s). + * + * Due to some possible reuse of configuration in the next case; there is only one real temporary path; + * but every case gets a separate folder in it. + * + * note: {@link #rootTempPath} and {@link #casetempPath} are made private to ensure that + */ + @TempDir + private static Path rootTempPath; + private Path casetempPath; + + @BeforeEach + public void setCaseTempDir(TestInfo testInfo) + { + String methodName = testInfo.getTestMethod().get().getName(); + casetempPath = FileUtils.createTempDirInLocation(rootTempPath, methodName).toPath(); + } + + + public File newTempFolder() + { + return newTempFolder(null); + } + + public File newTempFolder(String prefix) + { + return FileUtils.createTempDirInLocation(casetempPath, prefix); + } + + public File newTempFile(String prefix) + { + try { + return Files.createTempFile(casetempPath, prefix, null).toFile(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + // FIXME remove + public TempFolderOverTempDir temXMEXAXISporaryFolder = new TempFolderOverTempDir(); + + public class TempFolderOverTempDir + { + + public File newFolder() + { + return newTempFolder("unknown"); + } + + public File newFolder(String string) + { + return newTempFolder(string); + } + } + + /** * @deprecated prefer to make {@link DruidExpression} directly to ensure expression tests accurately test the full * expression structure, this method is just to have a convenient way to fix a very large number of existing tests diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java index f7364ad6be76..2594d2e98dce 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/CalciteTests.java @@ -114,6 +114,7 @@ public class CalciteTests public static final String DATASOURCE3 = "numfoo"; public static final String DATASOURCE4 = "foo4"; public static final String DATASOURCE5 = "lotsocolumns"; + public static final String ARRAYS_DATASOURCE = "arrays"; public static final String BROADCAST_DATASOURCE = "broadcast"; public static final String FORBIDDEN_DATASOURCE = "forbiddenDatasource"; public static final String FORBIDDEN_DESTINATION = "forbiddenDestination"; diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryLogHook.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryLogHook.java index c95e2e609204..3e00d1701818 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryLogHook.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryLogHook.java @@ -23,47 +23,23 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.apache.calcite.runtime.Hook; -import org.apache.druid.jackson.DefaultObjectMapper; import org.apache.druid.java.util.common.logger.Logger; import org.apache.druid.query.Query; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Supplier; /** - * JUnit Rule that adds a Calcite hook to log and remember Druid queries. + * Class to log Druid queries. */ -public class QueryLogHook implements TestRule +public class QueryLogHook { private static final Logger log = new Logger(QueryLogHook.class); - private final Supplier objectMapperSupplier; + private final ObjectMapper objectMapper; private final List> recordedQueries = Lists.newCopyOnWriteArrayList(); - private final AtomicBoolean skipLog = new AtomicBoolean(false); - - public QueryLogHook(final Supplier objectMapperSupplier) - { - this.objectMapperSupplier = objectMapperSupplier; - } - - public static QueryLogHook create() - { - return new QueryLogHook(() -> DefaultObjectMapper.INSTANCE); - } - public static QueryLogHook create(final ObjectMapper objectMapper) + public QueryLogHook(ObjectMapper objectMapper) { - return new QueryLogHook(() -> objectMapper); - } - - public void clearRecordedQueries() - { - recordedQueries.clear(); + this.objectMapper = objectMapper; } public List> getRecordedQueries() @@ -71,48 +47,24 @@ public List> getRecordedQueries() return ImmutableList.copyOf(recordedQueries); } - public void withSkippedLog(Consumer consumer) + protected void accept(Object query) { try { - skipLog.set(true); - consumer.accept(null); + recordedQueries.add((Query) query); + log.info( + "Issued query: %s", + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(query) + ); } - finally { - skipLog.set(false); + catch (Exception e) { + log.warn(e, "Failed to serialize query: %s", query); } } - @Override - public Statement apply(final Statement base, final Description description) + public void logQueriesFor(Runnable r) { - return new Statement() - { - @Override - public void evaluate() throws Throwable - { - clearRecordedQueries(); - - final Consumer function = query -> { - if (skipLog.get()) { - return; - } - - try { - recordedQueries.add((Query) query); - log.info( - "Issued query: %s", - objectMapperSupplier.get().writerWithDefaultPrettyPrinter().writeValueAsString(query) - ); - } - catch (Exception e) { - log.warn(e, "Failed to serialize query: %s", query); - } - }; - - try (final Hook.Closeable unhook = Hook.QUERY_PLAN.add(function)) { - base.evaluate(); - } - } - }; + try (final Hook.Closeable unhook = Hook.QUERY_PLAN.addThread(this::accept)) { + r.run(); + } } } diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java index 8ec1f30fafcd..1506a5c2001a 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/SqlTestFramework.java @@ -151,7 +151,7 @@ SpecificSegmentsQuerySegmentWalker createQuerySegmentWalker( QueryRunnerFactoryConglomerate conglomerate, JoinableFactoryWrapper joinableFactory, Injector injector - ) throws IOException; + ); SqlEngine createEngine( QueryLifecycleFactory qlf, @@ -527,18 +527,13 @@ public JoinableFactoryWrapper joinableFactoryWrapper(final Injector injector) @LazySingleton public SpecificSegmentsQuerySegmentWalker segmentsQuerySegmentWalker(final Injector injector) { - try { - SpecificSegmentsQuerySegmentWalker walker = componentSupplier.createQuerySegmentWalker( - injector.getInstance(QueryRunnerFactoryConglomerate.class), - injector.getInstance(JoinableFactoryWrapper.class), - injector - ); - resourceCloser.register(walker); - return walker; - } - catch (IOException e) { - throw new RE(e); - } + SpecificSegmentsQuerySegmentWalker walker = componentSupplier.createQuerySegmentWalker( + injector.getInstance(QueryRunnerFactoryConglomerate.class), + injector.getInstance(JoinableFactoryWrapper.class), + injector + ); + resourceCloser.register(walker); + return walker; } @Provides diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/util/TestDataBuilder.java b/sql/src/test/java/org/apache/druid/sql/calcite/util/TestDataBuilder.java index 868317681485..2303d51e02ab 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/util/TestDataBuilder.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/util/TestDataBuilder.java @@ -42,6 +42,7 @@ import org.apache.druid.query.DataSource; import org.apache.druid.query.GlobalTableDataSource; import org.apache.druid.query.InlineDataSource; +import org.apache.druid.query.NestedDataTestUtils; import org.apache.druid.query.QueryRunnerFactoryConglomerate; import org.apache.druid.query.aggregation.CountAggregatorFactory; import org.apache.druid.query.aggregation.DoubleSumAggregatorFactory; @@ -832,6 +833,30 @@ public static SpecificSegmentsQuerySegmentWalker createMockWalker( .rows(USER_VISIT_ROWS) .buildMMappedIndex(); + final QueryableIndex arraysIndex = IndexBuilder + .create() + .tmpDir(new File(tmpDir, "9")) + .segmentWriteOutMediumFactory(OffHeapMemorySegmentWriteOutMediumFactory.instance()) + .schema( + new IncrementalIndexSchema.Builder() + .withTimestampSpec(NestedDataTestUtils.AUTO_SCHEMA.getTimestampSpec()) + .withDimensionsSpec(NestedDataTestUtils.AUTO_SCHEMA.getDimensionsSpec()) + .withMetrics( + new CountAggregatorFactory("cnt") + ) + .withRollup(false) + .build() + ) + .inputSource( + ResourceInputSource.of( + NestedDataTestUtils.class.getClassLoader(), + NestedDataTestUtils.ARRAY_TYPES_DATA_FILE + ) + ) + .inputFormat(TestDataBuilder.DEFAULT_JSON_INPUT_FORMAT) + .inputTmpDir(new File(tmpDir, "9-input")) + .buildMMappedIndex(); + return SpecificSegmentsQuerySegmentWalker.createWalker( injector, conglomerate, @@ -946,6 +971,15 @@ public static SpecificSegmentsQuerySegmentWalker createMockWalker( .size(0) .build(), makeWikipediaIndexWithAggregation(tmpDir) + ).add( + DataSegment.builder() + .dataSource(CalciteTests.ARRAYS_DATASOURCE) + .version("1") + .interval(arraysIndex.getDataInterval()) + .shardSpec(new LinearShardSpec(1)) + .size(0) + .build(), + arraysIndex ); } diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java index 921be219b2a4..fa3c1edcea24 100644 --- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java +++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java @@ -102,18 +102,14 @@ import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog; import org.apache.druid.sql.calcite.util.CalciteTestBase; import org.apache.druid.sql.calcite.util.CalciteTests; -import org.apache.druid.sql.calcite.util.QueryLogHook; import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -122,7 +118,9 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.StreamingOutput; + import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.AbstractList; @@ -144,6 +142,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.hamcrest.MatcherAssert.assertThat; + @SuppressWarnings("ALL") public class SqlResourceTest extends CalciteTestBase { @@ -168,14 +168,10 @@ public class SqlResourceTest extends CalciteTestBase private static Closer staticCloser = Closer.create(); private static QueryRunnerFactoryConglomerate conglomerate; - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); + private static SpecificSegmentsQuerySegmentWalker walker; private static QueryScheduler scheduler; - @Rule - public QueryLogHook queryLogHook = QueryLogHook.create(); - private Closer resourceCloser; private TestRequestLogger testRequestLogger; private SqlResource resource; @@ -196,8 +192,8 @@ public class SqlResourceTest extends CalciteTestBase private static final AtomicReference> SCHEDULER_BAGGAGE = new AtomicReference<>(); - @BeforeClass - public static void setupClass() throws Exception + @BeforeAll + public static void setupClass(@TempDir File tempDir) { conglomerate = QueryStackTests.createQueryRunnerFactoryConglomerate(staticCloser); scheduler = new QueryScheduler( @@ -220,17 +216,17 @@ public Sequence run(Query query, Sequence resultSequence) ); } }; - walker = CalciteTests.createMockWalker(conglomerate, temporaryFolder.newFolder(), scheduler); + walker = CalciteTests.createMockWalker(conglomerate, tempDir, scheduler); staticCloser.register(walker); } - @AfterClass + @AfterAll public static void teardownClass() throws Exception { staticCloser.close(); } - @Before + @BeforeEach public void setUp() throws Exception { SCHEDULER_BAGGAGE.set(() -> null); @@ -341,7 +337,7 @@ MockHttpServletRequest request() return makeExpectedReq(CalciteTests.REGULAR_USER_AUTH_RESULT); } - @After + @AfterEach public void tearDown() throws Exception { SCHEDULER_BAGGAGE.set(() -> null); @@ -1603,7 +1599,7 @@ public void testAssertionErrorThrowsErrorWithFilterResponse() throws Exception Status.BAD_REQUEST.getStatusCode() ); - MatcherAssert.assertThat( + assertThat( exception.getUnderlyingException(), DruidExceptionMatcher .invalidSqlInput() @@ -2302,7 +2298,7 @@ private DruidException validateErrorResponse( if (messageContainsString == null) { Assert.assertNull(exception.getMessage()); } else { - MatcherAssert.assertThat(exception.getMessage(), CoreMatchers.containsString(messageContainsString)); + assertThat(exception.getMessage(), CoreMatchers.containsString(messageContainsString)); } return exception; diff --git a/web-console/package-lock.json b/web-console/package-lock.json index 34ab07b8a2c2..69e3011c1db4 100644 --- a/web-console/package-lock.json +++ b/web-console/package-lock.json @@ -7489,9 +7489,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -24215,9 +24215,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "fontsource-open-sans": { "version": "3.0.9", diff --git a/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap b/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap index 213e043bc932..b0edee5ef4ce 100644 --- a/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap +++ b/web-console/src/dialogs/lookup-edit-dialog/__snapshots__/lookup-edit-dialog.spec.tsx.snap @@ -127,33 +127,28 @@ exports[`LookupEditDialog matches snapshot 1`] = ` }, { "defined": [Function], - "info": -

    - URI for the file of interest, specified as a - - file - - , - - hdfs - - , - - - s3 - - , or - - gs - - path -

    -

    - The URI prefix option is strictly better than URI and should be used instead -

    -
    , + "info":

    + URI for the file of interest, specified as a + + file + + , + + hdfs + + , + + + s3 + + , or + + gs + + path +

    , "issueWithValue": [Function], - "label": "URI (deprecated)", + "label": "URI", "name": "extractionNamespace.uri", "placeholder": "s3://bucket/some/key/prefix/lookups-01.gz", "required": [Function], diff --git a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx index 5a751af20116..fa3fd905be38 100644 --- a/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx +++ b/web-console/src/druid-models/ingestion-spec/ingestion-spec.tsx @@ -297,7 +297,10 @@ export function getSchemaMode(spec: Partial): SchemaMode { return Array.isArray(dimensions) && dimensions.length === 0 ? 'string-only-discovery' : 'fixed'; } -export function getArrayMode(spec: Partial): ArrayMode { +export function getArrayMode( + spec: Partial, + whenUnclear: ArrayMode = 'arrays', +): ArrayMode { const schemaMode = getSchemaMode(spec); switch (schemaMode) { case 'type-aware-discovery': @@ -332,7 +335,7 @@ export function getArrayMode(spec: Partial): ArrayMode { return 'multi-values'; } - return 'arrays'; + return whenUnclear; } } } diff --git a/web-console/src/druid-models/lookup-spec/lookup-spec.tsx b/web-console/src/druid-models/lookup-spec/lookup-spec.tsx index d187c839bc95..b0e0d69fff9b 100644 --- a/web-console/src/druid-models/lookup-spec/lookup-spec.tsx +++ b/web-console/src/druid-models/lookup-spec/lookup-spec.tsx @@ -169,7 +169,7 @@ export const LOOKUP_FIELDS: Field[] = [ { name: 'extractionNamespace.uri', type: 'string', - label: 'URI (deprecated)', + label: 'URI', placeholder: 's3://bucket/some/key/prefix/lookups-01.gz', defined: l => oneOfKnown(deepGet(l, 'extractionNamespace.type'), KNOWN_EXTRACTION_NAMESPACE_TYPES, 'uri') && @@ -178,13 +178,10 @@ export const LOOKUP_FIELDS: Field[] = [ !deepGet(l, 'extractionNamespace.uriPrefix') && !deepGet(l, 'extractionNamespace.uri'), issueWithValue: issueWithUri, info: ( - <> -

    - URI for the file of interest, specified as a file, hdfs,{' '} - s3, or gs path -

    -

    The URI prefix option is strictly better than URI and should be used instead

    - +

    + URI for the file of interest, specified as a file, hdfs,{' '} + s3, or gs path +

    ), }, { diff --git a/web-console/src/druid-models/query-context/query-context.tsx b/web-console/src/druid-models/query-context/query-context.tsx index 838ab1f3a503..17e8204e9496 100644 --- a/web-console/src/druid-models/query-context/query-context.tsx +++ b/web-console/src/druid-models/query-context/query-context.tsx @@ -18,6 +18,8 @@ import { deepDelete, deepSet } from '../../utils'; +export type ArrayIngestMode = 'array' | 'mvd'; + export interface QueryContext { useCache?: boolean; populateCache?: boolean; @@ -32,7 +34,7 @@ export interface QueryContext { durableShuffleStorage?: boolean; maxParseExceptions?: number; groupByEnableMultiValueUnnesting?: boolean; - arrayIngestMode?: 'array' | 'mvd'; + arrayIngestMode?: ArrayIngestMode; [key: string]: any; } @@ -248,3 +250,20 @@ export function changeMaxParseExceptions( return deepDelete(context, 'maxParseExceptions'); } } + +// arrayIngestMode + +export function getArrayIngestMode(context: QueryContext): ArrayIngestMode | undefined { + return context.arrayIngestMode; +} + +export function changeArrayIngestMode( + context: QueryContext, + arrayIngestMode: ArrayIngestMode | undefined, +): QueryContext { + if (arrayIngestMode) { + return deepSet(context, 'arrayIngestMode', arrayIngestMode); + } else { + return deepDelete(context, 'arrayIngestMode'); + } +} diff --git a/web-console/src/druid-models/workbench-query/workbench-query.ts b/web-console/src/druid-models/workbench-query/workbench-query.ts index 27cf6a0109da..d59cdbbe92ef 100644 --- a/web-console/src/druid-models/workbench-query/workbench-query.ts +++ b/web-console/src/druid-models/workbench-query/workbench-query.ts @@ -94,6 +94,8 @@ export class WorkbenchQuery { partitionedByHint: string | undefined, arrayMode: ArrayMode, ): WorkbenchQuery { + const queryContext: QueryContext = {}; + if (arrayMode === 'arrays') queryContext.arrayIngestMode = 'array'; return new WorkbenchQuery({ queryString: ingestQueryPatternToQuery( externalConfigToIngestQueryPattern( @@ -103,9 +105,7 @@ export class WorkbenchQuery { arrayMode, ), ).toString(), - queryContext: { - arrayIngestMode: 'array', - }, + queryContext, }); } diff --git a/web-console/src/helpers/spec-conversion.spec.ts b/web-console/src/helpers/spec-conversion.spec.ts index bea3f7303188..9271196dafaf 100644 --- a/web-console/src/helpers/spec-conversion.spec.ts +++ b/web-console/src/helpers/spec-conversion.spec.ts @@ -123,10 +123,7 @@ describe('spec conversion', () => { expect(converted.queryString).toMatchSnapshot(); expect(converted.queryContext).toEqual({ - arrayIngestMode: 'array', - groupByEnableMultiValueUnnesting: false, maxParseExceptions: 3, - finalizeAggregations: false, maxNumTasks: 5, indexSpec: { dimensionCompression: 'lzf', @@ -232,11 +229,7 @@ describe('spec conversion', () => { expect(converted.queryString).toMatchSnapshot(); - expect(converted.queryContext).toEqual({ - arrayIngestMode: 'array', - groupByEnableMultiValueUnnesting: false, - finalizeAggregations: false, - }); + expect(converted.queryContext).toEqual({}); }); it('converts index_hadoop spec (with rollup)', () => { @@ -357,11 +350,7 @@ describe('spec conversion', () => { expect(converted.queryString).toMatchSnapshot(); - expect(converted.queryContext).toEqual({ - arrayIngestMode: 'array', - groupByEnableMultiValueUnnesting: false, - finalizeAggregations: false, - }); + expect(converted.queryContext).toEqual({}); }); it('converts with issue when there is a __time transform', () => { @@ -663,5 +652,9 @@ describe('spec conversion', () => { }); expect(converted.queryString).toMatchSnapshot(); + + expect(converted.queryContext).toEqual({ + arrayIngestMode: 'array', + }); }); }); diff --git a/web-console/src/helpers/spec-conversion.ts b/web-console/src/helpers/spec-conversion.ts index f25406e50288..7e7673c73842 100644 --- a/web-console/src/helpers/spec-conversion.ts +++ b/web-console/src/helpers/spec-conversion.ts @@ -32,11 +32,18 @@ import type { DimensionSpec, IngestionSpec, MetricSpec, + QueryContext, QueryWithContext, TimestampSpec, Transform, } from '../druid-models'; -import { inflateDimensionSpec, NO_SUCH_COLUMN, TIME_COLUMN, upgradeSpec } from '../druid-models'; +import { + getArrayMode, + inflateDimensionSpec, + NO_SUCH_COLUMN, + TIME_COLUMN, + upgradeSpec, +} from '../druid-models'; import { deepGet, filterMap, nonEmptyArray, oneOf } from '../utils'; export function getSpecDatasourceName(spec: Partial): string { @@ -73,11 +80,11 @@ export function convertSpecToSql(spec: any): QueryWithContext { } spec = upgradeSpec(spec, true); - const context: Record = { - finalizeAggregations: false, - groupByEnableMultiValueUnnesting: false, - arrayIngestMode: 'array', - }; + const context: QueryContext = {}; + + if (getArrayMode(spec, 'multi-values') === 'arrays') { + context.arrayIngestMode = 'array'; + } const indexSpec = deepGet(spec, 'spec.tuningConfig.indexSpec'); if (indexSpec) { diff --git a/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx b/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx index 0fc89573bd2f..27017f725da3 100644 --- a/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx +++ b/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx @@ -48,7 +48,6 @@ import './sql-data-loader-view.scss'; const INITIAL_QUERY_CONTEXT: QueryContext = { finalizeAggregations: false, groupByEnableMultiValueUnnesting: false, - arrayIngestMode: 'array', }; interface LoaderContent extends QueryWithContext { @@ -190,6 +189,8 @@ export const SqlDataLoaderView = React.memo(function SqlDataLoaderView( initInputFormat={inputFormat} doneButton={false} onSet={({ inputSource, inputFormat, signature, timeExpression, arrayMode }) => { + const queryContext: QueryContext = { ...INITIAL_QUERY_CONTEXT }; + if (arrayMode === 'arrays') queryContext.arrayIngestMode = 'array'; setContent({ queryString: ingestQueryPatternToQuery( externalConfigToIngestQueryPattern( @@ -199,7 +200,7 @@ export const SqlDataLoaderView = React.memo(function SqlDataLoaderView( arrayMode, ), ).toString(), - queryContext: INITIAL_QUERY_CONTEXT, + queryContext, }); }} altText="Skip the wizard and continue with custom SQL" diff --git a/web-console/src/views/workbench-view/run-panel/run-panel.tsx b/web-console/src/views/workbench-view/run-panel/run-panel.tsx index ac67c40ff278..5d9c9852791e 100644 --- a/web-console/src/views/workbench-view/run-panel/run-panel.tsx +++ b/web-console/src/views/workbench-view/run-panel/run-panel.tsx @@ -25,6 +25,7 @@ import { MenuDivider, MenuItem, Position, + Tag, useHotkeys, } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; @@ -35,8 +36,15 @@ import React, { useCallback, useMemo, useState } from 'react'; import { MenuCheckbox, MenuTristate } from '../../../components'; import { EditContextDialog, StringInputDialog } from '../../../dialogs'; import { IndexSpecDialog } from '../../../dialogs/index-spec-dialog/index-spec-dialog'; -import type { DruidEngine, IndexSpec, QueryContext, WorkbenchQuery } from '../../../druid-models'; +import type { + ArrayIngestMode, + DruidEngine, + IndexSpec, + QueryContext, + WorkbenchQuery, +} from '../../../druid-models'; import { + changeArrayIngestMode, changeDurableShuffleStorage, changeFailOnEmptyInsert, changeFinalizeAggregations, @@ -47,6 +55,7 @@ import { changeUseApproximateTopN, changeUseCache, changeWaitUntilSegmentsLoad, + getArrayIngestMode, getDurableShuffleStorage, getFailOnEmptyInsert, getFinalizeAggregations, @@ -59,6 +68,7 @@ import { getWaitUntilSegmentsLoad, summarizeIndexSpec, } from '../../../druid-models'; +import { getLink } from '../../../links'; import { deepGet, deepSet, pluralIfNeeded, tickIcon } from '../../../utils'; import { MaxTasksButton } from '../max-tasks-button/max-tasks-button'; import { QueryParametersDialog } from '../query-parameters-dialog/query-parameters-dialog'; @@ -87,6 +97,20 @@ const NAMED_TIMEZONES: string[] = [ 'Australia/Sydney', // +11.0 ]; +const ARRAY_INGEST_MODE_DESCRIPTION: Record = { + array: ( + <> + array: Load SQL VARCHAR ARRAY as Druid{' '} + ARRAY<STRING> + + ), + mvd: ( + <> + mvd: Load SQL VARCHAR ARRAY as Druid multi-value STRING + + ), +}; + export interface RunPanelProps { query: WorkbenchQuery; onQueryChange(query: WorkbenchQuery): void; @@ -112,6 +136,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { const numContextKeys = Object.keys(queryContext).length; const queryParameters = query.queryParameters; + const arrayIngestMode = getArrayIngestMode(queryContext); const maxParseExceptions = getMaxParseExceptions(queryContext); const failOnEmptyInsert = getFailOnEmptyInsert(queryContext); const finalizeAggregations = getFinalizeAggregations(queryContext); @@ -472,6 +497,35 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { changeQueryContext={changeQueryContext} /> )} + {ingestMode && ( + + {([undefined, 'array', 'mvd'] as (ArrayIngestMode | undefined)[]).map((m, i) => ( + changeQueryContext(changeArrayIngestMode(queryContext, m))} + /> + ))} + + + + } + > +