use crate::files::write_tmp_file;
use crate::rows::{rows, NULL};
use crate::SqlClient;
use async_compression::tokio::write::GzipEncoder;
use cubestore::cachestore::QUEUE_ITEM_EXTERNAL_ID_MAX_LEN;
use cubestore::metastore::{Column, ColumnType};
use cubestore::queryplanner::physical_plan_flags::PhysicalPlanFlags;
use cubestore::queryplanner::pretty_printers::{pp_phys_plan, pp_phys_plan_ext, PPOptions};
use cubestore::queryplanner::MIN_TOPK_STREAM_ROWS;
use cubestore::sql::{timestamp_from_string, InlineTable, SqlQueryContext};
use cubestore::store::DataFrame;
use cubestore::table::{Row, TableValue, TimestampValue};
use cubestore::util::decimal::Decimal;
use cubestore::CubeError;
use indoc::indoc;
use itertools::Itertools;
use pretty_assertions::assert_eq;
use std::collections::HashSet;
use std::env;
use std::fs::File;
use std::future::Future;
use std::io::Write;
use std::panic::RefUnwindSafe;
use std::path::Path;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::io::{AsyncWriteExt, BufWriter};
use tokio::join;

pub type TestFn = Box<
    dyn Fn(Box<dyn SqlClient>) -> Pin<Box<dyn Future<Output = Result<(), CubeError>> + Send>>
        + Send
        + Sync
        + RefUnwindSafe,
>;
pub fn sql_tests(prefix: &str) -> Vec<(&'static str, TestFn)> {
    let test_list = vec![
        t("insert", insert),
        t(
            "repartition_multi_node_consistency",
            repartition_multi_node_consistency,
        ),
        t("select_test", select_test),
        t("refresh_selects", refresh_selects),
        t("negative_numbers", negative_numbers),
        t("negative_decimal", negative_decimal),
        t("decimal_math", decimal_math),
        t("custom_types", custom_types),
        t("group_by_boolean", group_by_boolean),
        t("group_by_decimal", group_by_decimal),
        t("group_by_nulls", group_by_nulls),
        t("logical_alias", logical_alias),
        t("float_decimal_scale", float_decimal_scale),
        t("float_merge", float_merge),
        t("join", join),
        t("filtered_join", filtered_join),
        t("cross_join_empty_sort_on", cross_join_empty_sort_on),
        t("three_tables_join", three_tables_join),
        t(
            "three_tables_join_with_filter",
            three_tables_join_with_filter,
        ),
        t("three_tables_join_with_union", three_tables_join_with_union),
        t("in_list", in_list),
        t("in_list_with_union", in_list_with_union),
        t("numeric_cast", numeric_cast),
        t("planning_numeric_cast", planning_numeric_cast),
        t("cast_timestamp_to_utf8", cast_timestamp_to_utf8),
        t("numbers_to_bool", numbers_to_bool),
        t("union", union),
        t("nested_union_empty_tables", nested_union_empty_tables),
        t("timestamp_select", timestamp_select),
        t("timestamp_seconds_frac", timestamp_seconds_frac),
        t("column_escaping", column_escaping),
        t("information_schema", information_schema),
        t("system_query_cache", system_query_cache),
        t("metastore_rocksdb_tables", metastore_rocksdb_tables),
        t("cachestore_rocksdb_tables", cachestore_rocksdb_tables),
        t("case_column_escaping", case_column_escaping),
        t("inner_column_escaping", inner_column_escaping),
        t("convert_tz", convert_tz),
        t("date_trunc", date_trunc),
        t("coalesce", coalesce),
        t("ilike", ilike),
        t("count_distinct_crash", count_distinct_crash),
        t(
            "count_distinct_group_by_crash",
            count_distinct_group_by_crash,
        ),
        t("count_distinct_take_crash", count_distinct_take_crash),
        t("create_schema_if_not_exists", create_schema_if_not_exists),
        t(
            "create_index_before_ingestion",
            create_index_before_ingestion,
        ),
        t("ambiguous_join_sort", ambiguous_join_sort),
        t("join_with_aliases", join_with_aliases),
        t("group_by_without_aggregates", group_by_without_aggregates),
        t("create_table_with_location", create_table_with_location),
        t(
            "create_table_with_location_messed_order",
            create_table_with_location_messed_order,
        ),
        t(
            "create_table_with_location_invalid_digit",
            create_table_with_location_invalid_digit,
        ),
        t("create_table_with_csv", create_table_with_csv),
        t(
            "create_table_with_csv_and_index",
            create_table_with_csv_and_index,
        ),
        t(
            "create_table_with_csv_no_header",
            create_table_with_csv_no_header,
        ),
        t(
            "create_table_with_csv_no_header_and_delimiter",
            create_table_with_csv_no_header_and_delimiter,
        ),
        t(
            "create_table_with_csv_no_header_and_quotes",
            create_table_with_csv_no_header_and_quotes,
        ),
        t("create_table_with_url", create_table_with_url),
        t("create_table_fail_and_retry", create_table_fail_and_retry),
        t("empty_crash", empty_crash),
        t("bytes", bytes),
        t("hyperloglog", hyperloglog),
        t("hyperloglog_empty_inputs", hyperloglog_empty_inputs),
        t("hyperloglog_empty_group_by", hyperloglog_empty_group_by),
        t("hyperloglog_inserts", hyperloglog_inserts),
        t(
            "create_table_with_location_and_hyperloglog",
            create_table_with_location_and_hyperloglog,
        ),
        t(
            "create_table_with_location_and_hyperloglog_postgress",
            create_table_with_location_and_hyperloglog_postgress,
        ),
        t(
            "create_table_with_location_and_hyperloglog_space_separated",
            create_table_with_location_and_hyperloglog_space_separated,
        ),
        t("hyperloglog_inplace_group_by", hyperloglog_inplace_group_by),
        t("hyperloglog_postgres", hyperloglog_postgres),
        t("hyperloglog_snowflake", hyperloglog_snowflake),
        t("hyperloglog_databricks", hyperloglog_databricks),
        t("xirr", xirr),
        t(
            "aggregate_index_hll_databricks",
            aggregate_index_hll_databricks,
        ),
        t("physical_plan_flags", physical_plan_flags),
        t("planning_inplace_aggregate", planning_inplace_aggregate),
        t("planning_hints", planning_hints),
        t("planning_inplace_aggregate2", planning_inplace_aggregate2),
        t("planning_topk_hash_aggregate", planning_topk_hash_aggregate),
        t("topk_hash_aggregate_trim", topk_hash_aggregate_trim),
        t("topk_large_inputs", topk_large_inputs),
        t("partitioned_index", partitioned_index),
        t(
            "partitioned_index_if_not_exists",
            partitioned_index_if_not_exists,
        ),
        t("drop_partitioned_index", drop_partitioned_index),
        t("planning_simple", planning_simple),
        t("planning_joins", planning_joins),
        t("planning_3_table_joins", planning_3_table_joins),
        t(
            "planning_join_with_partitioned_index",
            planning_join_with_partitioned_index,
        ),
        t("topk_query", topk_query),
        t("topk_having", topk_having),
        t("topk_decimals", topk_decimals),
        t("planning_topk_having", planning_topk_having),
        t("planning_topk_hll", planning_topk_hll),
        t("topk_hll", topk_hll),
        t("topk_hll_with_nulls", topk_hll_with_nulls),
        t("offset", offset),
        t("having", having),
        t("rolling_window_join", rolling_window_join),
        t("rolling_window_query", rolling_window_query),
        t("rolling_window_exprs", rolling_window_exprs),
        t(
            "rolling_window_query_timestamps",
            rolling_window_query_timestamps,
        ),
        t(
            "rolling_window_query_timestamps_exceeded",
            rolling_window_query_timestamps_exceeded,
        ),
        t(
            "rolling_window_extra_aggregate",
            rolling_window_extra_aggregate,
        ),
        t(
            "rolling_window_extra_aggregate_addon",
            rolling_window_extra_aggregate_addon,
        ),
        t(
            "rolling_window_extra_aggregate_timestamps",
            rolling_window_extra_aggregate_timestamps,
        ),
        t(
            "rolling_window_one_week_interval",
            rolling_window_one_week_interval,
        ),
        t(
            "rolling_window_one_quarter_interval",
            rolling_window_one_quarter_interval,
        ),
        t("rolling_window_offsets", rolling_window_offsets),
        t("rolling_window_filtered", rolling_window_filtered),
        t("rolling_window_no_aggregates", rolling_window_no_aggregates),
        t(
            "rolling_window_unused_partition_by",
            rolling_window_unused_partition_by,
        ),
        t(
            "rolling_window_two_aggregates",
            rolling_window_two_aggregates,
        ),
        t("decimal_index", decimal_index),
        t("decimal_order", decimal_order),
        t("float_index", float_index),
        t("float_order", float_order),
        t("date_add", date_add),
        t("date_bin", date_bin),
        t("now", now),
        t("dump", dump),
        t("unsorted_merge_assertion", unsorted_merge_assertion),
        t("unsorted_data_timestamps", unsorted_data_timestamps),
        // t("ksql_simple", ksql_simple),
        t(
            "dimension_only_queries_for_stream_table",
            dimension_only_queries_for_stream_table,
        ),
        t(
            "unique_key_and_multi_measures_for_stream_table",
            unique_key_and_multi_measures_for_stream_table,
        ),
        t(
            "unique_key_and_multi_partitions",
            unique_key_and_multi_partitions,
        ),
        t(
            "unique_key_and_multi_partitions_hash_aggregate",
            unique_key_and_multi_partitions_hash_aggregate,
        ),
        t("filter_pushdown_unique_key", filter_pushdown_unique_key),
        t(
            "group_by_prefix_sorted_aggregate_multi_partition",
            group_by_prefix_sorted_aggregate_multi_partition,
        ),
        t(
            "group_by_prefix_limit_high_cardinality",
            group_by_prefix_limit_high_cardinality,
        ),
        t(
            "planning_aggregate_below_merge_with_limit",
            planning_aggregate_below_merge_with_limit,
        ),
        t(
            "global_aggregate_no_chunk_merge",
            global_aggregate_no_chunk_merge,
        ),
        t(
            "global_aggregate_unique_key_keeps_merge",
            global_aggregate_unique_key_keeps_merge,
        ),
        t("prefilter_chunks_shared_scan", prefilter_chunks_shared_scan),
        t("divide_by_zero", divide_by_zero),
        t(
            "filter_multiple_in_for_decimal",
            filter_multiple_in_for_decimal,
        ),
        t(
            "planning_filter_multiple_in_for_decimal",
            planning_filter_multiple_in_for_decimal,
        ),
        t("panic_worker", panic_worker),
        t(
            "planning_filter_index_selection",
            planning_filter_index_selection,
        ),
        t("planning_aggregate_index", planning_aggregate_index),
        t("aggregate_index", aggregate_index),
        t("aggregate_index_hll", aggregate_index_hll),
        t(
            "aggregate_index_with_hll_bytes",
            aggregate_index_with_hll_bytes,
        ),
        t("aggregate_index_errors", aggregate_index_errors),
        t("aggregate_index_decimal", aggregate_index_decimal),
        t(
            "aggregate_index_decimal_overflow",
            aggregate_index_decimal_overflow,
        ),
        t("inline_tables", inline_tables),
        t("inline_tables_2x", inline_tables_2x),
        t("build_range_end", build_range_end),
        t("cache_incr", cache_incr),
        t("cache_set_get_rm", cache_set_get_rm),
        t("cache_set_get_set_get", cache_set_get_set_get),
        t("cache_compaction", cache_compaction),
        t("cache_set_nx", cache_set_nx),
        t("cache_prefix_keys", cache_prefix_keys),
        t("queue_list_v1", queue_list_v1),
        t("queue_full_workflow_v1", queue_full_workflow_v1),
        t("queue_full_workflow_v2", queue_full_workflow_v2),
        t(
            "queue_full_workflow_v2_with_external_id",
            queue_full_workflow_v2_with_external_id,
        ),
        t(
            "queue_add_external_id_max_len",
            queue_add_external_id_max_len,
        ),
        t("queue_latest_result_v1", queue_latest_result_v1),
        t("queue_retrieve_extended", queue_retrieve_extended),
        t("queue_add_and_retrieve", queue_add_and_retrieve),
        t(
            "queue_add_and_retrieve_backlog",
            queue_add_and_retrieve_backlog,
        ),
        t("queue_ack_then_result_v1", queue_ack_then_result_v1),
        t("queue_ack_then_result_v2", queue_ack_then_result_v2),
        t(
            "queue_ack_then_result_v2_by_id",
            queue_ack_then_result_v2_by_id,
        ),
        t("queue_orphaned_timeout", queue_orphaned_timeout),
        t("queue_heartbeat_by_id", queue_heartbeat_by_id),
        t("queue_heartbeat_by_path", queue_heartbeat_by_path),
        t("queue_merge_extra_by_path", queue_merge_extra_by_path),
        t("queue_merge_extra_by_id", queue_merge_extra_by_id),
        t(
            "queue_multiple_result_blocking",
            queue_multiple_result_blocking,
        ),
        t("queue_custom_orphaned", queue_custom_orphaned),
        t(
            "queue_result_by_id_external_id_mismatch",
            queue_result_by_id_external_id_mismatch,
        ),
        t(
            "queue_result_ack_multiple_with_external_id",
            queue_result_ack_multiple_with_external_id,
        ),
        t("limit_pushdown_group", limit_pushdown_group),
        t("limit_pushdown_group_having", limit_pushdown_group_having),
        t(
            "limit_pushdown_group_nonprefix_order",
            limit_pushdown_group_nonprefix_order,
        ),
        t(
            "limit_pushdown_group_null_appended",
            limit_pushdown_group_null_appended,
        ),
        t(
            "limit_pushdown_group_null_order_first",
            limit_pushdown_group_null_order_first,
        ),
        t("limit_pushdown_group_order", limit_pushdown_group_order),
        t(
            "limit_pushdown_group_where_order",
            limit_pushdown_group_where_order,
        ),
        t("limit_pushdown_without_group", limit_pushdown_without_group),
        t(
            "limit_pushdown_without_group_resort",
            limit_pushdown_without_group_resort,
        ),
        t("limit_pushdown_unique_key", limit_pushdown_unique_key),
        t("sys_drop_cache", sys_drop_cache),
        t("sys_cachestore_info", sys_cachestore_info),
        t("sys_metastore_healthcheck", sys_metastore_healthcheck),
        t("sys_cachestore_healthcheck", sys_cachestore_healthcheck),
        t("join_multi_partition_small", join_multi_partition_small),
        t("join_multi_partition_large", join_multi_partition_large),
    ];

    let test_list = if prefix == "migration" {
        test_list
            .into_iter()
            .filter(|(name, _)| !excluded_from_migration_test(name))
            .collect()
    } else {
        test_list
    };

    return test_list;

    fn t<F>(name: &'static str, f: fn(Box<dyn SqlClient>) -> F) -> (&'static str, TestFn)
    where
        F: Future<Output = Result<(), CubeError>> + Send + 'static,
    {
        (name, Box::new(move |c| Box::pin(f(c))))
    }
}

lazy_static::lazy_static! {
    // Generally, these are tests that would fail and which are useless as a migration test.  Some
    // other migration tests are useless, but they pass.
    //
    // Also, some tests are new.  This should probably be a whitelist.
    static ref MIGRATION_TEST_EXCLUSION_SET: HashSet<String> = [
        // Tests that would fail and are useless as a migration test.
        "aggregate_index_errors",
        // Old versions panic building an aggregating index over a decimal measure.
        "aggregate_index_decimal",
        "aggregate_index_decimal_overflow",
        "create_table_with_location_invalid_digit",
        "create_table_with_url",
        "hyperloglog_inserts",
        "partitioned_index_if_not_exists",
        "drop_partitioned_index",
        "dump",
        "panic_worker",

        // These are confirmed to fail if you backport migration tests to old cube (thus making
        // it a non-migration test)
        "dimension_only_queries_for_stream_table",
        "limit_pushdown_unique_key",
        "queue_ack_then_result_v2",
        "queue_ack_then_result_v2_by_id",
        "queue_custom_orphaned",
        "queue_result_by_id_external_id_mismatch",
        "queue_result_ack_multiple_with_external_id",
        "queue_full_workflow_v1",
        "queue_full_workflow_v2",
        "queue_full_workflow_v2_with_external_id",
        "queue_heartbeat_by_id",
        "queue_heartbeat_by_path",
        "queue_latest_result_v1",
        "queue_list_v1",
        "queue_merge_extra_by_id",
        "queue_orphaned_timeout",
        "queue_retrieve_extended",
        "unique_key_and_multi_measures_for_stream_table",
        "unique_key_and_multi_partitions",
        "unique_key_and_multi_partitions_hash_aggregate",

        // New tests
        "join_multi_partition_small",
        "join_multi_partition_large",
        "decimal_math",
        "planning_filter_multiple_in_for_decimal",
        "planning_numeric_cast",
        "create_table_with_csv_no_header",
        "create_table_with_csv_no_header_and_delimiter",
        "create_table_with_csv_no_header_and_quotes",
        "filter_pushdown_unique_key",
        "group_by_prefix_sorted_aggregate_multi_partition",
        "group_by_prefix_limit_high_cardinality",
        "planning_aggregate_below_merge_with_limit",
        "global_aggregate_no_chunk_merge",
        "global_aggregate_unique_key_keeps_merge",
        "limit_pushdown_group_having",
        "limit_pushdown_group_nonprefix_order",
        "limit_pushdown_group_null_appended",
        "limit_pushdown_group_null_order_first",
        "prefilter_chunks_shared_scan",
        "planning_topk_hash_aggregate",
        "topk_hash_aggregate_trim",
        "queue_add_and_retrieve",
        "queue_add_and_retrieve_backlog",
        "queue_add_external_id_max_len",
    ].into_iter().map(ToOwned::to_owned).collect();
}

fn excluded_from_migration_test(name: &str) -> bool {
    MIGRATION_TEST_EXCLUSION_SET.contains(name)
}

async fn insert(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA Foo").await?;
    let _ = service
        .exec_query(
            "CREATE TABLE Foo.Persons (
                            PersonID int,
                            LastName varchar(255),
                            FirstName varchar(255),
                            Address varchar(255),
                            City varchar(255)
                          )",
        )
        .await?;

    service.exec_query(
            "INSERT INTO Foo.Persons
        (
            PersonID,
            LastName,
            FirstName,
            Address,
            City
        )

        VALUES
        (23, 'LastName 1', 'FirstName 1', 'Address 1', 'City 1'), (38, 'LastName 21', 'FirstName 2', 'Address 2', 'City 2'),
        (24, 'LastName 3', 'FirstName 1', 'Address 1', 'City 1'), (37, 'LastName 22', 'FirstName 2', 'Address 2', 'City 2'),
        (25, 'LastName 4', 'FirstName 1', 'Address 1', 'City 1'), (36, 'LastName 23', 'FirstName 2', 'Address 2', 'City 2'),
        (26, 'LastName 5', 'FirstName 1', 'Address 1', 'City 1'), (35, 'LastName 24', 'FirstName 2', 'Address 2', 'City 2'),
        (27, 'LastName 6', 'FirstName 1', 'Address 1', 'City 1'), (34, 'LastName 25', 'FirstName 2', 'Address 2', 'City 2'),
        (28, 'LastName 7', 'FirstName 1', 'Address 1', 'City 1'), (33, 'LastName 26', 'FirstName 2', 'Address 2', 'City 2'),
        (29, 'LastName 8', 'FirstName 1', 'Address 1', 'City 1'), (32, 'LastName 27', 'FirstName 2', 'Address 2', 'City 2'),
        (30, 'LastName 9', 'FirstName 1', 'Address 1', 'City 1'), (31, 'LastName 28', 'FirstName 2', 'Address 2', 'City 2')"
        ).await?;

    service.exec_query("INSERT INTO Foo.Persons
        (LastName, PersonID, FirstName, Address, City)
        VALUES
        ('LastName 1', 23, 'FirstName 1', 'Address 1', 'City 1'), ('LastName 2', 22, 'FirstName 2', 'Address 2', 'City 2');").await?;
    Ok(())
}

async fn refresh_selects(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let t = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap()
        .as_secs_f64();

    let result = service
        .exec_query("SELECT FLOOR((UNIX_TIMESTAMP()) / 10)")
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![Row::new(vec![TableValue::Float((t / 10.).floor().into())])]
    );

    let result = service
        .exec_query("SELECT ((3600 * 24 - 28800) / 86400)")
        .await?;
    assert_eq!(result.get_rows(), &vec![Row::new(vec![TableValue::Int(0)])]);

    let result = service
        .exec_query("SELECT ((3600 * (24 + 8) - 28800) / 86400)")
        .await?;
    assert_eq!(result.get_rows(), &vec![Row::new(vec![TableValue::Int(1)])]);
    let result = service
        .exec_query("SELECT ((3600 * (48 + 8) - 28800) / 86400)")
        .await?;
    assert_eq!(result.get_rows(), &vec![Row::new(vec![TableValue::Int(2)])]);
    Ok(())
}

async fn select_test(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA Foo").await?;

    let _ = service
        .exec_query(
            "CREATE TABLE Foo.Persons (
                            PersonID int,
                            LastName varchar(255),
                            FirstName varchar(255),
                            Address varchar(255),
                            City varchar(255)
                          );",
        )
        .await?;

    service
        .exec_query(
            "INSERT INTO Foo.Persons
            (LastName, PersonID, FirstName, Address, City)
            VALUES
            ('LastName 1', 23, 'FirstName 1', 'Address 1', 'City 1'),
            ('LastName 2', 22, 'FirstName 2', 'Address 2', 'City 2');",
        )
        .await?;

    let result = service
        .exec_query("SELECT PersonID person_id from Foo.Persons")
        .await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(22)]));
    assert_eq!(result.get_rows()[1], Row::new(vec![TableValue::Int(23)]));
    Ok(())
}

async fn negative_numbers(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.values (int_value int)")
        .await?;

    service
        .exec_query("INSERT INTO foo.values (int_value) VALUES (-153)")
        .await?;

    let result = service.exec_query("SELECT * from foo.values").await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(-153)]));
    Ok(())
}

async fn negative_decimal(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.values (decimal_value decimal)")
        .await?;

    service
        .exec_query("INSERT INTO foo.values (decimal_value) VALUES (-0.12345)")
        .await?;

    let result = service.exec_query("SELECT * from foo.values").await?;

    assert_eq!(
        match &result.get_rows()[0].values()[0] {
            TableValue::Decimal(d) => d.to_string(5),
            x => panic!("Expected decimal but found: {:?}", x),
        },
        "-0.12345"
    );
    Ok(())
}

async fn decimal_math(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;
    service
        .exec_query("CREATE TABLE foo.test_decimal (value Decimal(5, 10))")
        .await?;
    service.exec_query("INSERT INTO foo.test_decimal (value) VALUES (10), (20), (30), (40), (100), (200), (300)").await?;
    let r: Arc<DataFrame> = service
        .exec_query("SELECT value, value / 3 FROM foo.test_decimal")
        .await?;
    let columns: &Vec<Column> = r.get_columns();
    assert_eq!(columns.len(), 2);
    assert_eq!(
        columns[0].get_column_type(),
        &ColumnType::Decimal {
            scale: 10,
            precision: 10
        }
    );
    assert_eq!(
        columns[1].get_column_type(),
        &ColumnType::Decimal {
            scale: 14,
            precision: 14
        }
    );
    const S10: i128 = 1_00000_00000i128;
    const S14: i128 = 1_0000_00000_00000i128;
    fn mk_row(n: i128) -> Vec<TableValue> {
        vec![
            TableValue::Decimal(Decimal::new(n * S10)),
            TableValue::Decimal(Decimal::new(n * S14 / 3)),
        ]
    }
    assert_eq!(
        to_rows(&r),
        [10, 20, 30, 40, 100, 200, 300]
            .into_iter()
            .map(mk_row)
            .collect::<Vec<_>>()
    );
    Ok(())
}

async fn custom_types(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.values (int_value mediumint, b1 bytes, b2 varbinary)")
        .await?;

    service
        .exec_query("INSERT INTO foo.values (int_value, b1, b2) VALUES (-153, X'0a', X'0b')")
        .await?;
    Ok(())
}

async fn group_by_boolean(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.bool_group (bool_value boolean)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.bool_group (bool_value) VALUES (true), (false), (true), (false), (false)"
        ).await?;

    // TODO compaction fails the test in between?
    // service.exec_query(
    //     "INSERT INTO foo.bool_group (bool_value) VALUES (true), (false), (true), (false), (false)"
    // ).await?;

    let result = service
        .exec_query("SELECT count(*) from foo.bool_group")
        .await?;
    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(5)]));

    let result = service
        .exec_query("SELECT count(*) from foo.bool_group where bool_value = true")
        .await?;
    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(2)]));

    let result = service
        .exec_query("SELECT count(*) from foo.bool_group where bool_value = 'true'")
        .await?;
    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(2)]));

    let result = service
        .exec_query(
            "SELECT g.bool_value, count(*) from foo.bool_group g GROUP BY 1 ORDER BY 2 DESC",
        )
        .await?;

    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![TableValue::Boolean(false), TableValue::Int(3)])
    );
    assert_eq!(
        result.get_rows()[1],
        Row::new(vec![TableValue::Boolean(true), TableValue::Int(2)])
    );
    Ok(())
}

async fn group_by_decimal(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.decimal_group (id INT, decimal_value DECIMAL)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.decimal_group (id, decimal_value) VALUES (1, 100), (2, 200), (3, 100), (4, 100), (5, 200)"
        ).await?;

    let result = service
        .exec_query("SELECT count(*) from foo.decimal_group")
        .await?;
    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(5)]));

    let result = service
        .exec_query("SELECT count(*) from foo.decimal_group where decimal_value = 200")
        .await?;
    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(2)]));

    let result = service
        .exec_query(
            "SELECT g.decimal_value, count(*) from foo.decimal_group g GROUP BY 1 ORDER BY 2 DESC",
        )
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::Decimal(Decimal::new(100 * 100_000)),
                TableValue::Int(3)
            ]),
            Row::new(vec![
                TableValue::Decimal(Decimal::new(200 * 100_000)),
                TableValue::Int(2)
            ])
        ]
    );
    Ok(())
}

async fn group_by_nulls(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA s").await?;

    let _ = service
        .exec_query("CREATE TABLE s.data (id int, n int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO s.data (id, n) VALUES (NULL, 1), (NULL, 2), (NULL, 3), (1, 1), (2, 2)",
        )
        .await?;

    let result = service
        .exec_query("SELECT id, sum(n) from s.data group by 1 order by 1")
        .await?;
    assert_eq!(
        to_rows(&result),
        rows(&[(Some(1), 1), (Some(2), 2), (None, 6)])
    );
    Ok(())
}

async fn logical_alias(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA s").await?;

    let _ = service
        .exec_query("CREATE TABLE s.logical (id int, n int)")
        .await?;

    service
        .exec_query("INSERT INTO s.logical (id, n) VALUES (1, 1), (2, 2), (3, 3), (1, 1), (2, 2)")
        .await?;

    let result = service
        .exec_query("SELECT  sum(n) from (select id, sum(n) n from s.logical group by 1) `test` ")
        .await?;
    assert_eq!(to_rows(&result), rows(&[(9)]));
    Ok(())
}

async fn float_decimal_scale(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;
    service
        .exec_query("CREATE TABLE foo.decimal_group (id INT, decimal_value FLOAT)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.decimal_group (id, decimal_value) VALUES (1, 677863988852), (2, 677863988852.123e-10), (3, 6778639882.123e+3)"
        ).await?;

    let result = service
        .exec_query("SELECT SUM(decimal_value) FROM foo.decimal_group")
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![Row::new(vec![TableValue::Float(7456503871042.786.into())])]
    );
    Ok(())
}

async fn float_merge(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service.exec_query("CREATE TABLE s.f1 (n float)").await?;
    service
        .exec_query("INSERT INTO s.f1 (n) VALUES (1.0), (2.0)")
        .await?;
    service.exec_query("CREATE TABLE s.f2 (n float)").await?;
    service
        .exec_query("INSERT INTO s.f2 (n) VALUES (1.0), (3.0)")
        .await?;
    let r = service
        .exec_query(
            "SELECT n \
             FROM (SELECT * from s.f1 UNION ALL SELECT * FROM s.f2) \
             GROUP BY 1 \
             ORDER BY 1",
        )
        .await?;

    assert_eq!(
        to_rows(&r),
        vec![
            vec![TableValue::Float(1.0.into())],
            vec![TableValue::Float(2.0.into())],
            vec![TableValue::Float(3.0.into())],
        ]
    );
    Ok(())
}

async fn join(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.orders (customer_id text, amount int)")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE foo.customers (id text, city text, state text)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.orders (customer_id, amount) VALUES ('a', 10), ('b', 2), ('b', 3)",
        )
        .await?;

    service.exec_query(
            "INSERT INTO foo.customers (id, city, state) VALUES ('a', 'San Francisco', 'CA'), ('b', 'New York', 'NY')"
        ).await?;

    let result = service.exec_query("SELECT c.city, sum(o.amount) from foo.orders o JOIN foo.customers c ON o.customer_id = c.id GROUP BY 1 ORDER BY 2 DESC").await?;

    assert_eq!(
        to_rows(&result),
        vec![
            vec![
                TableValue::String("San Francisco".to_string()),
                TableValue::Int(10)
            ],
            vec![
                TableValue::String("New York".to_string()),
                TableValue::Int(5)
            ]
        ]
    );

    // Same query, reverse comparison order.
    let result2 = service.exec_query("SELECT c.city, sum(o.amount) from foo.orders o JOIN foo.customers c ON c.id = o.customer_id GROUP BY 1 ORDER BY 2 DESC").await?;
    assert_eq!(result.get_rows(), result2.get_rows());

    // Join on non-existing field.
    assert!(service.exec_query("SELECT c.id, sum(o.amount) FROM foo.customers c JOIN foo.orders o ON c.id = o.not_found")
        .await.is_err());
    assert!(service.exec_query("SELECT c.id, sum(o.amount) FROM foo.customers c JOIN foo.orders o ON o.not_found = c.id")
        .await.is_err());

    // Join on ambiguous fields.
    let result = service
        .exec_query(
            "SELECT c.id, k.id FROM foo.customers c JOIN foo.customers k ON c.id = k.id ORDER BY 1",
        )
        .await?;
    assert_eq!(to_rows(&result), rows(&[("a", "a"), ("b", "b")]));
    Ok(())
}

/// Reproduces CORE-593: a rolling-window pre-aggregation generates a cross/range join (empty
/// equi-join `on`) over a rollup table plus GROUP BY + ORDER BY. The empty `on` propagates as an
/// empty `sort_on` to the index scan, which builds a SortPreservingMergeExec with no sort
/// expressions. With DataFusion 46 such a merge errors at execution ("Sort expressions cannot be
/// empty for streaming merge") -- but only when the worker has more than one partition to merge,
/// so the table must hold several chunks.
async fn cross_join_empty_sort_on(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;
    let _ = service
        .exec_query("CREATE TABLE foo.t (source text, n int)")
        .await?;

    // Separate inserts produce separate chunks, so the index scan merges more than one partition.
    service
        .exec_query("INSERT INTO foo.t (source, n) VALUES ('a', 1), ('b', 2)")
        .await?;
    service
        .exec_query("INSERT INTO foo.t (source, n) VALUES ('a', 3)")
        .await?;
    service
        .exec_query("INSERT INTO foo.t (source, n) VALUES ('c', 4)")
        .await?;

    let result = service
        .exec_query(
            "SELECT q.source, sum(q.n) FROM foo.t q \
             CROSS JOIN (SELECT 1 AS x UNION ALL SELECT 2 AS x) series \
             GROUP BY q.source ORDER BY q.source",
        )
        .await?;

    assert_eq!(
        to_rows(&result),
        vec![
            vec![TableValue::String("a".to_string()), TableValue::Int(8)],
            vec![TableValue::String("b".to_string()), TableValue::Int(4)],
            vec![TableValue::String("c".to_string()), TableValue::Int(8)],
        ]
    );
    Ok(())
}

async fn filtered_join(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.employee (name varchar) INDEX employee_name (name)")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE foo.employee_department_bridge (employee_name text, department_name text) INDEX employee_department_bridge_name (employee_name)")
        .await
        ?;

    service
        .exec_query("INSERT INTO foo.employee (name) VALUES ('John'), ('Jim')")
        .await?;

    service
        .exec_query("INSERT INTO foo.employee_department_bridge (employee_name, department_name) VALUES ('John','Marketing'), ('Jim','Marketing')")
        .await
        ?;

    let result = service.exec_query("select * from foo.employee AS e LEFT JOIN foo.employee_department_bridge b on b.employee_name = e.name where b.department_name = 'Non existing'").await?;

    assert_eq!(result.len(), 0);

    println!("{:?}", service
        .exec_query(
            "EXPLAIN ANALYZE select e.name from foo.employee AS e LEFT JOIN foo.employee_department_bridge b on b.employee_name=e.name where b.department_name = 'Marketing' GROUP BY 1 LIMIT 10000",
        )
        .await
        ?);

    let result = service
        .exec_query(
            "select e.name from foo.employee AS e LEFT JOIN foo.employee_department_bridge b on b.employee_name=e.name where b.department_name = 'Marketing' GROUP BY 1 ORDER BY 1 LIMIT 10000",
        )
        .await
        ?;
    assert_eq!(to_rows(&result), rows(&[("Jim"), ("John")]));
    Ok(())
}

async fn three_tables_join(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query(
            "CREATE TABLE foo.orders (orders_customer_id text, orders_product_id int, amount int)",
        )
        .await?;
    service
        .exec_query("CREATE INDEX orders_by_product ON foo.orders (orders_product_id)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.customers (customer_id text, city text, state text)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.products (product_id int, name text)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.orders (orders_customer_id, orders_product_id, amount) VALUES ('a', 1, 10), ('b', 2, 2), ('b', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.orders (orders_customer_id, orders_product_id, amount) VALUES ('b', 1, 10), ('c', 2, 2), ('c', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.orders (orders_customer_id, orders_product_id, amount) VALUES ('c', 1, 10), ('d', 2, 2), ('d', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.customers (customer_id, city, state) VALUES ('a', 'San Francisco', 'CA'), ('b', 'New York', 'NY')"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.customers (customer_id, city, state) VALUES ('c', 'San Francisco', 'CA'), ('d', 'New York', 'NY')"
        ).await?;

    service
        .exec_query(
            "INSERT INTO foo.products (product_id, name) VALUES (1, 'Potato'), (2, 'Tomato')",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT city, name, sum(amount) FROM foo.orders o \
            LEFT JOIN foo.customers c ON orders_customer_id = customer_id \
            LEFT JOIN foo.products p ON orders_product_id = product_id \
            GROUP BY 1, 2 ORDER BY 3 DESC, 1 ASC, 2 ASC",
        )
        .await?;

    let expected = vec![
        Row::new(vec![
            TableValue::String("San Francisco".to_string()),
            TableValue::String("Potato".to_string()),
            TableValue::Int(20),
        ]),
        Row::new(vec![
            TableValue::String("New York".to_string()),
            TableValue::String("Potato".to_string()),
            TableValue::Int(10),
        ]),
        Row::new(vec![
            TableValue::String("New York".to_string()),
            TableValue::String("Tomato".to_string()),
            TableValue::Int(10),
        ]),
        Row::new(vec![
            TableValue::String("San Francisco".to_string()),
            TableValue::String("Tomato".to_string()),
            TableValue::Int(5),
        ]),
    ];

    assert_eq!(result.get_rows(), &expected);

    let result = service
        .exec_query(
            "SELECT city, name, sum(amount) FROM foo.orders o \
            LEFT JOIN foo.customers c ON orders_customer_id = customer_id \
            LEFT JOIN foo.products p ON orders_product_id = product_id \
            WHERE customer_id = 'b' AND product_id IN ('2')
            GROUP BY 1, 2 ORDER BY 3 DESC, 1 ASC, 2 ASC",
        )
        .await?;

    let expected = vec![Row::new(vec![
        TableValue::String("New York".to_string()),
        TableValue::String("Tomato".to_string()),
        TableValue::Int(5),
    ])];

    assert_eq!(result.get_rows(), &expected);
    Ok(())
}

async fn three_tables_join_with_filter(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query(
            "CREATE TABLE foo.orders (orders_customer_id text, orders_product_id int, amount int)",
        )
        .await?;
    service
        .exec_query("CREATE INDEX orders_by_product ON foo.orders (orders_product_id)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.customers (customer_id text, city text, state text)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.products (product_id int, name text)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.orders (orders_customer_id, orders_product_id, amount) VALUES ('a', 1, 10), ('b', 2, 2), ('b', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.orders (orders_customer_id, orders_product_id, amount) VALUES ('b', 1, 10), ('c', 2, 2), ('c', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.orders (orders_customer_id, orders_product_id, amount) VALUES ('c', 1, 10), ('d', 2, 2), ('d', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.customers (customer_id, city, state) VALUES ('a', 'San Francisco', 'CA'), ('b', 'New York', 'NY')"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.customers (customer_id, city, state) VALUES ('c', 'San Francisco', 'CA'), ('d', 'New York', 'NY')"
        ).await?;

    service
        .exec_query(
            "INSERT INTO foo.products (product_id, name) VALUES (1, 'Potato'), (2, 'Tomato')",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT city, name, sum(amount) FROM foo.orders o \
            LEFT JOIN foo.products p ON orders_product_id = product_id \
            LEFT JOIN foo.customers c ON orders_customer_id = customer_id \
            WHERE customer_id = 'a' \
            GROUP BY 1, 2 ORDER BY 3 DESC, 1 ASC, 2 ASC",
        )
        .await?;

    let expected = vec![Row::new(vec![
        TableValue::String("San Francisco".to_string()),
        TableValue::String("Potato".to_string()),
        TableValue::Int(10),
    ])];

    assert_eq!(result.get_rows(), &expected);
    Ok(())
}

async fn three_tables_join_with_union(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service.exec_query("CREATE TABLE foo.orders_1 (orders_customer_id text, orders_product_id int, amount int)").await?;
    service.exec_query("CREATE TABLE foo.orders_2 (orders_customer_id text, orders_product_id int, amount int)").await?;
    service
        .exec_query("CREATE INDEX orders_by_product_1 ON foo.orders_1 (orders_product_id)")
        .await?;
    service
        .exec_query("CREATE INDEX orders_by_product_2 ON foo.orders_2 (orders_product_id)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.customers (customer_id text, city text, state text)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.products (product_id int, name text)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.orders_1 (orders_customer_id, orders_product_id, amount) VALUES ('a', 1, 10), ('b', 2, 2), ('b', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.orders_1 (orders_customer_id, orders_product_id, amount) VALUES ('b', 1, 10), ('c', 2, 2), ('c', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.orders_2 (orders_customer_id, orders_product_id, amount) VALUES ('c', 1, 10), ('d', 2, 2), ('d', 2, 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.customers (customer_id, city, state) VALUES ('a', 'San Francisco', 'CA'), ('b', 'New York', 'NY')"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.customers (customer_id, city, state) VALUES ('c', 'San Francisco', 'CA'), ('d', 'New York', 'NY')"
        ).await?;

    service
        .exec_query(
            "INSERT INTO foo.products (product_id, name) VALUES (1, 'Potato'), (2, 'Tomato')",
        )
        .await?;

    let result = service.exec_query(
            "SELECT city, name, sum(amount) FROM (SELECT * FROM foo.orders_1 UNION ALL SELECT * FROM foo.orders_2) o \
            LEFT JOIN foo.customers c ON orders_customer_id = customer_id \
            LEFT JOIN foo.products p ON orders_product_id = product_id \
            WHERE customer_id = 'a' \
            GROUP BY 1, 2 ORDER BY 3 DESC, 1 ASC, 2 ASC"
        ).await?;

    let expected = vec![Row::new(vec![
        TableValue::String("San Francisco".to_string()),
        TableValue::String("Potato".to_string()),
        TableValue::Int(10),
    ])];

    assert_eq!(result.get_rows(), &expected);
    Ok(())
}

async fn in_list(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.customers (id text, city text, state text)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.customers (id, city, state) VALUES ('a', 'San Francisco', 'CA'), ('b', 'New York', 'NY'), ('c', 'San Diego', 'CA'), ('d', 'Austin', 'TX')"
        ).await?;

    let result = service
        .exec_query("SELECT count(*) from foo.customers WHERE state in ('CA', 'TX')")
        .await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(3)]));
    Ok(())
}

async fn in_list_with_union(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.customers_1 (id text, city text, state text)")
        .await?;

    service
        .exec_query("CREATE TABLE foo.customers_2 (id text, city text, state text)")
        .await?;

    service.exec_query(
        "INSERT INTO foo.customers_1 (id, city, state) VALUES ('a1', 'San Francisco', 'CA'), ('b1', 'New York', 'NY'), ('c1', 'San Diego', 'CA'), ('d1', 'Austin', 'TX')"
    ).await?;

    service.exec_query(
        "INSERT INTO foo.customers_2 (id, city, state) VALUES ('a2', 'San Francisco', 'CA'), ('b2', 'New York', 'NY'), ('c2', 'San Diego', 'CA'), ('d2', 'Austin', 'TX')"
    ).await?;

    let result = service
        .exec_query("SELECT count(*) from (SELECT * FROM foo.customers_1 UNION ALL SELECT * FROM foo.customers_2) AS `customers` WHERE state in ('CA', 'TX')")
        .await
        ?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(6)]));
    Ok(())
}

async fn numeric_cast_setup(service: &dyn SqlClient) -> Result<&'static str, CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.managers (id text, department_id int)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.managers (id, department_id) VALUES ('a', 1), ('b', 3), ('c', 3), ('d', 5)"
        ).await?;

    Ok(("SELECT count(*) from foo.managers WHERE department_id in ('3', '5')") as _)
}

async fn numeric_cast(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let query = numeric_cast_setup(service.as_ref()).await?;

    let result = service.exec_query(query).await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(3)]));
    Ok(())
}

async fn planning_numeric_cast(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let query = numeric_cast_setup(service.as_ref()).await?;

    // Check that we're casting '3' to int and not department_id to Utf8, with our Cube-specific type_coercion changes in DF.
    let plans = service.plan_query(query).await?;
    let expected =
        "Projection, [count(Int64(1))@0:count(*)]\
        \n  LinearFinalAggregate\
        \n    CoalescePartitions\
        \n      ClusterSend, partitions: [[1]]\
        \n        CoalescePartitions\
        \n          LinearPartialAggregate\
        \n            Projection, []\
        \n              Filter, predicate: department_id@0 = 3 OR department_id@0 = 5\
        \n                Scan, index: default:1:[1], fields: [department_id], predicate: department_id = Int64(3) OR department_id = Int64(5)\
        \n                  Empty";
    assert_eq!(
        expected,
        pp_phys_plan_ext(
            plans.router.as_ref(),
            &PPOptions {
                traverse_past_clustersend: true,
                show_filters: true,
                ..PPOptions::none()
            }
        ),
    );
    Ok(())
}

async fn cast_timestamp_to_utf8(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (id text, created timestamp)")
        .await?;

    service.exec_query(
        "INSERT INTO foo.timestamps (id, created) VALUES ('a', '2022-01-01T00:00:00Z'), ('b', '2021-01-01T00:00:00Z')"
    ).await?;

    let r = service
        .exec_query("SELECT id, CAST(created AS VARCHAR) from foo.timestamps ORDER BY id ASC")
        .await?;

    assert_eq!(
        to_rows(&r),
        rows(&[("a", "2022-01-01T00:00:00"), ("b", "2021-01-01T00:00:00"),])
    );
    Ok(())
}

async fn numbers_to_bool(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let r = service
        .exec_query("SELECT 1 = TRUE, FALSE = 0, -1 = TRUE, 123 = TRUE")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(true, true, true, true)]));

    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.bools (b boolean, i int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.bools(b, i) VALUES (true, 0), (false, 0), (true, 123), (false, 123)",
        )
        .await?;

    // Compare array with constant.
    let r = service
        .exec_query("SELECT b, b = 1, b = 0, b = 123 FROM s.bools GROUP BY 1, 2, 3 ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[(false, false, true, false), (true, true, false, true)])
    );

    // Compare array with array.
    let r = service
        .exec_query("SELECT b, i, b = i FROM s.bools ORDER BY 1, 2")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (false, 0, true),
            (false, 123, false),
            (true, 0, false),
            (true, 123, true)
        ])
    );

    // Other types work fine.
    let r = service
        .exec_query("SELECT 1 = 1, '1' = 1, 'foo' = 'foo'")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(true, true, true)]));
    Ok(())
}

async fn union(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.orders1 (customer_id text, amount int)")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE foo.orders2 (customer_id text, amount int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.orders1 (customer_id, amount) VALUES ('a', 10), ('b', 2), ('b', 3)",
        )
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.orders2 (customer_id, amount) VALUES ('b', 20), ('c', 20), ('b', 30)",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT `u`.customer_id, sum(`u`.amount) FROM \
            (select * from foo.orders1 union all select * from foo.orders2) `u` \
            WHERE `u`.customer_id like '%' GROUP BY 1 ORDER BY 2 DESC",
        )
        .await?;

    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![
            TableValue::String("b".to_string()),
            TableValue::Int(55)
        ])
    );
    assert_eq!(
        result.get_rows()[1],
        Row::new(vec![
            TableValue::String("c".to_string()),
            TableValue::Int(20)
        ])
    );
    assert_eq!(
        result.get_rows()[2],
        Row::new(vec![
            TableValue::String("a".to_string()),
            TableValue::Int(10)
        ])
    );
    Ok(())
}

async fn nested_union_empty_tables(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.un_1 (a int, b int, c int)")
        .await?;

    let _ = service
        .exec_query("CREATE TABLE foo.un (a int, b int, c int)")
        .await?;

    let _ = service
        .exec_query("CREATE TABLE foo.un_2 (a int, b int, c int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.un (a, b, c) VALUES
                        (1, 2, 3),
                        (2, 3, 4),
                        (5, 6, 7),
                        (8, 9, 10)",
        )
        .await?;

    let result = service
        .exec_query(
            "
                SELECT aa, bb FROM
                (
                    SELECT
                        a aa,
                        b bb
                    FROM (
                        SELECT * FROM foo.un
                    )
                    UNION ALL
                    SELECT
                        a aa,
                        b bb
                    FROM
                    (
                        SELECT * FROM foo.un_1
                        UNION ALL
                        SELECT * FROM foo.un_2

                    )
                )
                GROUP BY 1, 2 ORDER BY 2 LIMIT 2
            ",
        )
        .await?;

    assert_eq!(result.get_rows().len(), 2);
    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![TableValue::Int(1), TableValue::Int(2),])
    );

    assert_eq!(
        result.get_rows()[1],
        Row::new(vec![TableValue::Int(2), TableValue::Int(3),])
    );
    Ok(())
}

async fn timestamp_select(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA foo").await?;

    let _ = service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.timestamps (t) VALUES ('2020-01-01T00:00:00.000Z'), ('2020-01-02T00:00:00.000Z'), ('2020-01-03T00:00:00.000Z')"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.timestamps (t) VALUES ('2020-01-01T00:00:00.000Z'), ('2020-01-02T00:00:00.000Z'), ('2020-01-03T00:00:00.000Z')"
        ).await?;

    let result = service.exec_query("SELECT count(*) from foo.timestamps WHERE t >= to_timestamp('2020-01-02T00:00:00.000Z')").await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(4)]));
    Ok(())
}

async fn timestamp_seconds_frac(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    for s in &[
        "1970-01-01T00:00:00.123Z",
        "1970-01-01T00:00:00.123",
        "1970-01-01 00:00:00.123Z",
        "1970-01-01 00:00:00.123 UTC",
    ] {
        assert_eq!(
            timestamp_from_string(s).expect(s).get_time_stamp(),
            123000000,
            "input {}",
            s
        );
        if s.ends_with("UTC") {
            // Currently accepted only on ingestion.
            continue;
        }
        let r = service
            .exec_query(&format!("SELECT to_timestamp('{}')", s))
            .await?;
        assert_eq!(to_rows(&r), rows(&[TimestampValue::new(123000000)]));
    }
    Ok(())
}

async fn column_escaping(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp, amount int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.timestamps (t, amount) VALUES \
            ('2020-01-01T00:00:00.000Z', 1), \
            ('2020-01-01T00:01:00.000Z', 2), \
            ('2020-01-02T00:10:00.000Z', 3)",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT date_trunc('day', `timestamp`.t) `day`, sum(`timestamp`.amount) \
            FROM foo.timestamps `timestamp` \
            WHERE `timestamp`.t >= to_timestamp('2020-01-02T00:00:00.000Z') GROUP BY 1",
        )
        .await?;

    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![
            TableValue::Timestamp(TimestampValue::new(1577923200000000000)),
            TableValue::Int(3)
        ])
    );
    Ok(())
}

async fn information_schema(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp, amount int)")
        .await?;

    let result = service
        .exec_query("SELECT schema_name FROM information_schema.schemata")
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![Row::new(vec![TableValue::String("foo".to_string())])]
    );

    let result = service
        .exec_query("SELECT table_name FROM information_schema.tables")
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![Row::new(vec![TableValue::String("timestamps".to_string())])]
    );
    Ok(())
}

async fn system_query_cache(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp, amount int)")
        .await?;

    service.exec_query("SELECT * FROM foo.timestamps").await?;

    service
        .exec_query("SELECT * FROM system.query_cache")
        .await?;
    Ok(())
}

async fn metastore_rocksdb_tables(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service
        .exec_query("SELECT * FROM metastore.rocksdb_properties")
        .await?;
    Ok(())
}

async fn cachestore_rocksdb_tables(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service
        .exec_query("SELECT * FROM cachestore.rocksdb_properties")
        .await?;
    Ok(())
}

async fn case_column_escaping(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp, amount int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.timestamps (t, amount) VALUES \
            ('2020-01-01T00:00:00.000Z', 1), \
            ('2020-01-01T00:01:00.000Z', 2), \
            ('2020-01-02T00:10:00.000Z', 3)",
        )
        .await?;

    let result = service.exec_query(
            "SELECT date_trunc('day', `timestamp`.t) `day`, sum(CASE WHEN `timestamp`.t > to_timestamp('2020-01-02T00:01:00.000Z') THEN `timestamp`.amount END) \
            FROM foo.timestamps `timestamp` \
            WHERE `timestamp`.t >= to_timestamp('2020-01-02T00:00:00.000Z') GROUP BY 1"
        ).await?;

    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![
            TableValue::Timestamp(TimestampValue::new(1577923200000000000)),
            TableValue::Int(3)
        ])
    );
    Ok(())
}

async fn inner_column_escaping(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp, amount int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.timestamps (t, amount) VALUES \
            ('2020-01-01T00:00:00.000Z', 1), \
            ('2020-01-01T00:01:00.000Z', 2), \
            ('2020-01-02T00:10:00.000Z', 3)",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT date_trunc('day', `t`) `day`, sum(`amount`) \
            FROM foo.timestamps `timestamp` \
            WHERE `t` >= to_timestamp('2020-01-02T00:00:00.000Z') GROUP BY 1",
        )
        .await?;

    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![
            TableValue::Timestamp(TimestampValue::new(1577923200000000000)),
            TableValue::Int(3)
        ])
    );
    Ok(())
}

async fn convert_tz(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp, amount int)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.timestamps (t, amount) VALUES \
            ('2020-01-01T00:00:00.000Z', 1), \
            ('2020-01-01T00:01:00.000Z', 2), \
            ('2020-01-02T00:10:00.000Z', 3)",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT date_trunc('day', `t`) `day`, sum(`amount`) \
            FROM foo.timestamps `timestamp` \
            WHERE `t` >= convert_tz(to_timestamp('2020-01-02T08:00:00.000Z'), '-08:00') GROUP BY 1",
        )
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![Row::new(vec![
            TableValue::Timestamp(TimestampValue::new(1577923200000000000)),
            TableValue::Int(3)
        ])]
    );
    Ok(())
}

async fn date_trunc(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (t timestamp)")
        .await?;

    service
        .exec_query(
            "INSERT INTO foo.timestamps (t) VALUES \
            ('2020-01-01T00:00:00.000Z'), \
            ('2020-03-01T00:00:00.000Z'), \
            ('2020-04-01T00:00:00.000Z'), \
            ('2020-07-01T00:00:00.000Z'), \
            ('2020-09-01T00:00:00.000Z')",
        )
        .await?;

    let result = service
        .exec_query(
            "SELECT date_trunc('quarter', `t`) `quarter` \
            FROM foo.timestamps `timestamp`",
        )
        .await?;

    assert_eq!(
        result.get_rows(),
        &vec![
            Row::new(vec![TableValue::Timestamp(TimestampValue::new(
                1577836800000000000
            )),]),
            Row::new(vec![TableValue::Timestamp(TimestampValue::new(
                1577836800000000000
            )),]),
            Row::new(vec![TableValue::Timestamp(TimestampValue::new(
                1585699200000000000
            )),]),
            Row::new(vec![TableValue::Timestamp(TimestampValue::new(
                1593561600000000000
            )),]),
            Row::new(vec![TableValue::Timestamp(TimestampValue::new(
                1593561600000000000
            )),])
        ]
    );
    Ok(())
}

async fn ilike(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.strings(t text, pat text)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.strings(t, pat) \
             VALUES ('aba', '%ABA'), ('ABa', '%aba%'), ('CABA', 'aba%'), ('ZABA', '%a%b%a%'), ('ZZZ', 'zzz'), ('TTT', 'TTT'),\
             ('some_underscore', '%some\\\\_underscore%'),\
             ('test [ special 1', '%test [%'),\
             ('test ( special 2', '%test (%'),\
             ('111 test {)?*|+aaa', '%test {)?*|+aaa'),\
             ('test2 }]\\\\222 ', 'test2 }]\\\\\\\\%'),\
             ('test2 -[]{}()*+?.,^$|# 2', '%-[]{}()*+?.,^$|#%')\
             ",

        )
        .await
        ?;
    let r = service
        .exec_query("SELECT t FROM s.strings WHERE t ILIKE '%aBA%' ORDER BY t")
        .await?;
    assert_eq!(to_rows(&r), rows(&["ABa", "CABA", "ZABA", "aba"]));

    let r = service
        .exec_query("SELECT t FROM s.strings WHERE t ILIKE 'aBA%' ORDER BY t")
        .await?;
    assert_eq!(to_rows(&r), rows(&["ABa", "aba"]));

    let r = service
        .exec_query("SELECT t FROM s.strings WHERE t ILIKE '%aBA' ORDER BY t")
        .await?;
    assert_eq!(to_rows(&r), rows(&["ABa", "CABA", "ZABA", "aba"]));

    let r = service
        .exec_query("SELECT t FROM s.strings WHERE t ILIKE 'aBA' ORDER BY t")
        .await?;
    assert_eq!(to_rows(&r), rows(&["ABa", "aba"]));

    let r = service
        .exec_query(
            "SELECT t FROM s.strings WHERE t ILIKE CONCAT('%', 'some\\\\_underscore', '%') ORDER BY t",
        )
        .await
        ?;
    assert_eq!(to_rows(&r), rows(&["some_underscore"]));

    let r = service
        .exec_query("SELECT t FROM s.strings WHERE t ILIKE CONCAT('%', '(', '%') ORDER BY t")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&["test ( special 2", "test2 -[]{}()*+?.,^$|# 2"])
    );

    let r = service
        .exec_query("SELECT t FROM s.strings WHERE t ILIKE CONCAT('%', '?*|+', '%') ORDER BY t")
        .await?;
    assert_eq!(to_rows(&r), rows(&["111 test {)?*|+aaa"]));

    let r = service
        .exec_query(
            "SELECT t FROM s.strings WHERE t ILIKE CONCAT('%', '-[]{}()*+?.,^', '%') ORDER BY t",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&["test2 -[]{}()*+?.,^$|# 2"]));
    // Compare constant string with a bunch of patterns.
    // Inputs are: ('aba', '%ABA'), ('ABa', '%aba%'), ('CABA', 'aba%'), ('ZABA', '%a%b%a%'),
    //             ('ZZZ', 'zzz'), ('TTT', 'TTT').
    let r = service
        .exec_query("SELECT pat FROM s.strings WHERE 'aba' ILIKE pat ORDER BY pat")
        .await?;
    assert_eq!(to_rows(&r), rows(&["%ABA", "%a%b%a%", "%aba%", "aba%"]));

    let r = service
        .exec_query("SELECT pat FROM s.strings WHERE 'ggggtest (fjfj)' ILIKE pat ORDER BY pat")
        .await?;
    assert_eq!(to_rows(&r), rows(&["%test (%"]));

    // Compare array against array.
    let r = service
        .exec_query("SELECT t, pat FROM s.strings WHERE t ILIKE pat ORDER BY t")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            ("111 test {)?*|+aaa", "%test {)?*|+aaa"),
            ("ABa", "%aba%"),
            ("TTT", "TTT"),
            ("ZABA", "%a%b%a%"),
            ("ZZZ", "zzz"),
            ("aba", "%ABA"),
            ("some_underscore", "%some\\_underscore%"),
            ("test ( special 2", "%test (%"),
            ("test [ special 1", "%test [%"),
            ("test2 -[]{}()*+?.,^$|# 2", "%-[]{}()*+?.,^$|#%"),
            ("test2 }]\\222 ", "test2 }]\\\\%"),
        ])
    );

    // Check NOT ILIKE also works.
    let r = service
        .exec_query("SELECT t, pat FROM s.strings WHERE t NOT ILIKE pat ORDER BY t")
        .await?;
    assert_eq!(to_rows(&r), rows(&[("CABA", "aba%")]));
    Ok(())
}

async fn coalesce(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;

    service
        .exec_query("CREATE TABLE s.Data (n int, v int, s text)")
        .await?;

    service
        .exec_query(
            "INSERT INTO s.Data (n, v, s) VALUES \
            (1, 2, 'foo'),\
            (null, 3, 'bar'),\
            (null, null, 'baz'),\
            (null, null, null)",
        )
        .await?;

    let r = service.exec_query("SELECT coalesce(1, 2, 3)").await?;
    assert_eq!(to_rows(&r), vec![vec![TableValue::Int(1)]]);
    let r = service.exec_query("SELECT coalesce(NULL, 2, 3)").await?;
    assert_eq!(to_rows(&r), vec![vec![TableValue::Int(2)]]);
    let r = service
        .exec_query("SELECT coalesce(NULL, NULL, NULL)")
        .await?;
    assert_eq!(to_rows(&r), vec![vec![TableValue::Null]]);
    let r = service
        .exec_query("SELECT coalesce(n, v) FROM s.Data ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![
            vec![TableValue::Int(1)],
            vec![TableValue::Int(3)],
            vec![TableValue::Null],
            vec![TableValue::Null],
        ]
    );
    // Type mismatch
    service
        .exec_query("SELECT coalesce(n, v, s) FROM s.Data ORDER BY 1")
        .await
        .unwrap_err();

    let r = service
        .exec_query("SELECT coalesce(n+1,v+1,0) FROM s.Data ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![
            vec![TableValue::Int(0)],
            vec![TableValue::Int(0)],
            vec![TableValue::Int(2)],
            vec![TableValue::Int(4)],
        ]
    );

    service
        .exec_query("SELECT n, coalesce() FROM s.Data ORDER BY 1")
        .await
        .unwrap_err();
    Ok(())
}

async fn count_distinct_crash(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    if !service.is_migration() {
        service.exec_query("CREATE SCHEMA s").await?;
        service.exec_query("CREATE TABLE s.Data (n int)").await?;

        let r = service
            .exec_query("SELECT COUNT(DISTINCT n) FROM s.Data")
            .await?;
        assert_eq!(to_rows(&r), vec![vec![TableValue::Int(0)]]);

        service
            .exec_query("INSERT INTO s.Data(n) VALUES (1), (2), (3), (3), (4), (4), (4)")
            .await?;
    }

    let r = service
        .exec_query("SELECT COUNT(DISTINCT n) FROM s.Data WHERE n > 4")
        .await?;

    assert_eq!(to_rows(&r), vec![vec![TableValue::Int(0)]]);
    let r = service
        .exec_query("SELECT COUNT(DISTINCT CASE WHEN n > 4 THEN n END) FROM s.Data")
        .await?;
    assert_eq!(to_rows(&r), vec![vec![TableValue::Int(0)]]);
    Ok(())
}

async fn count_distinct_group_by_crash(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service.exec_query("CREATE TABLE s.Data (n string)").await?;
    service
        .exec_query("INSERT INTO s.Data (n) VALUES ('a'), ('b'), ('c'), ('b'), ('c')")
        .await?;

    let r = service
        .exec_query(
            "SELECT n, COUNT(DISTINCT CASE WHEN n <> 'a' THEN n END), COUNT(*) \
             FROM s.Data \
             GROUP BY 1 \
             ORDER BY 1",
        )
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![
            vec![
                TableValue::String("a".to_string()),
                TableValue::Int(0),
                TableValue::Int(1)
            ],
            vec![
                TableValue::String("b".to_string()),
                TableValue::Int(1),
                TableValue::Int(2)
            ],
            vec![
                TableValue::String("c".to_string()),
                TableValue::Int(1),
                TableValue::Int(2)
            ],
        ]
    );
    Ok(())
}

async fn count_distinct_take_crash(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(id int, n int)")
        .await?;
    service
        .exec_query("INSERT INTO s.data(id, n) VALUES (1, 1)")
        .await?;
    // This used to crash because `take` on empty list returned null. The implementation can easily
    // change with time, though, so test is not robust.
    let r = service
        .exec_query("SELECT n, COUNT(DISTINCT CASE WHEN id = 2 THEN 2 END) FROM s.data GROUP BY n")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(1, 0)]));
    Ok(())
}

async fn create_schema_if_not_exists(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS Foo")
        .await?;
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS Foo")
        .await?;
    Ok(())
}

async fn create_index_before_ingestion(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.timestamps (id int, t timestamp)")
        .await?;

    service
        .exec_query("CREATE INDEX by_timestamp ON foo.timestamps (`t`)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.timestamps (id, t) VALUES (1, '2020-01-01T00:00:00.000Z'), (2, '2020-01-02T00:00:00.000Z'), (3, '2020-01-03T00:00:00.000Z')"
        ).await?;

    let result = service.exec_query("SELECT count(*) from foo.timestamps WHERE t >= to_timestamp('2020-01-02T00:00:00.000Z')").await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(2)]));
    Ok(())
}

async fn ambiguous_join_sort(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.sessions (t timestamp, id int)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.page_views (session_id int, page_view_count int)")
        .await?;

    service
        .exec_query("CREATE INDEX by_id ON foo.sessions (id)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.sessions (t, id) VALUES ('2020-01-01T00:00:00.000Z', 1), ('2020-01-02T00:00:00.000Z', 2), ('2020-01-03T00:00:00.000Z', 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.page_views (session_id, page_view_count) VALUES (1, 10), (2, 20), (3, 30)"
        ).await?;

    let result = service.exec_query("SELECT sum(p.page_view_count) from foo.sessions s JOIN foo.page_views p ON s.id = p.session_id WHERE s.t >= to_timestamp('2020-01-02T00:00:00.000Z')").await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(50)]));
    Ok(())
}

async fn join_with_aliases(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.sessions (t timestamp, id int)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.page_views (session_id int, page_view_count int)")
        .await?;

    service
        .exec_query("CREATE INDEX by_id ON foo.sessions (id)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.sessions (t, id) VALUES ('2020-01-01T00:00:00.000Z', 1), ('2020-01-02T00:00:00.000Z', 2), ('2020-01-03T00:00:00.000Z', 3)"
        ).await?;

    service.exec_query(
            "INSERT INTO foo.page_views (session_id, page_view_count) VALUES (1, 10), (2, 20), (3, 30)"
        ).await?;

    let result = service.exec_query("SELECT sum(`page_view_count`) from foo.sessions `sessions` JOIN foo.page_views `page_views` ON `id` = `session_id` WHERE `t` >= to_timestamp('2020-01-02T00:00:00.000Z')").await?;

    assert_eq!(result.get_rows()[0], Row::new(vec![TableValue::Int(50)]));
    Ok(())
}

async fn group_by_without_aggregates(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query(
            "CREATE TABLE foo.sessions (id int, company_id int, location_id int, t timestamp)",
        )
        .await?;

    service
        .exec_query("CREATE INDEX by_company ON foo.sessions (company_id, location_id, id)")
        .await?;

    service.exec_query(
            "INSERT INTO foo.sessions (company_id, location_id, t, id) VALUES (1, 1, '2020-01-01T00:00:00.000Z', 1), (1, 2, '2020-01-02T00:00:00.000Z', 2), (2, 1, '2020-01-03T00:00:00.000Z', 3)"
        ).await?;

    let result = service.exec_query("SELECT `sessions`.location_id, `sessions`.id FROM foo.sessions `sessions` GROUP BY 1, 2 ORDER BY 2").await?;

    assert_eq!(
        result.get_rows(),
        &vec![
            Row::new(vec![TableValue::Int(1), TableValue::Int(1)]),
            Row::new(vec![TableValue::Int(2), TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(1), TableValue::Int(3)]),
        ]
    );
    Ok(())
}

async fn create_table_with_location(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let paths = {
        let dir = env::temp_dir();

        let path_1 = dir.clone().join("foo-1.csv");
        let path_2 = dir.clone().join("foo-2.csv.gz");
        let mut file = File::create(path_1.clone())?;

        file.write_all("id,city,arr,t\n".as_bytes())?;
        file.write_all("1,San Francisco,\"[\"\"Foo\n\n\"\",\"\"Bar\"\",\"\"FooBar\"\"]\",\"2021-01-24 12:12:23 UTC\"\n".as_bytes())?;
        file.write_all("2,\"New York\",\"[\"\"\"\"]\",2021-01-24 19:12:23.123 UTC\n".as_bytes())?;
        file.write_all("3,New York,\"de Comunicación\",2021-01-25 19:12:23 UTC\n".as_bytes())?;

        let mut file = GzipEncoder::new(BufWriter::new(
            tokio::fs::File::create(path_2.clone()).await?,
        ));

        file.write_all("id,city,arr,t\n".as_bytes()).await?;
        file.write_all("1,San Francisco,\"[\"\"Foo\"\",\"\"Bar\"\",\"\"FooBar\"\"]\",\"2021-01-24 12:12:23 UTC\"\n".as_bytes()).await?;
        file.write_all("2,\"New York\",\"[\"\"\"\"]\",2021-01-24 19:12:23 UTC\n".as_bytes())
            .await?;
        file.write_all("3,New York,,2021-01-25 19:12:23 UTC\n".as_bytes())
            .await?;
        file.write_all("4,New York,\"\",2021-01-25 19:12:23 UTC\n".as_bytes())
            .await?;
        file.write_all("5,New York,\"\",2021-01-25 19:12:23 UTC\n".as_bytes())
            .await?;
        file.write_all("6,New York,\\\\N,\"\\N\"\n".as_bytes())
            .await?;

        file.shutdown().await?;

        vec![path_1, path_2]
    };

    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS Foo")
        .await?;
    let _ = service.exec_query(
            &format!(
                "CREATE TABLE Foo.Persons (id int, city text, t timestamp, arr text) INDEX persons_city (`city`, `id`) LOCATION {}",
                paths.into_iter().map(|p| format!("'{}'", p.to_string_lossy())).join(",")
            )
        ).await?;
    service.migration_hardcode_next_query(Err(CubeError::user("... has data ...".to_owned())));
    let res = service
        .exec_query("CREATE INDEX by_city ON Foo.Persons (city)")
        .await;
    let error = format!("{:?}", res);
    assert!(error.contains("has data"));

    let result = service
        .exec_query("SELECT count(*) as cnt from Foo.Persons")
        .await?;
    assert_eq!(result.get_rows(), &vec![Row::new(vec![TableValue::Int(9)])]);

    let result = service.exec_query("SELECT count(*) as cnt from Foo.Persons WHERE arr = '[\"Foo\",\"Bar\",\"FooBar\"]' or arr = '[\"\"]' or arr is null").await?;
    assert_eq!(result.get_rows(), &vec![Row::new(vec![TableValue::Int(7)])]);
    Ok(())
}

async fn create_table_with_location_messed_order(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let paths = {
        let dir = env::temp_dir();

        let path_1 = dir.clone().join("messed-order.csv");
        let mut file = File::create(path_1.clone())?;

        file.write_all("c6,c11,c10,c5,c9,c4,c2,c8,c1,c3,c7,c12\n".as_bytes())?;
        file.write_all(
            "123,0,0.5,193,0.5,2,2021-11-01,0.5,foo,42,0,2021-01-01 00:00:00\n".as_bytes(),
        )?;

        vec![path_1]
    };

    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let _ = service.exec_query(
        &format!(
            "CREATE TABLE test.main (`c1` varchar(255), `c2` date, `c3` bigint, `c4` bigint, `c5` bigint, `c6` bigint, `c7` double, `c8` double, `c9` double, `c10` double, `c11` double, `c12` timestamp)  LOCATION {}",
            paths.into_iter().map(|p| format!("'{}'", p.to_string_lossy())).join(",")
        )
    ).await?;

    let result = service
        .exec_query("SELECT count(*) as cnt from test.main")
        .await?;
    assert_eq!(result.get_rows(), &vec![Row::new(vec![TableValue::Int(1)])]);
    Ok(())
}

async fn create_table_with_location_invalid_digit(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let paths = {
        let dir = env::temp_dir();

        let path_1 = dir.clone().join("invalid_digit.csv");
        let mut file = File::create(path_1.clone())?;

        file.write_all("c1,c3\n".as_bytes())?;
        file.write_all("foo,1a23\n".as_bytes())?;

        vec![path_1]
    };

    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let res = service
        .exec_query(&format!(
            "CREATE TABLE test.main (`c1` text, `c3` decimal)  LOCATION {}",
            paths
                .into_iter()
                .map(|p| format!("'{}'", p.to_string_lossy()))
                .join(",")
        ))
        .await;

    println!("Res: {:?}", res);

    assert!(
        res.is_err(),
        "Expected invalid digit error but got {:?}",
        res
    );
    Ok(())
}

async fn create_table_with_csv(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let file = write_tmp_file(indoc! {"
        fruit,number
        apple,2
        banana,3
    "})?;
    let path = file.path().to_string_lossy();
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let _ = service
        .exec_query(format!("CREATE TABLE test.table (`fruit` text, `number` int) WITH (input_format = 'csv') LOCATION '{}'", path).as_str())
        .await
        ?;
    let result = service.exec_query("SELECT * FROM test.table").await?;
    assert_eq!(
        to_rows(&result),
        vec![
            vec![TableValue::String("apple".to_string()), TableValue::Int(2)],
            vec![TableValue::String("banana".to_string()), TableValue::Int(3)]
        ]
    );
    Ok(())
}

async fn create_table_with_csv_and_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let file = write_tmp_file(indoc! {"
        fruit,number
        apple,2
        banana,3
    "})?;
    let path = file.path().to_string_lossy();
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let _ = service
        .exec_query(format!("CREATE TABLE test.table (`fruit` text, `number` int) WITH (input_format = 'csv') INDEX by_number (`number`) LOCATION '{}'", path).as_str())
        .await
        ?;
    let result = service.exec_query("SELECT * FROM test.table").await?;
    assert_eq!(
        to_rows(&result),
        vec![
            vec![TableValue::String("apple".to_string()), TableValue::Int(2)],
            vec![TableValue::String("banana".to_string()), TableValue::Int(3)]
        ]
    );
    Ok(())
}

async fn create_table_with_csv_no_header(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let file = write_tmp_file(indoc! {"
        apple,2
        banana,3
    "})?;
    let path = file.path().to_string_lossy();
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let _ = service
        .exec_query(format!("CREATE TABLE test.table (`fruit` text, `number` int) WITH (input_format = 'csv_no_header') LOCATION '{}'", path).as_str())
        .await
        ?;
    let result = service.exec_query("SELECT * FROM test.table").await?;
    assert_eq!(
        to_rows(&result),
        vec![
            vec![TableValue::String("apple".to_string()), TableValue::Int(2)],
            vec![TableValue::String("banana".to_string()), TableValue::Int(3)]
        ]
    );
    Ok(())
}

async fn create_table_with_csv_no_header_and_delimiter(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let file = write_tmp_file(indoc! {"
        \"apple\u{0001}31
        a\"pple\u{0001}32
        a\"pp\"le\u{0001}12
        apple\u{0001}2
        banana\u{0001}3
        \"orange\" orange\u{0001}4
    "})?;
    let path = file.path().to_string_lossy();
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let _ = service
        .exec_query(format!("CREATE TABLE test.table (`fruit` text, `number` int) WITH (input_format = 'csv_no_header', delimiter = '^A', disable_quoting = true) LOCATION '{}'", path).as_str())
        .await
        ?;
    let result = service.exec_query("SELECT * FROM test.table").await?;
    assert_eq!(
        to_rows(&result),
        vec![
            vec![
                TableValue::String("\"apple".to_string()),
                TableValue::Int(31)
            ],
            vec![
                TableValue::String("\"orange\" orange".to_string()),
                TableValue::Int(4)
            ],
            vec![
                TableValue::String("a\"pp\"le".to_string()),
                TableValue::Int(12)
            ],
            vec![
                TableValue::String("a\"pple".to_string()),
                TableValue::Int(32)
            ],
            vec![TableValue::String("apple".to_string()), TableValue::Int(2)],
            vec![TableValue::String("banana".to_string()), TableValue::Int(3)],
        ]
    );
    Ok(())
}

async fn create_table_with_csv_no_header_and_quotes(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let file = write_tmp_file(indoc! {"
        \"\"\"apple\",31
        \"a\"\"pple\",32
        \"a\"\"pp\"\"le\",12
        apple,2
        banana,3
        \"\"\"orange\"\" orange\",4
    "})?;
    let path = file.path().to_string_lossy();
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS test")
        .await?;
    let _ = service
        .exec_query(format!("CREATE TABLE test.table (`fruit` text, `number` int) WITH (input_format = 'csv_no_header', delimiter = ',', disable_quoting = false) LOCATION '{}'", path).as_str())
        .await
        ?;
    let result = service.exec_query("SELECT * FROM test.table").await?;
    assert_eq!(
        to_rows(&result),
        vec![
            vec![
                TableValue::String("\"apple".to_string()),
                TableValue::Int(31)
            ],
            vec![
                TableValue::String("\"orange\" orange".to_string()),
                TableValue::Int(4)
            ],
            vec![
                TableValue::String("a\"pp\"le".to_string()),
                TableValue::Int(12)
            ],
            vec![
                TableValue::String("a\"pple".to_string()),
                TableValue::Int(32)
            ],
            vec![TableValue::String("apple".to_string()), TableValue::Int(2)],
            vec![TableValue::String("banana".to_string()), TableValue::Int(3)],
        ]
    );
    Ok(())
}

async fn create_table_with_url(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    // TODO serve this data ourselves
    let url = "https://data.wprdc.org/dataset/0b584c84-7e35-4f4d-a5a2-b01697470c0f/resource/e95dd941-8e47-4460-9bd8-1e51c194370b/download/bikepghpublic.csv";

    service
        .exec_query("CREATE SCHEMA IF NOT EXISTS foo")
        .await?;
    let create_table_sql = format!("CREATE TABLE foo.bikes (`Response ID` int, `Start Date` text, `End Date` text) WITH (input_format = 'csv') LOCATION '{}'", url);
    let (_, query_result) = tokio::join!(
        service.exec_query(&create_table_sql),
        service.exec_query("SELECT count(*) from foo.bikes")
    );
    assert!(
        query_result.is_err(),
        "Table shouldn't be ready but querying returns {:?}",
        query_result
    );

    let result = service.exec_query("SELECT count(*) from foo.bikes").await?;
    assert_eq!(
        result.get_rows(),
        &vec![Row::new(vec![TableValue::Int(813)])]
    );
    Ok(())
}

async fn create_table_fail_and_retry(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service.migration_hardcode_generic_err();
    service
        .exec_query(
            "CREATE TABLE s.Data(n int, v int) INDEX reverse (v,n) LOCATION 'non-existing-file'",
        )
        .await
        .unwrap_err();
    service
        .exec_query("CREATE TABLE s.Data(n int, v int) INDEX reverse (v,n)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data(n, v) VALUES (1, -1), (2, -2)")
        .await?;
    let rows = service
        .exec_query("SELECT n FROM s.Data ORDER BY n")
        .await?;
    assert_eq!(
        to_rows(&rows),
        vec![vec![TableValue::Int(1)], vec![TableValue::Int(2)]]
    );
    Ok(())
}

async fn empty_crash(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA IF NOT EXISTS s").await?;
    let _ = service
        .exec_query("CREATE TABLE s.Table (id int, s int)")
        .await?;
    let _ = service
        .exec_query("INSERT INTO s.Table(id, s) VALUES (1, 10);")
        .await?;

    let r = service
        .exec_query("SELECT * from s.Table WHERE id = 1 AND s = 15")
        .await?;
    assert_eq!(r.get_rows(), &vec![]);

    let r = service
        .exec_query("SELECT id, sum(s) from s.Table WHERE id = 1 AND s = 15 GROUP BY 1")
        .await?;
    assert_eq!(r.get_rows(), &vec![]);
    Ok(())
}

async fn bytes(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service.exec_query("CREATE SCHEMA IF NOT EXISTS s").await?;
    let _ = service
        .exec_query("CREATE TABLE s.Bytes (id int, data bytea)")
        .await?;
    let _ = service
        .exec_query(
            "INSERT INTO s.Bytes(id, data) VALUES (1, '01 ff 1a'), (2, X'deADbeef'), (3, 456)",
        )
        .await?;

    let result = service.exec_query("SELECT * from s.Bytes").await?;
    let r = result.get_rows();
    assert_eq!(r.len(), 3);
    assert_eq!(r[0].values()[1], TableValue::Bytes(vec![0x01, 0xff, 0x1a]));
    assert_eq!(
        r[1].values()[1],
        TableValue::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
    );
    assert_eq!(
        r[2].values()[1],
        TableValue::Bytes("456".as_bytes().to_vec())
    );
    Ok(())
}

async fn hyperloglog(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE hll.sketches (id int, hll varbinary)")
        .await?;

    let sparse = "X'020C0200C02FF58941D5F0C6'";
    let dense = "X' '";

    service
        .exec_query(&format!(
            "INSERT INTO hll.sketches (id, hll) VALUES (1, {s}), (2, {d}), (3, {s}), (4, {d})",
            s = sparse,
            d = dense
        ))
        .await?;

    //  Check cardinality.
    let result = service
        .exec_query("SELECT id, cardinality(hll) as cnt from hll.sketches WHERE id < 3 ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&result),
        vec![
            vec![TableValue::Int(1), TableValue::Int(2)],
            vec![TableValue::Int(2), TableValue::Int(655)]
        ]
    );
    // Check merge and cardinality.
    let result = service
        .exec_query("SELECT cardinality(merge(hll)) from hll.sketches WHERE id < 3")
        .await?;
    assert_eq!(to_rows(&result), vec![vec![TableValue::Int(657)]]);

    // Now merge all 4 HLLs, results should stay the same.
    let result = service
        .exec_query("SELECT cardinality(merge(hll)) from hll.sketches")
        .await?;
    assert_eq!(to_rows(&result), vec![vec![TableValue::Int(657)]]);

    // TODO: add format checks on insert and test invalid inputs.
    Ok(())
}

async fn hyperloglog_empty_inputs(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE hll.sketches (id int, hll varbinary)")
        .await?;

    let result = service
        .exec_query("SELECT cardinality(merge(hll)) from hll.sketches")
        .await?;
    assert_eq!(to_rows(&result), vec![vec![TableValue::Int(0)]]);

    let result = service
        .exec_query("SELECT merge(hll) from hll.sketches")
        .await?;
    assert_eq!(to_rows(&result), vec![vec![TableValue::Bytes(vec![])]]);
    Ok(())
}

async fn hyperloglog_empty_group_by(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE hll.sketches (id int, key int, hll varbinary)")
        .await?;

    let result = service
        .exec_query("SELECT key, cardinality(merge(hll)) from hll.sketches group by key")
        .await?;
    assert_eq!(to_rows(&result), Vec::<Vec<TableValue>>::new());
    Ok(())
}

async fn hyperloglog_inserts(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE hll.sketches (id int, hll hyperloglog)")
        .await?;

    service
        .exec_query("INSERT INTO hll.sketches(id, hll) VALUES (0, X'')")
        .await
        .expect_err("should not allow invalid HLL");
    service
        .exec_query("INSERT INTO hll.sketches(id, hll) VALUES (0, X'020C0200C02FF58941D5F0C6')")
        .await
        .expect("should allow valid HLL");
    service
        .exec_query("INSERT INTO hll.sketches(id, hll) VALUES (0, X'020C0200C02FF58941D5F0C6123')")
        .await
        .expect_err("should not allow invalid HLL (with extra bytes)");
    Ok(())
}

async fn create_table_with_location_and_hyperloglog(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let paths = {
        let dir = env::temp_dir();

        let path_1 = dir.clone().join("hyperloglog.csv");
        let mut file = File::create(path_1.clone())?;

        file.write_all("id,hll,hll_base\n".as_bytes())?;

        file.write_all(
            format!(
                "0,02 0c 01 00 80 a5 90 34,{}\n",
                base64::encode(vec![0x02, 0x0c, 0x01, 0x00, 0x80, 0xa5, 0x90, 0x34])
            )
            .as_bytes(),
        )?;
        file.write_all(
            format!(
                "1,02 0C 02 00 C0 2F F5 89 41 D5 F0 C6,{}\n",
                base64::encode(vec![
                    0x02, 0x0c, 0x02, 0x00, 0xc0, 0x2f, 0xf5, 0x89, 0x41, 0xd5, 0xf0, 0xc6
                ])
            )
            .as_bytes(),
        )?;

        vec![path_1]
    };
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query(&format!("CREATE TABLE hll.locations (id int, hll hyperloglog, hll_base hyperloglog) LOCATION {}",
            paths
                .into_iter()
                .map(|p| format!("'{}'", p.to_string_lossy()))
                .join(",")
        ))
        .await
        ?;

    let res = service
        .exec_query("SELECT cardinality(merge(hll)) = cardinality(merge(hll_base)) FROM hll.locations GROUP BY id")
        .await
        ?;
    assert_eq!(
        to_rows(&res),
        vec![
            vec![TableValue::Boolean(true)],
            vec![TableValue::Boolean(true)],
        ]
    );
    Ok(())
}
async fn create_table_with_location_and_hyperloglog_postgress(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let paths = {
        let dir = env::temp_dir();

        let path_1 = dir.clone().join("hyperloglog-pg.csv");
        let mut file = File::create(path_1.clone())?;

        file.write_all("id,hll,hll_base\n".as_bytes())?;

        file.write_all(
            format!("0,11 8b 7f,{}\n", base64::encode(vec![0x11, 0x8b, 0x7f])).as_bytes(),
        )?;
        file.write_all(
            format!(
                "1,12 8b 7f ee 22 c4 70 69 1a 81 34,{}\n",
                base64::encode(vec![
                    0x12, 0x8b, 0x7f, 0xee, 0x22, 0xc4, 0x70, 0x69, 0x1a, 0x81, 0x34
                ])
            )
            .as_bytes(),
        )?;

        vec![path_1]
    };
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query(&format!("CREATE TABLE hll.locations_pg (id int, hll HLL_POSTGRES, hll_base HLL_POSTGRES) LOCATION {}",
            paths
                .into_iter()
                .map(|p| format!("'{}'", p.to_string_lossy()))
                .join(",")
        ))
        .await
        ?;

    let res = service
        .exec_query("SELECT cardinality(merge(hll)) = cardinality(merge(hll_base)) FROM hll.locations_pg GROUP BY id")
        .await
        ?;
    assert_eq!(
        to_rows(&res),
        vec![
            vec![TableValue::Boolean(true)],
            vec![TableValue::Boolean(true)],
        ]
    );
    Ok(())
}

async fn create_table_with_location_and_hyperloglog_space_separated(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let paths = {
        let dir = env::temp_dir();

        let path_1 = dir.clone().join("hyperloglog-ssep.csv");
        let mut file = File::create(path_1.clone())?;

        file.write_all("id,hll,hll_base\n".as_bytes())?;

        file.write_all(
            format!(
                "0,02 0c 01 00 05 05 7b cf,{}\n",
                base64::encode(vec![0x02, 0x0c, 0x01, 0x00, 0x05, 0x05, 0x7b, 0xcf])
            )
            .as_bytes(),
        )?;
        file.write_all(
            format!(
                "1,02 0c 01 00 15 15 7b ff,{}\n",
                base64::encode(vec![0x02, 0x0c, 0x01, 0x00, 0x15, 0x15, 0x7b, 0xff])
            )
            .as_bytes(),
        )?;

        vec![path_1]
    };
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query(&format!("CREATE TABLE hll.locations_ssep (id int, hll varbinary, hll_base varbinary) LOCATION {}",
            paths
                .into_iter()
                .map(|p| format!("'{}'", p.to_string_lossy()))
                .join(",")
        ))
        .await
        ?;

    let res = service
        .exec_query("SELECT cardinality(merge(hll)) = cardinality(merge(hll_base)) FROM hll.locations_ssep GROUP BY id")
        .await
        ?;
    assert_eq!(
        to_rows(&res),
        vec![
            vec![TableValue::Boolean(true)],
            vec![TableValue::Boolean(true)],
        ]
    );
    let res = service
        .exec_query("SELECT hll, hll_base FROM hll.locations_ssep")
        .await?;
    for r in to_rows(&res).iter() {
        assert_eq!(r[0], r[1]);
    }
    println!("res {:?}", res);
    Ok(())
}
async fn hyperloglog_inplace_group_by(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let _ = service
        .exec_query("CREATE SCHEMA IF NOT EXISTS hll")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE hll.sketches1(id int, hll hyperloglog)")
        .await?;
    let _ = service
        .exec_query("CREATE TABLE hll.sketches2(id int, hll hyperloglog)")
        .await?;

    service
        .exec_query(
            "INSERT INTO hll.sketches1(id, hll) \
                     VALUES (0, X'020C0200C02FF58941D5F0C6'), \
                            (1, X'020C0200C02FF58941D5F0C6')",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO hll.sketches2(id, hll) \
                     VALUES (1, X'020C0200C02FF58941D5F0C6'), \
                            (2, X'020C0200C02FF58941D5F0C6')",
        )
        .await?;

    // Case expression should handle binary results.
    service
        .exec_query(
            "SELECT id, CASE WHEN id = 0 THEN merge(hll) ELSE merge(hll) END \
             FROM hll.sketches1 \
             GROUP BY 1",
        )
        .await?;
    // Without the ELSE branch.
    service
        .exec_query(
            "SELECT id, CASE WHEN id = 0 THEN merge(hll) END \
             FROM hll.sketches1 \
             GROUP BY 1",
        )
        .await?;
    // Binary type in condition. For completeness, probably not very useful in practice.
    // TODO: this fails for unrelated reasons, binary support is ad-hoc at this point.
    //       uncomment when fixed.
    // service.exec_query(
    //     "SELECT id, CASE hll WHEN '' THEN NULL else hll END \
    //      FROM hll.sketches1",
    // ).await?;

    // MergeSortExec uses the same code as case expression internally.
    let rows = service
        .exec_query(
            "SELECT id, cardinality(merge(hll)) \
             FROM
               (SELECT * FROM hll.sketches1
                UNION ALL
                SELECT * FROM hll.sketches2) \
             GROUP BY 1 ORDER BY 1",
        )
        .await?;
    assert_eq!(
        to_rows(&rows),
        vec![
            vec![TableValue::Int(0), TableValue::Int(2)],
            vec![TableValue::Int(1), TableValue::Int(2)],
            vec![TableValue::Int(2), TableValue::Int(2)],
        ]
    );
    Ok(())
}

async fn hyperloglog_postgres(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.hlls(id int, hll HLL_POSTGRES)")
        .await?;
    service.exec_query("INSERT INTO s.hlls(id, hll) VALUES \
        (1, X'118b7f'),\
        (2, X'128b7fee22c470691a8134'),\
        (3, X'138b7f04a10642078507c308e309230a420ac10c2510a2114511611363138116811848188218a119411a821ae11f0122e223a125a126632685276327a328e2296129e52b812fe23081320132c133e335a53641368236a23721374237e1382138e13a813c243e6140e341854304434148a24a034f8150c1520152e254e155a1564157e158e35ac25b265b615c615fc1620166a368226a416a626c016c816d677163728275817a637a817ac37b617c247c427d677f6180e18101826382e1846184e18541858287e1880189218a418b818bc38e018ea290a19244938295e4988198c299e29b239b419c419ce49da1a1e1a321a381a4c1aa61acc2ae01b0a1b101b142b161b443b801bd02bd61bf61c263c4a3c501c7a1caa1cb03cd03cf03cf42d123d4c3d662d744d901dd01df81e001e0a2e641e7e3edc1f0a2f1c1f203f484f5c4f763fc84fdc1fe02fea1'),\
        (4, X'148b7f21083288a4320a12086719c65108c1088422884511063388232904418c8520484184862886528c65198832106328c83114e6214831108518d03208851948511884188441908119083388661842818c43190c320ce4210a50948221083084a421c8328c632104221c4120d01284e20902318ca5214641942319101294641906228483184e128c43188e308882204a538c8328903288642102220c64094631086330c832106320c46118443886329062118a230c63108a320c23204a11852419c6528c85210a318c6308c41088842086308ce7110a418864190650884210ca631064108642a1022186518c8509862109020a0a4318671144150842400e5090631a0811848320c821888120c81114a220880290622906310d0220c83090a118c433106128c221902210cc23106029044114841104409862190c43188111063104c310c6728c8618c62290441102310c23214440882438ca2110a32908548c432110329462188a43946328842114640944320884190c928c442084228863318a2190a318c6618ca3114651886618c44190c5108e2110612144319062284641908428882314862106419883310421988619ca420cc511442104633888218c4428465288651910730c81118821088218c6418c45108452106519ce410d841904218863308622086211483198c710c83104a328c620906218864118623086418c8711423094632186420c4620c41104620a441108e40882628c6311c212046428c8319021104672888428ca320c431984418c4209043084451886510c641108310c4c20c66188472146310ca71084820c621946218c8228822190e2410861904411c27288621144328c6440c6311063190813086228ca710c2218c4718865188c2114850888608864404a3194e22882310ce53088619ca31904519503188e1118c4214cb2948110c6119c2818c843108520c43188c5204821186528c871908311086214c630c4218c8418cc3298a31888210c63110a121042198622886531082098c419c4210c6210c8338c25294610944518c442104610884104424206310c8311462288873102308c2440c451082228824310440982220c4240c622084310c642850118c641148430d0128c8228c2120c221884428863208c21a0a4190a4404c21186548865204633906308ca32086211c8319ce22146520c6120803318a518c840084519461208c21908538cc428c2110844384e40906320c44014a3204e62042408c8328c632146318c812004310c41318e3208a5308a511827104a4188c51048421446090a7088631102231484104473084318c41210860906919083190652906129c4628c45310652848221443114420084500865184a618c81198c32906418c63190e320c231882728484184671888309465188a320c83208632144318c6331c642988108c61218812144328d022844021022184a31908328c6218c2328c4528cc541428190641046418c84108443146230c6419483214232184411863290a210824318c220868194631106618c43188821048230c4128c6310c0330462094241106330c42188c321043118863046438823110a041464108e3190e4209a11902439c43188631104321008090441106218c6419064294a229463594622244320cc71184510902924421908218c62308641044328ca328882111012884120ca52882428c62184442086718c4221c8211082208a321023115270086218c4218c6528ce400482310a520c43104a520c44210811884118c4310864198263942331822')"
    ).await?;

    let r = service
        .exec_query("SELECT id, cardinality(hll) FROM s.hlls ORDER BY id")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(1, 0), (2, 1), (3, 164), (4, 9722)]));
    Ok(())
}

async fn hyperloglog_snowflake(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(id int, hll HLL_SNOWFLAKE) ")
        .await?;
    service.exec_query(r#"INSERT INTO s.Data(id, hll) VALUES (1, '{"precision": 12,
                          "sparse": {
                            "indices": [223,736,976,1041,1256,1563,1811,2227,2327,2434,2525,2656,2946,2974,3256,3745,3771,4066],
                            "maxLzCounts": [1,2,1,4,2,2,3,1,1,2,4,2,1,1,2,3,2,1]
                          },
                          "version": 4
                        }')"#).await?;

    let r = service
        .exec_query("SELECT id, cardinality(hll) FROM s.Data")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![vec![TableValue::Int(1), TableValue::Int(18)]]
    );

    // Does not allow to import HLL in AirLift format.
    service.migration_hardcode_generic_err();
    service
        .exec_query("INSERT INTO s.Data(id, hll) VALUES(2, X'020C0200C02FF58941D5F0C6')")
        .await
        .unwrap_err();
    Ok(())
}

async fn hyperloglog_databricks(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.hlls(id int, hll HLL_DATASKETCHES)")
        .await?;

    service.exec_query("INSERT INTO s.hlls(id, hll) VALUES \
        (1, X'0201070c03000408067365047b65c3a608c39b17c29a0ac383c2b0380400000000000000000000000000000000'), \
        (2, X'0201070c03000408c39b17c29a0ac383c2b03804067365047b65c3a60800000000000000000000000000000000'), \
        (3, X'0301070c05000009140000000000000021c3b23905c2a1c38d490ac283c2b711071bc2a1c3961200000000000000000000000008c29bc39904497ac39908000000002bc3b2c3bb062c45670ac3adc29e24074bc298c2a6086f2c7f050000000000000000c392c295c2900dc3b3c28bc38106c38dc3884607c2b50dc3b70600000000c3b762c28207c398c393350f00000000000000001b27c2b20b00000000c29dc28a7210000000003fc3b95b0f')"
    ).await?;

    let r = service
        .exec_query("SELECT id, cardinality(hll) FROM s.hlls ORDER BY id")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(1, 4), (2, 4), (3, 20)]));
    Ok(())
}

async fn xirr(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    // XIRR result may differ between platforms, so we truncate the results with LEFT(_, 10).
    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, -10000.0 AS payment
            UNION ALL
            SELECT '2014-03-01'::date AS date, 2750.0 AS payment
            UNION ALL
            SELECT '2014-10-30'::date AS date, 4250.0 AS payment
            UNION ALL
            SELECT '2015-02-15'::date AS date, 3250.0 AS payment
            UNION ALL
            SELECT '2015-04-01'::date AS date, 2750.0 AS payment
        ) AS "t"
        "#,
        )
        .await?;

    assert_eq!(to_rows(&r), rows(&["0.37485859"]));

    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, -10000.0 AS payment
        ) AS "t"
        WHERE 0 = 1
        "#,
        )
        .await
        .unwrap_err();
    assert_eq!(r.elide_backtrace(), CubeError::internal("Execution error: A result for XIRR couldn't be determined because the arguments are empty".to_owned()));

    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, 10000.0 AS payment
        ) AS "t"
        "#,
        )
        .await
        .unwrap_err();
    assert_eq!(
        r.elide_backtrace(),
        CubeError::internal(
            "Execution error: The XIRR function couldn't find a solution".to_owned()
        )
    );

    // --- on_error testing ---

    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date, 0, NULL::double)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, -10000.0 AS payment
            UNION ALL
            SELECT '2014-03-01'::date AS date, 2750.0 AS payment
            UNION ALL
            SELECT '2014-10-30'::date AS date, 4250.0 AS payment
            UNION ALL
            SELECT '2015-02-15'::date AS date, 3250.0 AS payment
            UNION ALL
            SELECT '2015-04-01'::date AS date, 2750.0 AS payment
        ) AS "t"
        "#,
        )
        .await?;

    assert_eq!(to_rows(&r), rows(&["0.37485859"]));

    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date, 0, NULL::double)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, -10000.0 AS payment
        ) AS "t"
        WHERE 0 = 1
        "#,
        )
        .await
        .unwrap_err();
    assert_eq!(r.elide_backtrace(), CubeError::internal("Execution error: A result for XIRR couldn't be determined because the arguments are empty".to_owned()));

    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date, 0, NULL::double)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, 10000.0 AS payment
        ) AS "t"
        "#,
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[()]));

    let r = service
        .exec_query(
            r#"
        SELECT LEFT(XIRR(payment, date, 0, 12345)::varchar, 10) AS xirr
        FROM (
            SELECT '2014-01-01'::date AS date, 10000.0 AS payment
        ) AS "t"
        "#,
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&["12345.0"]));
    Ok(())
}

async fn aggregate_index_hll_databricks(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b int, a_hll HLL_DATASKETCHES)
                     AGGREGATIONS(merge(a_hll))
                     AGGREGATE INDEX aggr_index (a, b)
                     ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Orders (a, b, a_hll) VALUES \
                    (1, 10, X'0201070c03000408067365047b65c3a608c39b17c29a0ac383c2b0380400000000000000000000000000000000'), \
                    (1, 20, X'0201070c03000408067365047b65c3a608c39b17c29a0ac383c2b0380400000000000000000000000000000000'), \
                    (1, 10, X'0201070c03000408067365047b65c3a608c39b17c29a0ac383c2b0380400000000000000000000000000000000'), \
                    (1, 20, X'0201070c03000408067365047b65c3a608c39b17c29a0ac383c2b0380400000000000000000000000000000000')
           ",
        )
        .await
        ?;

    let res = service
        .exec_query("SELECT a, b, cardinality(merge(a_hll)) as hll FROM s.Orders GROUP BY 1, 2 ORDER BY 1, 2")
        .await
        ?;
    assert_eq!(
        to_rows(&res),
        [
            [TableValue::Int(1), TableValue::Int(10), TableValue::Int(4)],
            [TableValue::Int(1), TableValue::Int(20), TableValue::Int(4)],
        ]
    );

    let res = service
        .exec_query("SELECT a, cardinality(merge(a_hll)) as hll FROM s.Orders WHERE b = 20 GROUP BY 1 ORDER BY 1")
        .await
        ?;
    assert_eq!(to_rows(&res), [[TableValue::Int(1), TableValue::Int(4)],]);

    let res = service
        .exec_query(
            "SELECT a, cardinality(merge(a_hll)) as hll FROM s.Orders GROUP BY 1 ORDER BY 1",
        )
        .await?;
    assert_eq!(to_rows(&res), [[TableValue::Int(1), TableValue::Int(4)],]);
    Ok(())
}

async fn physical_plan_flags(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE PARTITIONED INDEX s.ind(url text, day text, category text)")
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Data(url text, day text, category text, hits int, clicks int) \
            ADD TO PARTITIONED INDEX s.ind(url, day, category)",
        )
        .await?;

    // (query, is_optimal)
    let cases = vec![
        ("SELECT SUM(hits) FROM s.Data", true),
        ("SELECT SUM(hits) FROM s.Data WHERE url = 'test'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url = 'test' AND day > 'test'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE day = 'test'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url = 'test' AND day = 'test'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url = 'test' AND category = 'test'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url = 'test' OR url = 'test_2'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url = 'test' OR category = 'test'", false),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test') OR (url = 'test' AND category = 'test')", false),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test') OR (url = 'test_1' OR url = 'test_2')", false),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test') OR (url = 'test_1' OR day = 'test_2')", false),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test') OR (url = 'test_1' OR day > 'test_2')", false),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test' AND category = 'test')", true),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test' AND category = 'test') OR (url = 'test_2' AND day = 'test_2' AND category = 'test_2')", true),
        ("SELECT SUM(hits) FROM s.Data WHERE (url = 'test' AND day = 'test' AND category = 'test') OR (url = 'test_2' AND day = 'test_2' AND category > 'test_2')", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url IN ('test_1', 'test_2')", false),
        ("SELECT SUM(hits) FROM s.Data WHERE url IS NOT NULL", false),
        ("SELECT SUM(hits), url FROM s.Data GROUP BY url", true),
        ("SELECT SUM(hits), url, day FROM s.Data GROUP BY url, day", true),
        ("SELECT SUM(hits), day FROM s.Data GROUP BY day", false),
        ("SELECT SUM(hits), day, category FROM s.Data GROUP BY day, category", false),
        ("SELECT SUM(hits), day, category FROM s.Data GROUP BY day, category", false)
    ];

    for (query, expected_optimal) in cases {
        let p = service.plan_query(query).await?;
        let flags = PhysicalPlanFlags::with_execution_plan(p.router.as_ref());
        assert_eq!(
            flags.is_suboptimal_query(),
            !expected_optimal,
            "Query failed: {}",
            query
        );
    }
    Ok(())
}

async fn planning_inplace_aggregate(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(url text, day int, hits int)")
        .await?;

    let p = service
        .plan_query("SELECT url, SUM(hits) FROM s.Data GROUP BY 1")
        .await?;
    let pp_opts = PPOptions {
        show_partitions: true,
        ..PPOptions::none()
    };
    assert_eq!(
        pp_phys_plan_ext(p.router.as_ref(), &pp_opts),
        "InlineFinalAggregate, partitions: 1\
        \n  ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &pp_opts),
        "InlineFinalAggregate, partitions: 1\
        \n  Worker, partitions: 1\
        \n    InlinePartialAggregate, partitions: 1\
        \n      Scan, index: default:1:[1]:sort_on[url], fields: [url, hits], partitions: 1\
        \n        Sort, partitions: 1\
        \n          Empty, partitions: 1"
    );

    // When there is no index, we fallback to inplace aggregates.
    let p = service
        .plan_query("SELECT day, SUM(hits) FROM s.Data GROUP BY 1")
        .await?;
    // TODO: Can we not have CoalescePartitions?  We don't want.
    assert_eq!(
        pp_phys_plan_ext(p.router.as_ref(), &pp_opts),
        "LinearFinalAggregate, partitions: 1\
        \n  CoalescePartitions, partitions: 1\
        \n    ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &pp_opts),
        "LinearFinalAggregate, partitions: 1\
        \n  CoalescePartitions, partitions: 1\
        \n    Worker, partitions: 1\
        \n      CoalescePartitions, partitions: 1\
        \n        LinearPartialAggregate, partitions: 1\
        \n          Scan, index: default:1:[1], fields: [day, hits], partitions: 1\
        \n            Empty, partitions: 1"
    );

    service
        .exec_query("CREATE TABLE s.DataBool(url text, segment boolean, day int, hits int)")
        .await?;

    let p = service
        .plan_query(
            "SELECT url, day, SUM(hits) FROM s.DataBool where segment = true  GROUP BY 1, 2",
        )
        .await?;
    let phys_plan = pp_phys_plan_ext(p.worker.as_ref(), &pp_opts);
    assert_eq!(
        phys_plan,
        "PartiallySortedFinalAggregate, partitions: 1\
        \n  Worker, partitions: 1\
        \n    PartiallySortedPartialAggregate, partitions: 1\
        \n      Filter, partitions: 1\
        \n        Scan, index: default:2:[2]:sort_on[url, segment, day], fields: *, partitions: 1\
        \n          Sort, partitions: 1\
        \n            Empty, partitions: 1"
    );
    let p = service
        .plan_query(
            "SELECT url, day, SUM(hits) FROM s.DataBool where segment = false  GROUP BY 1, 2",
        )
        .await?;
    let phys_plan = pp_phys_plan_ext(p.worker.as_ref(), &pp_opts);
    assert_eq!(
        phys_plan,
        "PartiallySortedFinalAggregate, partitions: 1\
        \n  Worker, partitions: 1\
        \n    PartiallySortedPartialAggregate, partitions: 1\
        \n      Filter, partitions: 1\
        \n        Scan, index: default:2:[2]:sort_on[url, segment, day], fields: *, partitions: 1\
        \n          Sort, partitions: 1\
        \n            Empty, partitions: 1"
    );
    Ok(())
}

async fn planning_topk_hash_aggregate(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(url text, day int, hits int)")
        .await?;
    service
        .exec_query("CREATE TABLE s.D3(a int, b int, c int, h int)")
        .await?;

    // GROUP BY a non-indexed column -> hash (Linear) partial aggregate; ORDER BY the group
    // column with a LIMIT -> the worker partial aggregate is replaced by GroupByLimitAggregate.
    let p = service
        .plan_query("SELECT day, SUM(hits) FROM s.Data GROUP BY 1 ORDER BY 1 LIMIT 10")
        .await?;
    let pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        pp.contains("GroupByLimitAggregate, k: 10, factor: 2,"),
        "expected GroupByLimitAggregate on the worker, got:\n{}",
        pp
    );

    // LIMIT + OFFSET -> k = limit + offset.
    let p = service
        .plan_query("SELECT day, SUM(hits) FROM s.Data GROUP BY 1 ORDER BY 1 LIMIT 10 OFFSET 5")
        .await?;
    let pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        pp.contains("GroupByLimitAggregate, k: 15, factor: 2,"),
        "expected k=15 (limit+offset), got:\n{}",
        pp
    );

    // ORDER BY an aggregate (not a group-by column) -> no trim.
    let p = service
        .plan_query("SELECT day, SUM(hits) FROM s.Data GROUP BY 1 ORDER BY 2 DESC LIMIT 10")
        .await?;
    let pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        !pp.contains("GroupByLimitAggregate"),
        "did not expect GroupByLimitAggregate when ordering by an aggregate, got:\n{}",
        pp
    );

    // No LIMIT -> no trim.
    let p = service
        .plan_query("SELECT day, SUM(hits) FROM s.Data GROUP BY 1 ORDER BY 1")
        .await?;
    let pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        !pp.contains("GroupByLimitAggregate"),
        "did not expect GroupByLimitAggregate without a limit, got:\n{}",
        pp
    );

    // ORDER BY a proper SUBSET of GROUP BY (b out of b, c). The worker cut and the router sort must
    // both use the total order T = [b, c]: the worker trim order carries the tie-break column c, and
    // the router's global Sort is extended with c so its top-k matches the global top-k by T.
    let p = service
        .plan_query("SELECT b, c, SUM(h) FROM s.D3 GROUP BY 1, 2 ORDER BY 1 LIMIT 3")
        .await?;
    let worker_pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        worker_pp.contains("GroupByLimitAggregate, k: 3, factor: 2,")
            && worker_pp.contains("(0, SortOptions { descending: false, nulls_first: false })")
            && worker_pp.contains("(1, SortOptions { descending: false, nulls_first: true })"),
        "expected worker trim order [b, c] totalized, got:\n{}",
        worker_pp
    );
    let router_pp = pp_phys_plan_ext(
        p.router.as_ref(),
        &PPOptions {
            show_sort_by: true,
            ..PPOptions::none()
        },
    );
    assert!(
        router_pp.contains("b@0") && router_pp.contains("c@1"),
        "expected router Sort extended with the tie-break column c, got:\n{}",
        router_pp
    );

    // Bare LIMIT (no ORDER BY) on a non-indexed group column: the limit can't ride the index, so the
    // worker still trims to the smallest groups by the full group key -- "any k" made deterministic.
    // The full group key is the total order, all columns ascending nulls-first.
    let p = service
        .plan_query("SELECT day, SUM(hits) FROM s.Data GROUP BY 1 LIMIT 10")
        .await?;
    let pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        pp.contains("GroupByLimitAggregate, k: 10, factor: 2,")
            && pp.contains("(0, SortOptions { descending: false, nulls_first: true })"),
        "expected GroupByLimitAggregate on a bare LIMIT, got:\n{}",
        pp
    );

    // UNION ALL + bare LIMIT: the per-branch trim descriptor must survive the cluster-send pull-up
    // over the union so the worker still trims above the union.
    service
        .exec_query("CREATE TABLE s.Data2(url text, day int, hits int)")
        .await?;
    let p = service
        .plan_query(
            "SELECT day, SUM(hits) FROM \
             (SELECT * FROM s.Data UNION ALL SELECT * FROM s.Data2) u GROUP BY 1 LIMIT 10",
        )
        .await?;
    let pp = pp_phys_plan_ext(p.worker.as_ref(), &PPOptions::none());
    assert!(
        pp.contains("GroupByLimitAggregate, k: 10, factor: 2,") && pp.contains("Union"),
        "expected GroupByLimitAggregate over the Union, got:\n{}",
        pp
    );

    Ok(())
}

async fn topk_hash_aggregate_trim(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(a int, b int, hits int)")
        .await?;
    // 12 distinct (a, b) groups, each with two rows so partial aggregation actually groups.
    // With k=3 and factor=2 the trim activates (g=12 > 6) but the result must match a full
    // top-k. ORDER BY a (a proper subset of GROUP BY a, b) exercises totalization: the worker
    // breaks ties on a by b so the router still receives every needed partial state.
    service
        .exec_query(
            "INSERT INTO s.Data(a, b, hits) VALUES \
             (1,1,10),(1,1,5),(1,2,1),(1,2,2),\
             (2,1,7),(2,1,3),(2,2,4),(2,2,6),\
             (3,1,8),(3,1,2),(3,2,9),(3,2,1),\
             (4,1,1),(4,1,1),(4,2,1),(4,2,1),\
             (5,1,1),(5,1,1),(5,2,1),(5,2,1),\
             (6,1,1),(6,1,1),(6,2,1),(6,2,1)",
        )
        .await?;

    // ORDER BY a, b LIMIT 3 (ascending): smallest three groups by (a, b).
    let r = service
        .exec_query("SELECT a, b, SUM(hits) FROM s.Data GROUP BY 1, 2 ORDER BY 1, 2 LIMIT 3")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(1, 1, 15), (1, 2, 3), (2, 1, 10)]));

    // ORDER BY a, b DESC LIMIT 3: largest three groups by (a, b).
    let r = service
        .exec_query(
            "SELECT a, b, SUM(hits) FROM s.Data GROUP BY 1, 2 ORDER BY 1 DESC, 2 DESC LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[(6, 2, 2), (6, 1, 2), (5, 2, 2)]));

    // ORDER BY a only (a proper subset of GROUP BY a, b), LIMIT 2. The selected group SET is
    // deterministic (both groups of a=1), but the intra-tie row order is not, so assert as a set.
    // Each returned group must carry its complete sum regardless of cross-worker tie-breaking,
    // which is what totalization (append b to the cut order) guarantees.
    let r = service
        .exec_query("SELECT a, b, SUM(hits) FROM s.Data GROUP BY 1, 2 ORDER BY 1 LIMIT 2")
        .await?;
    let got = to_rows(&r);
    assert_eq!(got.len(), 2, "expected 2 rows, got: {:?}", got);
    for expected in rows(&[(1, 1, 15), (1, 2, 3)]) {
        assert!(
            got.contains(&expected),
            "missing {:?} in {:?}",
            expected,
            got
        );
    }

    // Bare LIMIT 3 (no ORDER BY): the trim orders by the full group key, so "any 3" resolves to the
    // 3 smallest by (a, b). The result order is unspecified, but the group SET and each group's full
    // sum must be exact -- the latter guards against undercounting a group split across workers.
    let r = service
        .exec_query("SELECT a, b, SUM(hits) FROM s.Data GROUP BY 1, 2 LIMIT 3")
        .await?;
    let got = to_rows(&r);
    assert_eq!(got.len(), 3, "expected 3 rows, got: {:?}", got);
    for expected in rows(&[(1, 1, 15), (1, 2, 3), (2, 1, 10)]) {
        assert!(
            got.contains(&expected),
            "missing {:?} in {:?}",
            expected,
            got
        );
    }

    Ok(())
}

async fn planning_hints(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(id1 int, id2 int, id3 int)")
        .await?;

    let mut show_hints = PPOptions::default();
    show_hints.show_output_hints = true;

    // Merge produces a sort order because there is only single partition.
    let p = service.plan_query("SELECT id1, id2 FROM s.Data").await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Worker, sort_order: [0, 1]\
        \n  Scan, index: default:1:[1], fields: [id1, id2], sort_order: [0, 1]\
        \n    Sort, sort_order: [0, 1]\
        \n      Empty"
    );

    let p = service.plan_query("SELECT id2, id1 FROM s.Data").await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Worker, sort_order: [1, 0]\
        \n  Projection, [id2, id1], sort_order: [1, 0]\
        \n    Scan, index: default:1:[1], fields: [id1, id2], sort_order: [0, 1]\
        \n      Sort, sort_order: [0, 1]\
        \n        Empty"
    );

    // Unsorted when skips columns from sort prefix.
    let p = service.plan_query("SELECT id2, id3 FROM s.Data").await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "CoalescePartitions\
        \n  Worker\
        \n    CoalescePartitions\
        \n      Scan, index: default:1:[1], fields: [id2, id3]\
        \n        Empty"
    );

    // The prefix columns are still sorted.
    let p = service.plan_query("SELECT id1, id3 FROM s.Data").await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Worker, sort_order: [0]\
        \n  Scan, index: default:1:[1], fields: [id1, id3], sort_order: [0]\
        \n    Sort, sort_order: [0]\
        \n      Empty"
    );

    // Single value hints.
    let p = service
        .plan_query("SELECT id3, id2 FROM s.Data WHERE id2 = 234")
        .await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "CoalescePartitions, single_vals: [1]\
        \n  Worker, single_vals: [1]\
        \n    CoalescePartitions, single_vals: [1]\
        \n      Projection, [id3, id2], single_vals: [1]\
        \n        Filter, single_vals: [0]\
        \n          Scan, index: default:1:[1], fields: [id2, id3]\
        \n            Empty"
    );

    // Removing single value columns should keep the sort order of the rest.
    let p = service
        .plan_query("SELECT id3 FROM s.Data WHERE id1 = 123 AND id2 = 234")
        .await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Worker, sort_order: [0]\
        \n  Filter, sort_order: [0]\
        \n    Scan, index: default:1:[1]:sort_on[id1, id2], fields: *, sort_order: [0, 1, 2]\
        \n      Sort, sort_order: [0, 1, 2]\
        \n        Empty"
    );
    let p = service
        .plan_query("SELECT id1, id3 FROM s.Data WHERE id2 = 234")
        .await?;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Worker, sort_order: [0, 1]\
        \n  Filter, sort_order: [0, 1]\
        \n    Scan, index: default:1:[1], fields: *, sort_order: [0, 1, 2]\
        \n      Sort, sort_order: [0, 1, 2]\
        \n        Empty"
    );
    Ok(())
}

async fn planning_inplace_aggregate2(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Data1(allowed boolean, site_id int, url text, day timestamp, hits int)",
        )
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Data2(allowed boolean, site_id int, url text, day timestamp, hits int)",
        )
        .await?;

    let p = service
        .plan_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         WHERE (`allowed` = 'true') AND (`site_id` = '1') \
                               AND (`day` >= to_timestamp('2021-01-01T00:00:00.000') \
                                AND `day` <= to_timestamp('2021-01-02T23:59:59.999')) \
                         GROUP BY 1 \
                         ORDER BY 2 DESC NULLS LAST \
                         LIMIT 10",
        )
        .await?;

    let mut verbose = PPOptions::default();
    verbose.show_output_hints = true;
    verbose.show_sort_by = true;
    assert_eq!(
        pp_phys_plan_ext(p.router.as_ref(), &verbose),
        "Projection, [url, sum(Data.hits)@1:hits]\
           \n  AggregateTopK, limit: 10, sortBy: [2 desc nulls last]\
           \n    ClusterSend, partitions: [[1, 2]], sort_order: [1]"
    );
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &verbose),
        "Projection, [url, sum(Data.hits)@1:hits]\
           \n  AggregateTopK, limit: 10, sortBy: [2 desc nulls last]\
           \n    Worker, sort_order: [1]\
           \n      Sort, by: [sum(Data.hits)@1 desc nulls last], sort_order: [1]\
           \n        LinearSingleAggregate\
           \n          CoalescePartitions\
           \n            Union\
           \n              Filter\
           \n                Scan, index: default:1:[1]:sort_on[allowed, site_id, url], fields: *, sort_order: [0, 1, 2, 3, 4]\
           \n                  Sort, by: [allowed@0, site_id@1, url@2, day@3, hits@4], sort_order: [0, 1, 2, 3, 4]\
           \n                    Empty\
           \n              CoalescePartitions\
           \n                Filter\
           \n                  Scan, index: default:2:[2]:sort_on[allowed, site_id, url], fields: *, sort_order: [0, 1, 2, 3, 4]\
           \n                    Sort, by: [allowed@0, site_id@1, url@2, day@3, hits@4], sort_order: [0, 1, 2, 3, 4]\
           \n                      Empty"
    );
    Ok(())
}

async fn partitioned_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE PARTITIONED INDEX s.ind(id int, url text)")
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Data1(id int, url text, hits int) \
                     ADD TO PARTITIONED INDEX s.ind(id, url)",
        )
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Data2(id2 int, url2 text, location text) \
                     ADD TO PARTITIONED INDEX s.ind(id2, url2)",
        )
        .await?;

    service
        .exec_query(
            "INSERT INTO s.Data1(id, url, hits) VALUES (0, 'a', 10), (1, 'a', 20), (2, 'c', 30)",
        )
        .await?;
    service
        .exec_query("INSERT INTO s.Data2(id2, url2, location) VALUES (0, 'a', 'Mars'), (1, 'c', 'Earth'), (2, 'c', 'Moon')")
        .await
        ?;

    let r = service
        .exec_query(
            "SELECT id, url, hits, location \
                     FROM s.Data1 `l` JOIN s.Data2 `r` ON l.id = r.id2 AND l.url = r.url2 \
                     ORDER BY 1, 2",
        )
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[(0, "a", 10, "Mars"), (2, "c", 30, "Moon")])
    );
    Ok(())
}

async fn partitioned_index_if_not_exists(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE PARTITIONED INDEX s.ind(id int, url text)")
        .await?;
    service
        .exec_query("CREATE PARTITIONED INDEX s.ind(id int, url text)")
        .await
        .unwrap_err();
    service
        .exec_query("CREATE PARTITIONED INDEX IF NOT EXISTS s.ind(id int, url text)")
        .await?;

    service
        .exec_query("CREATE PARTITIONED INDEX IF NOT EXISTS s.other_ind(id int, url text)")
        .await?;
    Ok(())
}

async fn drop_partitioned_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE PARTITIONED INDEX s.ind(url text, some_column int)")
        .await?;
    // DROP without any data.
    service.exec_query("DROP PARTITIONED INDEX s.ind").await?;
    // Another drop fails as index does not exist.
    service
        .exec_query("DROP PARTITIONED INDEX s.ind")
        .await
        .unwrap_err();
    // Note columns are different.
    service
        .exec_query("CREATE PARTITIONED INDEX s.ind(id int, url text)")
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Data1(id int, url text, hits int) \
                     ADD TO PARTITIONED INDEX s.ind(id, url)",
        )
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Data2(id2 int, url2 text, location text) \
                     ADD TO PARTITIONED INDEX s.ind(id2, url2)",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Data1(id, url, hits) VALUES (0, 'a', 10), (1, 'a', 20), (2, 'c', 30)",
        )
        .await?;
    service
        .exec_query("INSERT INTO s.Data2(id2, url2, location) VALUES (0, 'a', 'Mars'), (1, 'c', 'Earth'), (2, 'c', 'Moon')")
        .await
        ?;

    let r = service
        .exec_query(
            "SELECT id, url, hits, location \
                     FROM s.Data1 `l` JOIN s.Data2 `r` ON l.id = r.id2 AND l.url = r.url2 \
                     ORDER BY 1, 2",
        )
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[(0, "a", 10, "Mars"), (2, "c", 30, "Moon")])
    );

    service.exec_query("DROP PARTITIONED INDEX s.ind").await?;
    service
        .exec_query(
            "CREATE TABLE s.Data3(id3 int, url3 text, location text) \
                     ADD TO PARTITIONED INDEX s.ind(id3, url3)",
        )
        .await
        .unwrap_err(); // Fails as the index does not exist anymore.

    // Query can still run from the default table data.
    let r = service
        .exec_query(
            "SELECT id, url, hits, location \
                     FROM s.Data1 `l` JOIN s.Data2 `r` ON l.id = r.id2 AND l.url = r.url2 \
                     ORDER BY 1, 2",
        )
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[(0, "a", 10, "Mars"), (2, "c", 30, "Moon")])
    );
    Ok(())
}

async fn topk_large_inputs(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits int)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits int)")
        .await?;

    const NUM_ROWS: i64 = 5 + MIN_TOPK_STREAM_ROWS as i64;

    let insert_data = |table, compute_hits: fn(i64) -> i64| {
        let service = &service;
        async move {
            let mut values = String::new();
            for i in 0..NUM_ROWS {
                if !values.is_empty() {
                    values += ", "
                }
                values += &format!("('url{}', {})", i, compute_hits(i));
            }
            service
                .exec_query(&format!(
                    "INSERT INTO s.{}(url, hits) VALUES {}",
                    table, values
                ))
                .await
                .unwrap();
        }
    };

    // Arrange so that top-k fully downloads both tables.
    insert_data("Data1", |i| i).await;
    insert_data("Data2", |i| NUM_ROWS - 2 * i).await;

    let query = "SELECT `url` `url`, SUM(`hits`) `hits` \
                     FROM (SELECT * FROM s.Data1 \
                           UNION ALL \
                           SELECT * FROM s.Data2) AS `Data` \
                     GROUP BY 1 \
                     ORDER BY 2 DESC \
                     LIMIT 10";

    let rows = service.exec_query(query).await?.get_rows().clone();
    assert_eq!(rows.len(), 10);
    for i in 0..10 {
        match &rows[i].values()[0] {
            TableValue::String(s) => assert_eq!(s, &format!("url{}", i)),
            v => panic!("invalid value in row {}: {:?}", i, v),
        }
        assert_eq!(
            rows[i].values()[1],
            TableValue::Int(NUM_ROWS - i as i64),
            "row {}",
            i
        );
    }
    Ok(())
}

async fn planning_simple(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Orders(id int, customer_id int, city text, amount int)")
        .await?;

    let p = service
        .plan_query("SELECT id, amount FROM s.Orders")
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Worker\
        \n  Scan, index: default:1:[1], fields: [id, amount]\
        \n    Sort\
        \n      Empty"
    );

    let p = service
        .plan_query("SELECT id, amount FROM s.Orders WHERE id > 10")
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Worker\
        \n  Filter\
        \n    Scan, index: default:1:[1], fields: [id, amount]\
        \n      Sort\
        \n        Empty"
    );

    let p = service
        .plan_query(
            "SELECT id, amount \
                 FROM s.Orders \
                 WHERE id > 10\
                 ORDER BY 2",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "Sort\
        \n  ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Sort\
        \n  Worker\
        \n    Filter\
        \n      Scan, index: default:1:[1], fields: [id, amount]\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query(
            "SELECT id, amount \
                 FROM s.Orders \
                 WHERE id > 10 \
                 LIMIT 10",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "GlobalLimit, n: 10\
        \n  ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "GlobalLimit, n: 10\
        \n  Worker\
        \n    Filter\
        \n      Scan, index: default:1:[1], fields: [id, amount]\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query(
            "SELECT id, SUM(amount) \
                                    FROM s.Orders \
                                    GROUP BY 1",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "InlineFinalAggregate\
        \n  ClusterSend, partitions: [[1]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Scan, index: default:1:[1]:sort_on[id], fields: [id, amount]\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query(
            "SELECT id, SUM(amount) \
                 FROM (SELECT * FROM s.Orders \
                       UNION ALL \
                       SELECT * FROM s.Orders)\
                 GROUP BY 1",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "InlineFinalAggregate\
        \n  ClusterSend, partitions: [[1, 1]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    MergeSort\
        \n      InlinePartialAggregate\
        \n        Union\
        \n          Scan, index: default:1:[1]:sort_on[id], fields: [id, amount]\
        \n            Sort\
        \n              Empty\
        \n          Scan, index: default:1:[1]:sort_on[id], fields: [id, amount]\
        \n            Sort\
        \n              Empty"
    );
    Ok(())
}

async fn planning_filter_index_selection(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Orders(a int, b int, c int, d int, amount int)")
        .await?;

    service
        .exec_query("CREATE INDEX cb ON s.Orders(c, b)")
        .await?;

    service
        .exec_query("CREATE INDEX bcd ON s.Orders(d, b, c)")
        .await?;

    let p = service
        .plan_query("SELECT b, SUM(amount) FROM s.Orders WHERE c = 5 GROUP BY 1")
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "InlineFinalAggregate\
        \n  ClusterSend, partitions: [[2]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Filter\
        \n        Scan, index: cb:2:[2]:sort_on[c, b], fields: [b, c, amount]\
        \n          Sort\
        \n            Empty"
    );

    let p = service
        .plan_query("SELECT b, SUM(amount) FROM s.Orders WHERE c in (5, 6) GROUP BY 1")
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "LinearFinalAggregate\
        \n  CoalescePartitions\
        \n    ClusterSend, partitions: [[2]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "LinearFinalAggregate\
        \n  CoalescePartitions\
        \n    Worker\
        \n      CoalescePartitions\
        \n        LinearPartialAggregate\
        \n          Filter\
        \n            Scan, index: cb:2:[2], fields: [b, c, amount]\
        \n              Sort\
        \n                Empty"
    );

    let p = service
        .plan_query(
            "SELECT b, SUM(amount) FROM s.Orders WHERE c = 5 and a > 5 and a < 10 GROUP BY 1",
        )
        .await?;

    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "InlineFinalAggregate\
        \n  ClusterSend, partitions: [[2]]"
    );

    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Filter\
        \n        Scan, index: cb:2:[2]:sort_on[c, b], fields: [a, b, c, amount]\
        \n          Sort\
        \n            Empty"
    );
    Ok(())
}

async fn planning_joins(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Orders(order_id int, customer_id int, amount int)")
        .await?;
    service
        .exec_query("CREATE INDEX by_customer ON s.Orders(customer_id)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Customers(customer_id int, customer_name text)")
        .await?;

    let p = service
        .plan_query(
            "SELECT order_id, customer_name \
                 FROM s.Orders `o`\
                 JOIN s.Customers `c` ON o.customer_id = c.customer_id",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "CoalescePartitions\
        \n  ClusterSend, partitions: [[2, 3]]"
    );
    assert_eq!(
            pp_phys_plan(p.worker.as_ref()),
            "CoalescePartitions\
            \n  Worker\
            \n    CoalescePartitions\
            \n      Projection, [order_id, customer_name]\
            \n        MergeJoin, on: [customer_id@1 = customer_id@0]\
            \n          Scan, index: by_customer:2:[2]:sort_on[customer_id], fields: [order_id, customer_id]\
            \n            Sort\
            \n              Empty\
            \n          Scan, index: default:3:[3]:sort_on[customer_id], fields: *\
            \n            Sort\
            \n              Empty"
        );

    let p = service
        .plan_query(
            "SELECT order_id, customer_name, SUM(amount) \
                                    FROM s.Orders `o` \
                                    JOIN s.Customers `c` ON o.customer_id = c.customer_id \
                                    GROUP BY 1, 2 \
                                    ORDER BY 3 DESC",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "Sort\
        \n  LinearFinalAggregate\
        \n    CoalescePartitions\
        \n      ClusterSend, partitions: [[2, 3]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Sort\
        \n  LinearFinalAggregate\
        \n    CoalescePartitions\
        \n      Worker\
        \n        CoalescePartitions\
        \n          LinearPartialAggregate\
        \n            Projection, [order_id, amount, customer_name]\
        \n              MergeJoin, on: [customer_id@1 = customer_id@0]\
        \n                Scan, index: by_customer:2:[2]:sort_on[customer_id], fields: *\
        \n                  Sort\
        \n                    Empty\
        \n                Scan, index: default:3:[3]:sort_on[customer_id], fields: *\
        \n                  Sort\
        \n                    Empty"
    );
    Ok(())
}

async fn planning_3_table_joins(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Orders(order_id int, customer_id int, product_id int, amount int)",
        )
        .await?;
    service
        .exec_query("CREATE INDEX by_customer ON s.Orders(customer_id)")
        .await?;
    service
        .exec_query("CREATE INDEX by_product_customer ON s.Orders(product_id, customer_id)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Customers(customer_id int, customer_name text)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Products(product_id int, product_name text)")
        .await?;

    let p = service
        .plan_query(
            "SELECT order_id, customer_name, product_name \
                 FROM s.Orders `o`\
                 JOIN s.Customers `c` ON o.customer_id = c.customer_id \
                 JOIN s.Products `p` ON o.product_id = p.product_id",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "CoalescePartitions\
        \n  ClusterSend, partitions: [[2, 4, 5]]"
    );
    assert_eq!(
            pp_phys_plan(p.worker.as_ref()),
            "CoalescePartitions\
            \n  Worker\
            \n    CoalescePartitions\
            \n      Projection, [order_id, customer_name, product_name]\
            \n        MergeJoin, on: [product_id@1 = product_id@0]\
            \n          Sort\
            \n            Projection, [order_id, product_id, customer_name]\
            \n              MergeJoin, on: [customer_id@1 = customer_id@0]\
            \n                Scan, index: by_customer:2:[2]:sort_on[customer_id], fields: [order_id, customer_id, product_id]\
            \n                  Sort\
            \n                    Empty\
            \n                Scan, index: default:4:[4]:sort_on[customer_id], fields: *\
            \n                  Sort\
            \n                    Empty\
            \n          Scan, index: default:5:[5]:sort_on[product_id], fields: *\
            \n            Sort\
            \n              Empty",
        );

    let p = service
        .plan_query(
            "SELECT order_id, customer_name, product_name \
                 FROM s.Orders `o`\
                 JOIN s.Customers `c` ON o.customer_id = c.customer_id \
                 JOIN s.Products `p` ON o.product_id = p.product_id \
                 WHERE p.product_id = 125",
        )
        .await?;

    // Check filter pushdown properly mirrors the filters on joins.
    let mut show_filters = PPOptions::default();
    show_filters.show_filters = true;
    assert_eq!(
            pp_phys_plan_ext(p.worker.as_ref(), &show_filters),
            "CoalescePartitions\
            \n  Worker\
            \n    CoalescePartitions\
            \n      Projection, [order_id, customer_name, product_name]\
            \n        MergeJoin, on: [product_id@1 = product_id@0]\
            \n          Projection, [order_id, product_id, customer_name]\
            \n            MergeJoin, on: [customer_id@1 = customer_id@0]\
            \n              Filter, predicate: product_id@2 = 125\
            \n                Scan, index: by_product_customer:3:[3]:sort_on[product_id, customer_id], fields: [order_id, customer_id, product_id], predicate: product_id = Int64(125)\
            \n                  Sort\
            \n                    Empty\
            \n              Scan, index: default:4:[4]:sort_on[customer_id], fields: *\
            \n                Sort\
            \n                  Empty\
            \n          Filter, predicate: product_id@0 = 125\
            \n            Scan, index: default:5:[5]:sort_on[product_id], fields: *, predicate: product_id = Int64(125)\
            \n              Sort\
            \n                Empty",
        );
    Ok(())
}

async fn planning_join_with_partitioned_index(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE PARTITIONED INDEX s.by_customer(customer_id int)")
        .await?;

    service
        .exec_query(
            "CREATE TABLE s.Orders(order_id int, customer_id int, product_id int, amount int) \
             ADD TO PARTITIONED INDEX s.by_customer(customer_id)",
        )
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Customers(customer_id int, customer_name text) \
             ADD TO PARTITIONED INDEX s.by_customer(customer_id)",
        )
        .await?;

    let p = service
        .plan_query(
            "SELECT order_id, customer_name \
                 FROM s.Orders `o`\
                 JOIN s.Customers `c` ON o.customer_id = c.customer_id",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.router.as_ref()),
        "CoalescePartitions\
        \n  ClusterSend, partitions: [[1, 3]]"
    );
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "CoalescePartitions\
        \n  Worker\
        \n    CoalescePartitions\
        \n      Projection, [order_id, customer_name]\
        \n        MergeJoin, on: [customer_id@1 = customer_id@0]\
        \n          Scan, index: #mi0:1:[1]:sort_on[customer_id], fields: [order_id, customer_id]\
        \n            Sort\
        \n              Empty\
        \n          Scan, index: #mi0:3:[3]:sort_on[customer_id], fields: *\
        \n            Sort\
        \n              Empty"
    );
    Ok(())
}

async fn topk_query(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits int)")
        .await?;
    service
            .exec_query("INSERT INTO s.Data1(url, hits) VALUES ('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('z', 100)")
            .await
            ?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits int)")
        .await?;
    service
            .exec_query("INSERT INTO s.Data2(url, hits) VALUES ('b', 50), ('c', 45), ('d', 40), ('e', 35), ('y', 80)")
            .await
            ?;

    // A typical top-k query.
    let r = service
        .exec_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("z", 100), ("y", 80), ("b", 52)]));

    // Same query, ascending order.
    let r = service
        .exec_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 ASC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("a", 1), ("e", 40), ("d", 44)]));

    // Min, descending.
    let r = service
        .exec_query(
            "SELECT `url` `url`, MIN(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("z", 100), ("y", 80), ("e", 5)]));

    // Min, ascending.
    let r = service
        .exec_query(
            "SELECT `url` `url`, MIN(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 ASC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("a", 1), ("b", 2), ("c", 3)]));

    // Max, descending.
    let r = service
        .exec_query(
            "SELECT `url` `url`, MAX(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("z", 100), ("y", 80), ("b", 50)]));

    // Max, ascending.
    let r = service
        .exec_query(
            "SELECT `url` `url`, MAX(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 ASC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("a", 1), ("e", 35), ("d", 40)]));
    Ok(())
}

async fn topk_having(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits int)")
        .await?;
    service
            .exec_query("INSERT INTO s.Data1(url, hits) VALUES ('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('z', 100)")
            .await
            ?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits int)")
        .await?;
    service
            .exec_query("INSERT INTO s.Data2(url, hits) VALUES ('b', 50), ('c', 45), ('d', 40), ('e', 35), ('y', 80)")
            .await
            ?;

    // A typical top-k query.
    let r = service
        .exec_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING SUM(`hits`) < 100 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("y", 80), ("b", 52), ("c", 48)]));

    let r = service
        .exec_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING MIN(`hits`) >= 3 AND SUM(`hits`) < 100 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("y", 80), ("c", 48), ("d", 44)]));

    service
        .exec_query("CREATE TABLE s.Data21(url text, hits int, hits_2 int)")
        .await?;
    service
            .exec_query("INSERT INTO s.Data21(url, hits, hits_2) VALUES  ('b', 5, 2), ('d', 3, 4), ('c', 4, 1),  ('e', 2, 10)")
            .await
            ?;
    service
        .exec_query("CREATE TABLE s.Data22(url text, hits int, hits_2 int)")
        .await?;
    service
            .exec_query("INSERT INTO s.Data22(url, hits, hits_2) VALUES ('b', 50, 3), ('c', 45, 12), ('d', 40, 10), ('e', 35, 5)")
            .await
            ?;

    let r = service
        .exec_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data21 \
                               UNION ALL \
                               SELECT * FROM s.Data22) AS `Data` \
                         GROUP BY 1 \
                         HAVING MAX(`hits_2`) < 11 \
                         ORDER BY 2 DESC \
                         LIMIT 2",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("b", 55), ("d", 43)]));
    Ok(())
}

async fn topk_decimals(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits decimal)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data1(url, hits) VALUES ('a', NULL), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('z', 100)")
        .await
        ?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits decimal)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data2(url, hits) VALUES ('b', 50), ('c', 45), ('d', 40), ('e', 35), ('y', 80), ('z', NULL)")
        .await
        ?;

    // A typical top-k query.
    let r = service
        .exec_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 DESC NULLS LAST \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[("z", dec5(100)), ("y", dec5(80)), ("b", dec5(52))])
    );
    Ok(())
}

async fn planning_topk_having(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits int, uhits HLL_POSTGRES)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits int, uhits HLL_POSTGRES)")
        .await?;
    let p = service
        .plan_query(
            "SELECT `url` `url`, SUM(`hits`) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING SUM(`hits`) > 10\
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    let mut show_hints = PPOptions::default();
    show_hints.show_filters = true;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Projection, [url, sum(Data.hits)@1:hits]\
        \n  AggregateTopK, limit: 3, having: sum(Data.hits)@1 > 10\
        \n    Worker\
        \n      Sort\
        \n        SortedSingleAggregate\
        \n          MergeSort\
        \n            Union\
        \n              Scan, index: default:1:[1]:sort_on[url], fields: [url, hits]\
        \n                Sort\
        \n                  Empty\
        \n              Scan, index: default:2:[2]:sort_on[url], fields: [url, hits]\
        \n                Sort\
        \n                  Empty"
    );

    let query = "SELECT `url` `url`, SUM(`hits`) `hits`, CARDINALITY(MERGE(`uhits`)) `uhits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING SUM(`hits`) > 10 AND CARDINALITY(MERGE(`uhits`)) > 5 \
                         ORDER BY 2 DESC \
                         LIMIT 3";
    let p = service.plan_query(query).await?;
    let mut show_hints = PPOptions::default();
    show_hints.show_filters = true;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Projection, [url, sum(Data.hits)@1:hits, cardinality(merge(Data.uhits)@2):uhits]\
        \n  AggregateTopK, limit: 3, having: sum(Data.hits)@1 > 10 AND cardinality(merge(Data.uhits)@2) > 5\
        \n    Worker\
        \n      Sort\
        \n        SortedSingleAggregate\
        \n          MergeSort\
        \n            Union\
        \n              Scan, index: default:1:[1]:sort_on[url], fields: *\
        \n                Sort\
        \n                  Empty\
        \n              Scan, index: default:2:[2]:sort_on[url], fields: *\
        \n                Sort\
        \n                  Empty"
        );
    // Checking execution because the column name MERGE(Data.uhits) in the top projection in the
    // above assertion seems incorrect, but the column number is correct.
    let result = service.exec_query(query).await?;
    assert_eq!(result.len(), 0);
    Ok(())
}

async fn planning_topk_hll(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits HLL_POSTGRES)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits HLL_POSTGRES)")
        .await?;
    // A typical top-k query.
    let p = service
        .plan_query(
            "SELECT `url` `url`, cardinality(merge(hits)) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    let mut show_hints = PPOptions::default();
    show_hints.show_filters = true;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Projection, [url, cardinality(merge(Data.hits)@1):hits]\
        \n  AggregateTopK, limit: 3\
        \n    Worker\
        \n      Sort\
        \n        SortedSingleAggregate\
        \n          MergeSort\
        \n            Union\
        \n              Scan, index: default:1:[1]:sort_on[url], fields: *\
        \n                Sort\
        \n                  Empty\
        \n              Scan, index: default:2:[2]:sort_on[url], fields: *\
        \n                Sort\
        \n                  Empty"
    );

    let p = service
        .plan_query(
            "SELECT `url` `url`, cardinality(merge(hits)) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING cardinality(merge(hits)) > 20 and cardinality(merge(hits)) < 40\
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    let mut show_hints = PPOptions::default();
    show_hints.show_filters = true;
    assert_eq!(
        pp_phys_plan_ext(p.worker.as_ref(), &show_hints),
        "Projection, [url, cardinality(merge(Data.hits)@1):hits]\
         \n  AggregateTopK, limit: 3, having: cardinality(merge(Data.hits)@1) > 20 AND cardinality(merge(Data.hits)@1) < 40\
         \n    Worker\
         \n      Sort\
         \n        SortedSingleAggregate\
         \n          MergeSort\
         \n            Union\
         \n              Scan, index: default:1:[1]:sort_on[url], fields: *\
         \n                Sort\
         \n                  Empty\
         \n              Scan, index: default:2:[2]:sort_on[url], fields: *\
         \n                Sort\
         \n                  Empty"
        );
    Ok(())
}

async fn topk_hll(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let hlls = ["X'118b7f'",
        "X'128b7fee22c470691a8134'",
        "X'138b7f04a10642078507c308e309230a420ac10c2510a2114511611363138116811848188218a119411a821ae11f0122e223a125a126632685276327a328e2296129e52b812fe23081320132c133e335a53641368236a23721374237e1382138e13a813c243e6140e341854304434148a24a034f8150c1520152e254e155a1564157e158e35ac25b265b615c615fc1620166a368226a416a626c016c816d677163728275817a637a817ac37b617c247c427d677f6180e18101826382e1846184e18541858287e1880189218a418b818bc38e018ea290a19244938295e4988198c299e29b239b419c419ce49da1a1e1a321a381a4c1aa61acc2ae01b0a1b101b142b161b443b801bd02bd61bf61c263c4a3c501c7a1caa1cb03cd03cf03cf42d123d4c3d662d744d901dd01df81e001e0a2e641e7e3edc1f0a2f1c1f203f484f5c4f763fc84fdc1fe02fea1'",
        "X'148b7f21083288a4320a12086719c65108c1088422884511063388232904418c8520484184862886528c65198832106328c83114e6214831108518d03208851948511884188441908119083388661842818c43190c320ce4210a50948221083084a421c8328c632104221c4120d01284e20902318ca5214641942319101294641906228483184e128c43188e308882204a538c8328903288642102220c64094631086330c832106320c46118443886329062118a230c63108a320c23204a11852419c6528c85210a318c6308c41088842086308ce7110a418864190650884210ca631064108642a1022186518c8509862109020a0a4318671144150842400e5090631a0811848320c821888120c81114a220880290622906310d0220c83090a118c433106128c221902210cc23106029044114841104409862190c43188111063104c310c6728c8618c62290441102310c23214440882438ca2110a32908548c432110329462188a43946328842114640944320884190c928c442084228863318a2190a318c6618ca3114651886618c44190c5108e2110612144319062284641908428882314862106419883310421988619ca420cc511442104633888218c4428465288651910730c81118821088218c6418c45108452106519ce410d841904218863308622086211483198c710c83104a328c620906218864118623086418c8711423094632186420c4620c41104620a441108e40882628c6311c212046428c8319021104672888428ca320c431984418c4209043084451886510c641108310c4c20c66188472146310ca71084820c621946218c8228822190e2410861904411c27288621144328c6440c6311063190813086228ca710c2218c4718865188c2114850888608864404a3194e22882310ce53088619ca31904519503188e1118c4214cb2948110c6119c2818c843108520c43188c5204821186528c871908311086214c630c4218c8418cc3298a31888210c63110a121042198622886531082098c419c4210c6210c8338c25294610944518c442104610884104424206310c8311462288873102308c2440c451082228824310440982220c4240c622084310c642850118c641148430d0128c8228c2120c221884428863208c21a0a4190a4404c21186548865204633906308ca32086211c8319ce22146520c6120803318a518c840084519461208c21908538cc428c2110844384e40906320c44014a3204e62042408c8328c632146318c812004310c41318e3208a5308a511827104a4188c51048421446090a7088631102231484104473084318c41210860906919083190652906129c4628c45310652848221443114420084500865184a618c81198c32906418c63190e320c231882728484184671888309465188a320c83208632144318c6331c642988108c61218812144328d022844021022184a31908328c6218c2328c4528cc541428190641046418c84108443146230c6419483214232184411863290a210824318c220868194631106618c43188821048230c4128c6310c0330462094241106330c42188c321043118863046438823110a041464108e3190e4209a11902439c43188631104321008090441106218c6419064294a229463594622244320cc71184510902924421908218c62308641044328ca328882111012884120ca52882428c62184442086718c4221c8211082208a321023115270086218c4218c6528ce400482310a520c43104a520c44210811884118c4310864198263942331822'"];
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits HLL_POSTGRES)")
        .await?;
    service
            .exec_query(
                &format!("INSERT INTO s.Data1(url, hits) VALUES ('a', {}), ('b', {}), ('c', {}), ('d', {}), ('k', {}) ",
                hlls[0], hlls[1], hlls[2], hlls[3], hlls[0]
            ))
            .await
            ?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits HLL_POSTGRES)")
        .await?;
    service
            .exec_query(
                &format!("INSERT INTO s.Data2(url, hits) VALUES ('b', {}), ('c', {}), ('e', {}), ('d', {}), ('h', {})",
                hlls[3], hlls[2], hlls[1], hlls[2], hlls[2]
                )
                )
            .await
            ?;

    // A typical top-k query.
    let r = service
        .exec_query(
            "SELECT `url` `url`, cardinality(merge(hits)) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("b", 9722), ("d", 9722), ("c", 171)]));

    let r = service
        .exec_query(
            "SELECT `url` `url`, cardinality(merge(hits)) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING cardinality(merge(hits)) < 9000
                         ORDER BY 2 DESC \
                         LIMIT 2",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("c", 171), ("h", 164)]));
    let r = service
        .exec_query(
            "SELECT `url` `url`, cardinality(merge(hits)) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         HAVING cardinality(merge(hits)) < 170 and cardinality(merge(hits)) > 160
                         ORDER BY 2 DESC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("h", 164)]));
    Ok(())
}

async fn topk_hll_with_nulls(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let hlls = ["X'118b7f'",
        "X'128b7fee22c470691a8134'",
        "X'138b7f04a10642078507c308e309230a420ac10c2510a2114511611363138116811848188218a119411a821ae11f0122e223a125a126632685276327a328e2296129e52b812fe23081320132c133e335a53641368236a23721374237e1382138e13a813c243e6140e341854304434148a24a034f8150c1520152e254e155a1564157e158e35ac25b265b615c615fc1620166a368226a416a626c016c816d677163728275817a637a817ac37b617c247c427d677f6180e18101826382e1846184e18541858287e1880189218a418b818bc38e018ea290a19244938295e4988198c299e29b239b419c419ce49da1a1e1a321a381a4c1aa61acc2ae01b0a1b101b142b161b443b801bd02bd61bf61c263c4a3c501c7a1caa1cb03cd03cf03cf42d123d4c3d662d744d901dd01df81e001e0a2e641e7e3edc1f0a2f1c1f203f484f5c4f763fc84fdc1fe02fea1'",
        "X'148b7f21083288a4320a12086719c65108c1088422884511063388232904418c8520484184862886528c65198832106328c83114e6214831108518d03208851948511884188441908119083388661842818c43190c320ce4210a50948221083084a421c8328c632104221c4120d01284e20902318ca5214641942319101294641906228483184e128c43188e308882204a538c8328903288642102220c64094631086330c832106320c46118443886329062118a230c63108a320c23204a11852419c6528c85210a318c6308c41088842086308ce7110a418864190650884210ca631064108642a1022186518c8509862109020a0a4318671144150842400e5090631a0811848320c821888120c81114a220880290622906310d0220c83090a118c433106128c221902210cc23106029044114841104409862190c43188111063104c310c6728c8618c62290441102310c23214440882438ca2110a32908548c432110329462188a43946328842114640944320884190c928c442084228863318a2190a318c6618ca3114651886618c44190c5108e2110612144319062284641908428882314862106419883310421988619ca420cc511442104633888218c4428465288651910730c81118821088218c6418c45108452106519ce410d841904218863308622086211483198c710c83104a328c620906218864118623086418c8711423094632186420c4620c41104620a441108e40882628c6311c212046428c8319021104672888428ca320c431984418c4209043084451886510c641108310c4c20c66188472146310ca71084820c621946218c8228822190e2410861904411c27288621144328c6440c6311063190813086228ca710c2218c4718865188c2114850888608864404a3194e22882310ce53088619ca31904519503188e1118c4214cb2948110c6119c2818c843108520c43188c5204821186528c871908311086214c630c4218c8418cc3298a31888210c63110a121042198622886531082098c419c4210c6210c8338c25294610944518c442104610884104424206310c8311462288873102308c2440c451082228824310440982220c4240c622084310c642850118c641148430d0128c8228c2120c221884428863208c21a0a4190a4404c21186548865204633906308ca32086211c8319ce22146520c6120803318a518c840084519461208c21908538cc428c2110844384e40906320c44014a3204e62042408c8328c632146318c812004310c41318e3208a5308a511827104a4188c51048421446090a7088631102231484104473084318c41210860906919083190652906129c4628c45310652848221443114420084500865184a618c81198c32906418c63190e320c231882728484184671888309465188a320c83208632144318c6331c642988108c61218812144328d022844021022184a31908328c6218c2328c4528cc541428190641046418c84108443146230c6419483214232184411863290a210824318c220868194631106618c43188821048230c4128c6310c0330462094241106330c42188c321043118863046438823110a041464108e3190e4209a11902439c43188631104321008090441106218c6419064294a229463594622244320cc71184510902924421908218c62308641044328ca328882111012884120ca52882428c62184442086718c4221c8211082208a321023115270086218c4218c6528ce400482310a520c43104a520c44210811884118c4310864198263942331822'"];
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(url text, hits HLL_POSTGRES)")
        .await?;
    service
            .exec_query(
                &format!("INSERT INTO s.Data1(url, hits) VALUES ('a', {}), ('b', {}), ('c', {}), ('d', {}), ('k', {}) ",
                "Null", hlls[1], hlls[2], hlls[3], hlls[3]
            ))
            .await
            ?;
    service
        .exec_query("CREATE TABLE s.Data2(url text, hits HLL_POSTGRES)")
        .await?;
    service
            .exec_query(
                &format!("INSERT INTO s.Data2(url, hits) VALUES ('b', {}), ('c', {}), ('e', {}), ('d', {}), ('h', {})",
                hlls[3], "Null", hlls[1], hlls[2], hlls[2]
                )
                )
            .await
            ?;

    // A typical top-k query.
    let r = service
        .exec_query(
            "SELECT `url` `url`, cardinality(merge(hits)) `hits` \
                         FROM (SELECT * FROM s.Data1 \
                               UNION ALL \
                               SELECT * FROM s.Data2) AS `Data` \
                         GROUP BY 1 \
                         ORDER BY 2 ASC \
                         LIMIT 3",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("a", 0), ("e", 1), ("c", 164)]));
    Ok(())
}

async fn offset(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service.exec_query("CREATE TABLE s.Data1(t text)").await?;
    service
        .exec_query("INSERT INTO s.Data1(t) VALUES ('a'), ('b'), ('c'), ('z')")
        .await?;
    service.exec_query("CREATE TABLE s.Data2(t text)").await?;
    service
        .exec_query("INSERT INTO s.Data2(t) VALUES ('f'), ('g'), ('h')")
        .await?;

    let r = service
        .exec_query(
            "SELECT t FROM (SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2)\
             ORDER BY 1",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&["a", "b", "c", "f", "g", "h", "z"]));
    let r = service
        .exec_query(
            "SELECT t FROM (SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2)\
             ORDER BY 1 \
             LIMIT 3 \
             OFFSET 2",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&["c", "f", "g"]));

    let r = service
        .exec_query(
            "SELECT t FROM (SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2)\
             ORDER BY 1 DESC \
             LIMIT 3 \
             OFFSET 1",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&["h", "g", "f"]));
    Ok(())
}

async fn having(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(id text, n int)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data1(id, n) VALUES ('a', 1), ('b', 2), ('c', 3)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Data2(id text, n int)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data2(id, n) VALUES ('a', 4), ('b', 5), ('c', 6)")
        .await?;

    let r = service
        .exec_query(
            "SELECT id, count(n) FROM s.Data1 \
             WHERE id != 'c' \
             GROUP BY 1 \
             HAVING 2 <= sum(n)",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("b", 1)]));

    let r = service
        .exec_query(
            "SELECT `data`.id, count(`data`.n) \
             FROM (SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2) `data` \
             WHERE n != 2 \
             GROUP BY 1 \
             HAVING sum(n) <= 5 \
             ORDER BY 1",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("a", 2), ("b", 1)]));

    // We diverge from datafusion here, which resolve `n` in the HAVING to `sum(n)` and fail.
    // At the moment CubeJS sends requests like this, though, so we choose to remove support for
    // filtering on aliases in the same query.
    let r = service
        .exec_query(
            "SELECT `data`.id, sum(n) AS n \
             FROM (SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2) `data` \
             GROUP BY 1 \
             HAVING sum(n) > 5 \
             ORDER BY 1",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[("b", 7), ("c", 9)]));
    // Since we do not resolve aliases, this will fail.
    let err = service
        .exec_query(
            "SELECT `data`.id, sum(n) AS n \
             FROM (SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2) `data` \
             GROUP BY 1 \
             HAVING n = 2 \
             ORDER BY 1",
        )
        .await;
    assert!(err.is_err());
    Ok(())
}

async fn rolling_window_join(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day timestamp, name text, n int)")
        .await?;
    let raw_query =
        "SELECT `Series`.date_from as `series__date_from`, name as `name`, sum(`Table`.n) as n FROM (\
               SELECT to_timestamp('2020-01-01T00:00:00.000') date_from, \
                      to_timestamp('2020-01-01T23:59:59.999') date_to \
               UNION ALL \
               SELECT to_timestamp('2020-01-02T00:00:00.000') date_from, \
                      to_timestamp('2020-01-02T23:59:59.999') date_to \
               UNION ALL \
               SELECT to_timestamp('2020-01-03T00:00:00.000') date_from, \
                      to_timestamp('2020-01-03T23:59:59.999') date_to \
               UNION ALL \
               SELECT to_timestamp('2020-01-04T00:00:00.000') date_from, \
                      to_timestamp('2020-01-04T23:59:59.999') date_to\
            ) AS `Series` \
            LEFT JOIN (\
               SELECT date_trunc('day', CONVERT_TZ(day,'+00:00')) `day`, name, sum(n) `n` \
               FROM s.Data \
               GROUP BY 1, 2 \
            ) AS `Table` ON `Table`.day <= `Series`.date_to \
            GROUP BY 1, 2";
    let query = raw_query.to_string() + " ORDER BY 1, 2, 3";
    let query_sort_subquery = format!(
        "SELECT q0.series__date_from, q0.name, q0.n FROM ({}) as q0 ORDER BY 1,2,3",
        raw_query
    );

    // let plan = service.plan_query(&query).await?.worker;
    // assert_eq!(
    //     pp_phys_plan(plan.as_ref()),
    //     "Sort\
    //   \n  Projection, [date_to, name, SUM(Table.n)@2:n]\
    //   \n    CrossJoinAgg, on: day@1 <= date_to@0\
    //   \n      Projection, [datetrunc(Utf8(\"day\"),converttz(s.Data.day,Utf8(\"+00:00\")))@0:day, name, SUM(s.Data.n)@2:n]\
    //   \n        FinalHashAggregate\
    //   \n          Worker\
    //   \n            PartialHashAggregate\
    //   \n              Merge\
    //   \n                Scan, index: default:1:[1], fields: *\
    //   \n                  Empty"
    // );
    //
    // let plan = service
    //     .plan_query(&query_sort_subquery)
    //     .await
    //     ?
    //     .worker;
    // assert_eq!(
    //     pp_phys_plan(plan.as_ref()),
    //     "Sort\
    //     \n  Projection, [date_to, name, n]\
    //     \n    Projection, [date_to, name, SUM(Table.n)@2:n]\
    //     \n      CrossJoinAgg, on: day@1 <= date_to@0\
    //     \n        Projection, [datetrunc(Utf8(\"day\"),converttz(s.Data.day,Utf8(\"+00:00\")))@0:day, name, SUM(s.Data.n)@2:n]\
    //     \n          FinalHashAggregate\
    //     \n            Worker\
    //     \n              PartialHashAggregate\
    //     \n                Merge\
    //     \n                  Scan, index: default:1:[1], fields: *\
    //     \n                    Empty"
    // );

    service
        .exec_query("INSERT INTO s.Data(day, name, n) VALUES ('2020-01-01T01:00:00.000', 'john', 10), \
                                                             ('2020-01-01T01:00:00.000', 'sara', 7), \
                                                             ('2020-01-03T02:00:00.000', 'sara', 3), \
                                                             ('2020-01-03T03:00:00.000', 'john', 9), \
                                                             ('2020-01-03T03:00:00.000', 'john', 11), \
                                                             ('2020-01-04T05:00:00.000', 'timmy', 5)")
        .await
        ?;

    let mut jan = (1..=4)
        .map(|d| timestamp_from_string(&format!("2020-01-{:02}T00:00:00.000", d)).unwrap())
        .collect_vec();
    jan.insert(0, jan[1]); // jan[i] will correspond to i-th day of the month.

    for q in &[query.as_str(), query_sort_subquery.as_str()] {
        log::info!("Testing query {}", q);
        let r = service.exec_query(q).await?;
        assert_eq!(
            to_rows(&r),
            rows(&[
                (jan[1], "john", 10),
                (jan[1], "sara", 7),
                (jan[2], "john", 10),
                (jan[2], "sara", 7),
                (jan[3], "john", 30),
                (jan[3], "sara", 10),
                (jan[4], "john", 30),
                (jan[4], "sara", 10),
                (jan[4], "timmy", 5)
            ])
        );
    }
    Ok(())
}

async fn rolling_window_query(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day int, name text, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Data(day, name, n) VALUES (1, 'john', 10), \
                                                     (1, 'sara', 7), \
                                                     (3, 'sara', 3), \
                                                     (3, 'john', 9), \
                                                     (3, 'john', 11), \
                                                     (5, 'timmy', 5)",
        )
        .await?;

    let r = service
        .exec_query(
            r#"SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000"#,
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 17), (2, 17), (3, 23), (4, 23), (5, 5)])
    );

    let r = service
        .exec_query(
            r#"SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        select
          1 date_from,
          2 date_to
        UNION ALL
        select
          2 date_from,
          3 date_to
        UNION ALL
        select
          3 date_from,
          4 date_to
        UNION ALL
        select
          4 date_from,
          5 date_to
        UNION ALL
        select
          4 date_from,
          5 date_to
        UNION ALL
        select
          5 date_from,
          6 date_to
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000"#,
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 17), (2, 17), (3, 23), (4, 23), (5, 5)])
    );

    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to`
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to` + 1
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 17), (2, 23), (3, 23), (4, 5), (5, 5)])
    );

    // Same, without preceding, i.e. with missing nodes.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to`
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, Some(17)),
            (2, None),
            (3, Some(23)),
            (4, None),
            (5, Some(5))
        ])
    );

    // Unbounded windows.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 17), (2, 17), (3, 40), (4, 40), (5, 45)]),
    );
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 45), (2, 28), (3, 28), (4, 5), (5, 5)])
    );
    let r = service
        .exec_query(
            "SELECT
      q_0.`orders__created_at_day`,
      `orders__rolling_number` `orders__rolling_number`
    FROM
      (
        SELECT
          `orders.created_at_series`.`date_from` `orders__created_at_day`,
          sum(`orders__rolling_number`) `orders__rolling_number`
        FROM
          (
            SELECT
              date_from as `date_from`,
              date_from + 1 AS `date_to`
            FROM (
                select unnest(generate_series(1, 5, 1))
            ) AS series(date_from)
          ) AS `orders.created_at_series`
          LEFT JOIN (
            SELECT
                day `orders__created_at_day`,
                SUM(n) `orders__rolling_number`
                FROM s.Data GROUP BY 1
          ) AS `orders_rolling_number_cumulative__base` ON 1 = 1
        GROUP BY
          1
      ) as q_0
    ORDER BY
      1 ASC
    LIMIT
      5000",
        )
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 45), (2, 45), (3, 45), (4, 45), (5, 45)])
    );
    // Combined windows.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to` + 1
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(1, 17), (2, 40), (3, 23), (4, 28), (5, 5)])
    );
    // Both bounds are either PRECEDING or FOLLOWING.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` + 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to` + 2
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, Some(23)),
            (2, Some(23)),
            (3, Some(5)),
            (4, Some(5)),
            (5, None)
        ])
    );
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` - 2
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to` - 1
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, None),
            (2, Some(17)),
            (3, Some(17)),
            (4, Some(23)),
            (5, Some(23))
        ])
    );
    // Empty inputs.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data
            WHERE day = 123123123
            GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to`
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(to_rows(&r), vec![] as Vec<Vec<_>>);

    // Broader range step than input data.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 4))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from` + 2
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(to_rows(&r), rows(&[(1, 40), (5, 5)]));

    // Dimension values not in the input data.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(-10, 10, 5))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from` + 2
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (-10, None),
            (-5, None),
            (0, Some(17)),
            (5, Some(5)),
            (10, None)
        ])
    );

    // Partition by clause.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  q_0.`orders__name`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders__name`,
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 2))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            name `orders__name`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1, 2
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 2
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1, 2
  ) as q_0
ORDER BY
  1, 2 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, "john", 10),
            (1, "sara", 7),
            (3, "john", 30),
            (3, "sara", 10),
            (5, "john", 20),
            (5, "sara", 3),
            (5, "timmy", 5)
        ])
    );

    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  q_0.`orders__name`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders__name`,
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 2))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            name `orders__name`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1, 2
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1, 2
  ) as q_0
ORDER BY
  1, 2 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, "john", 10),
            (1, "sara", 7),
            (3, "john", 20),
            (3, "sara", 3),
            (5, "timmy", 5)
        ])
    );

    // Missing dates must be filled.
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  q_0.`orders__name`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders__name`,
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            name `orders__name`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1, 2
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from`
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1, 2
  ) as q_0
ORDER BY
  1, 2 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, Some("john"), Some(10)),
            (1, Some("sara"), Some(7)),
            (2, None, None),
            (3, Some("john"), Some(20)),
            (3, Some("sara"), Some(3)),
            (4, None, None),
            (5, Some("timmy"), Some(5))
        ])
    );

    // TODO upgrade DF: it doesn't make sense to check for parsing errors here anymore.
    // TODO However it makes sense to check more edge cases of rolling window optimizer so it doesn't apply if it can't be.
    // Check for errors.
    // GROUP BY not allowed with ROLLING.
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data GROUP BY 1 ROLLING_WINDOW DIMENSION day FROM 0 TO 10 EVERY 2")
    //     .await
    //     .unwrap_err();
    // // Rolling aggregate without ROLLING_WINDOW.
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data")
    //     .await
    //     .unwrap_err();
    // // ROLLING_WINDOW without rolling aggregate.
    // service
    //     .exec_query("SELECT day, n FROM s.Data ROLLING_WINDOW DIMENSION day FROM 0 to 10 EVERY 2")
    //     .await
    //     .unwrap_err();
    // // No RANGE in rolling aggregate.
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n)) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 0 to 10 EVERY 2")
    //     .await
    //     .unwrap_err();
    // // No DIMENSION.
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW FROM 0 to 10 EVERY 2")
    //     .await
    //     .unwrap_err();
    // // Invalid DIMENSION.
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION unknown FROM 0 to 10 EVERY 2")
    //     .await
    //     .unwrap_err();
    // // Invalid types in FROM, TO, EVERY.
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 'a' to 10 EVERY 1")
    //     .await
    //     .unwrap_err();
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 0 to 'a' EVERY 1")
    //     .await
    //     .unwrap_err();
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 0 to 10 EVERY 'a'")
    //     .await
    //     .unwrap_err();
    // // Invalid values for FROM, TO, EVERY
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 0 to 10 EVERY 0")
    //     .await
    //     .unwrap_err();
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 0 to 10 EVERY -10")
    //     .await
    //     .unwrap_err();
    // service
    //     .exec_query("SELECT day, ROLLING(SUM(n) RANGE 2 PRECEDING) FROM s.Data ROLLING_WINDOW DIMENSION day FROM 10 to 0 EVERY 10")
    //     .await
    //     .unwrap_err();
    Ok(())
}

async fn rolling_window_no_aggregates(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day int, name text, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Data(day, name, n) VALUES (1, 'john', 10), \
                                                     (1, 'sara', 7), \
                                                     (3, 'sara', 3), \
                                                     (3, 'john', 9), \
                                                     (3, 'john', 11), \
                                                     (5, 'timmy', 5)",
        )
        .await?;

    // Regression test for the rolling optimizer (commit 9481045): a grouped
    // aggregate with no aggregate expressions — a `SELECT DISTINCT dim` key
    // generator over the time series, as emitted for a multi-rolling-measure
    // query — used to be rewritten into a RollingWindowAggregate with an empty
    // `rolling_aggs` list, which panicked in the executor. It must instead run as
    // a plain aggregate/range-join and return the series dimension keys.
    let r = service
        .exec_query(
            r#"SELECT
  q_0.`orders__created_at_day`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000"#,
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[1i64, 2, 3, 4, 5]));
    Ok(())
}

async fn plan_strings(service: &Box<dyn SqlClient>, query: &str) -> Result<String, CubeError> {
    let res = service
        .exec_query(&format!("EXPLAIN ANALYZE {}", query))
        .await?;
    Ok(res
        .get_rows()
        .iter()
        .flat_map(|r| r.values().iter())
        .filter_map(|v| match v {
            TableValue::String(s) => Some(s.as_str()),
            _ => None,
        })
        .collect::<Vec<_>>()
        .join("\n"))
}

/// Result assertions alone cannot tell an optimized plan from the fallback it degrades to, so
/// shapes whose whole point is whether a rewrite fires need to say so about the plan itself.
async fn assert_plan_contains(
    service: &Box<dyn SqlClient>,
    query: &str,
    expected: &str,
) -> Result<(), CubeError> {
    let plan = plan_strings(service, query).await?;
    if !plan.contains(expected) {
        return Err(CubeError::internal(format!(
            "`{}` not found in the plan for {}:\n{}",
            expected, query, plan
        )));
    }
    Ok(())
}

async fn assert_plan_omits(
    service: &Box<dyn SqlClient>,
    query: &str,
    unexpected: &str,
) -> Result<(), CubeError> {
    let plan = plan_strings(service, query).await?;
    if plan.contains(unexpected) {
        return Err(CubeError::internal(format!(
            "`{}` unexpectedly present in the plan for {}:\n{}",
            unexpected, query, plan
        )));
    }
    Ok(())
}

async fn rolling_window_unused_partition_by(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day int, name text, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Data(day, name, n) VALUES (1, 'john', 10), \
                                                     (1, 'sara', 7), \
                                                     (3, 'sara', 3), \
                                                     (3, 'john', 9), \
                                                     (3, 'john', 11), \
                                                     (5, 'timmy', 5)",
        )
        .await?;

    // A rolling window over a series of 7 points, grouped by a partition-by column. Bucket `d`
    // covers days `d - 1` and `d`, so day 7 has no rows at all and every partition key there is
    // null. `partition_expr` is how the source produces that column, `inner_partition` is how the
    // projection above the aggregate exposes it (empty when it only groups by it), and `outer` is
    // what the consumer selects.
    let query = |partition_expr: &str, inner_partition: &str, outer: &str| {
        format!(
            "SELECT {outer} FROM (
  SELECT `s0`.`date_from` `day`, {inner_partition} sum(`b`.`num`) `num`
  FROM (SELECT date_from `date_from`, date_from + 1 `date_to`
        FROM (select unnest(generate_series(1, 7, 1))) AS series(date_from)) `s0`
  LEFT JOIN (SELECT day `d`, {partition_expr} `win`, SUM(n) `num` FROM s.Data GROUP BY 1, 2) `b`
    ON `b`.`d` > `s0`.`date_to` - 1 AND `b`.`d` <= `s0`.`date_to`
  GROUP BY 1, `b`.`win`) `q`
ORDER BY 1 ASC, 2 ASC"
        )
    };

    // The rolling window always emits dimension + partition_by + rolling aggregates, so a GROUP BY
    // column the projection it replaces did not select reappears in its output. The rewrite must
    // not widen the schema of the node it replaces, or every ancestor keeps resolving its columns
    // against the old one and planning fails with `No field named ...`.
    let without_partition = rows(&[
        (1i64, Some(17i64)),
        (2, Some(17)),
        (3, Some(23)),
        (4, Some(23)),
        (5, Some(5)),
        (6, Some(5)),
        (7, None),
    ]);

    // A calc-group style constant: the source column is not nullable, but the empty bucket at day 7
    // still has to report a null key for it. The rewrite has to restore the projection's output on
    // top of the rolling node here, since `win` is grouped by but not selected.
    let pruned = query("'R12'", "`b`.`win` `win`,", "`q`.`day`, `q`.`num`");
    assert_plan_contains(&service, &pruned, "RollingWindowAgg").await?;
    let r = service.exec_query(&pruned).await?;
    assert_eq!(to_rows(&r), without_partition);

    // Same, but the projection omits the partition-by column outright instead of leaving it to
    // projection pruning.
    let r = service
        .exec_query(&query("'R12'", "", "`q`.`day`, `q`.`num`"))
        .await?;
    assert_eq!(to_rows(&r), without_partition);

    // Several partitions, all of them asked for: the rewrite outputs exactly what the projection
    // did, so nothing has to be restored on top of it.
    let selected = query(
        "name",
        "`b`.`win` `win`,",
        "`q`.`day`, `q`.`win`, `q`.`num`",
    );
    assert_plan_contains(&service, &selected, "RollingWindowAgg").await?;
    let r = service.exec_query(&selected).await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1i64, Some("john"), Some(10i64)),
            (1, Some("sara"), Some(7)),
            (2, Some("john"), Some(10)),
            (2, Some("sara"), Some(7)),
            (3, Some("john"), Some(20)),
            (3, Some("sara"), Some(3)),
            (4, Some("john"), Some(20)),
            (4, Some("sara"), Some(3)),
            (5, Some("timmy"), Some(5)),
            (6, Some("timmy"), Some(5)),
            (7, None, None),
        ])
    );

    // Several partitions, none of them asked for: the per-partition rows survive, which pins that
    // the restored projection picks the aggregate and not one of the partition keys.
    let r = service
        .exec_query(&query("name", "", "`q`.`day`, `q`.`num`"))
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1i64, Some(7i64)),
            (1, Some(10)),
            (2, Some(7)),
            (2, Some(10)),
            (3, Some(3)),
            (3, Some(20)),
            (4, Some(3)),
            (4, Some(20)),
            (5, Some(5)),
            (6, Some(5)),
            (7, None),
        ])
    );

    Ok(())
}

async fn rolling_window_two_aggregates(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day int, n int, m int)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data(day, n, m) VALUES (1, 10, 100), (3, 3, 300), (5, 5, 500)")
        .await?;

    // The rolling executor advances one group counter across all of its aggregates while each of
    // them accumulates into its own group space, so with two of them every aggregate past the
    // first reads slots the others left empty and answers null. Until that is fixed the rewrite
    // declines, and the query runs as a plain aggregate over the range join.
    let query = "SELECT `q`.`day`, `q`.`sn`, `q`.`sm` FROM (
  SELECT `s0`.`date_from` `day`, sum(`b`.`n`) `sn`, sum(`b`.`m`) `sm`
  FROM (SELECT date_from `date_from`, date_from + 1 `date_to`
        FROM (select unnest(generate_series(1, 5, 1))) AS series(date_from)) `s0`
  LEFT JOIN (SELECT day `d`, n `n`, m `m` FROM s.Data) `b`
    ON `b`.`d` > `s0`.`date_to` - 1 AND `b`.`d` <= `s0`.`date_to`
  GROUP BY 1) `q`
ORDER BY 1 ASC";
    assert_plan_omits(&service, query, "RollingWindowAgg").await?;
    let r = service.exec_query(query).await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1i64, None, None),
            (2, Some(3i64), Some(300i64)),
            (3, None, None),
            (4, Some(5), Some(500)),
            (5, None, None),
        ])
    );
    Ok(())
}

async fn rolling_window_exprs(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day int, n int)")
        .await?;
    service
        .exec_query("INSERT INTO s.data(day, n) VALUES(1, 10), (2, 20), (3, 30)")
        .await?;
    let r = service
        .exec_query(
            "SELECT
  `orders__rolling_number` / `orders__rolling_number_count`  `orders__rolling_number`,
  `orders__rolling_number_avg` `orders__rolling_number_avg`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      count(`orders__rolling_number`) `orders__rolling_number_count`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 3, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          n `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 3, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          n `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      avg(`orders__rolling_number`) `orders__rolling_number_avg`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 3, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          n `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_2 ON (
    q_1.`orders__created_at_day` = q_2.`orders__created_at_day`
    OR (
      q_1.`orders__created_at_day` IS NULL
      AND q_2.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(to_rows(&r), rows(&[(10, 10.), (15, 15.), (25, 25.)]));
    Ok(())
}

async fn rolling_window_query_timestamps(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day timestamp, name string, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(day, name, n)\
                        VALUES \
                         ('2021-01-01T00:00:00Z', 'john', 10), \
                         ('2021-01-01T00:00:00Z', 'sara', 7), \
                         ('2021-01-03T00:00:00Z', 'sara', 3), \
                         ('2021-01-03T00:00:00Z', 'john', 9), \
                         ('2021-01-03T00:00:00Z', 'john', 11), \
                         ('2021-01-05T00:00:00Z', 'timmy', 5)",
        )
        .await?;

    let mut jan = (1..=5)
        .map(|d| timestamp_from_string(&format!("2021-01-{:02}T00:00:00.000Z", d)).unwrap())
        .collect_vec();
    jan.insert(0, jan[1]); // jan[i] will correspond to i-th day of the month.

    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + INTERVAL '1 DAY' AS `date_to`
        FROM (
            select unnest(generate_series(to_timestamp('2021-01-01T00:00:00Z'), to_timestamp('2021-01-05T00:00:00Z'), INTERVAL '1 day'))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - INTERVAL '1 day'
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (jan[1], 17),
            (jan[2], 17),
            (jan[3], 23),
            (jan[4], 23),
            (jan[5], 5)
        ])
    );
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + INTERVAL '1 DAY' AS `date_to`
        FROM (
            select unnest(generate_series(to_timestamp('2021-01-01T00:00:00Z'), to_timestamp('2021-01-05T00:00:00Z'), INTERVAL '1 day'))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            SUM(n) `orders__rolling_number`
            FROM s.Data GROUP BY 1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_from`
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_from` + INTERVAL '1 day'
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (jan[1], 17),
            (jan[2], 23),
            (jan[3], 23),
            (jan[4], 5),
            (jan[5], 5)
        ])
    );
    Ok(())
}

async fn rolling_window_query_timestamps_exceeded(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day int, name string, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(day, name, n)\
                        VALUES \
                         (4, 'john', 10), \
                         (4, 'sara', 7), \
                         (5, 'sara', 3), \
                         (5, 'john', 9), \
                         (6, 'john', 11), \
                         (7, 'timmy', 5)",
        )
        .await?;

    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  q_0.`orders__name`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders__name`,
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(-5, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
            day `orders__created_at_day`,
            name `orders__name`,
            SUM(n) `orders__rolling_number`
            FROM s.data GROUP BY 1, 2
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1, 2
  ) as q_0
ORDER BY
  1, 2 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (-5, None, None),
            (-4, None, None),
            (-3, None, None),
            (-2, None, None),
            (-1, None, None),
            (0, None, None),
            (1, None, None),
            (2, None, None),
            (3, None, None),
            (4, Some("john"), Some(10)),
            (4, Some("sara"), Some(7)),
            (5, Some("john"), Some(19)),
            (5, Some("sara"), Some(10))
        ])
    );
    Ok(())
}
async fn rolling_window_extra_aggregate(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day int, name text, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Data(day, name, n) VALUES (1, 'john', 10), \
                                                     (1, 'sara', 7), \
                                                     (3, 'sara', 3), \
                                                     (3, 'john', 9), \
                                                     (3, 'john', 11), \
                                                     (5, 'timmy', 5)",
        )
        .await?;

    let r = service
        .exec_query(
            r#"SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`,
  `orders__number` `orders__number`
FROM
  (
    SELECT
      day `orders__created_at_day`,
      sum(n) `orders__number`
    FROM
      s.Data AS `main__orders__main`
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          sum(n) `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
        GROUP BY
          1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000"#,
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, 17, Some(17)),
            (2, 17, None),
            (3, 23, Some(23)),
            (4, 23, None),
            (5, 5, Some(5))
        ])
    );

    // We could also distribute differently.
    let r = service
        .exec_query(
            "SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`,
  `orders__number` `orders__number`
FROM
  (
    SELECT
      CASE WHEN day <= 3 THEN 1 ELSE 5 END `orders__created_at_day`,
      sum(n) `orders__number`
    FROM
      s.Data AS `main__orders__main`
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(1, 5, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          sum(n) `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
        GROUP BY
          1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (1, 17, Some(40)),
            (2, 17, None),
            (3, 23, None),
            (4, 23, None),
            (5, 5, Some(5))
        ])
    );

    // Putting everything into an out-of-range dimension.
    // TODO upgrade DF: incorrect test
    // let r = service
    //     .exec_query(
    //         "SELECT day, ROLLING(SUM(n) RANGE 1 PRECEDING), SUM(n) \
    //          FROM (SELECT day, SUM(n) as n FROM s.Data GROUP BY 1) \
    //          ROLLING_WINDOW DIMENSION day \
    //          GROUP BY DIMENSION 6 \
    //          FROM 1 TO 5 EVERY 1 \
    //          ORDER BY 1",
    //     )
    //     .await
    //     ?;
    // assert_eq!(
    //     to_rows(&r),
    //     rows(&[
    //         (1, 17, NULL),
    //         (2, 17, NULL),
    //         (3, 23, NULL),
    //         (4, 23, NULL),
    //         (5, 5, NULL)
    //     ])
    // );

    // TODO upgrade DF: it doesn't make sense to check for parsing errors here anymore.
    // Check errors.
    // Mismatched types.
    // service
    //     .exec_query(
    //         "SELECT day, ROLLING(SUM(n) RANGE 1 PRECEDING), SUM(n) \
    //          FROM (SELECT day, SUM(n) as n FROM s.Data GROUP BY 1) \
    //          ROLLING_WINDOW DIMENSION day \
    //          GROUP BY DIMENSION 'aaa' \
    //          FROM 1 TO 5 EVERY 1 \
    //          ORDER BY 1",
    //     )
    //     .await
    //     .unwrap_err();
    // // Aggregate without GROUP BY DIMENSION.
    // service
    //     .exec_query(
    //         "SELECT day, ROLLING(SUM(n) RANGE 1 PRECEDING), SUM(n) \
    //          FROM (SELECT day, SUM(n) as n FROM s.Data GROUP BY 1) \
    //          ROLLING_WINDOW DIMENSION day \
    //          FROM 1 TO 5 EVERY 1 \
    //          ORDER BY 1",
    //     )
    //     .await
    //     .unwrap_err();
    // // GROUP BY DIMENSION without aggregates.
    // service
    //     .exec_query(
    //         "SELECT day, ROLLING(SUM(n) RANGE 1 PRECEDING) \
    //          FROM (SELECT day, SUM(n) as n FROM s.Data GROUP BY 1) \
    //          ROLLING_WINDOW DIMENSION day \
    //          GROUP BY DIMENSION 0 \
    //          FROM 1 TO 5 EVERY 1 \
    //          ORDER BY 1",
    //     )
    //     .await
    //     .unwrap_err();
    Ok(())
}

async fn rolling_window_extra_aggregate_addon(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(day int, name text, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Data(day, name, n) VALUES (11, 'john', 10), \
                                                     (11, 'sara', 7), \
                                                     (13, 'sara', 3), \
                                                     (13, 'john', 9), \
                                                     (13, 'john', 11), \
                                                     (15, 'timmy', 5)",
        )
        .await?;

    let r = service
        .exec_query(
            "SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`,
  `orders__number` `orders__number`
FROM
  (
    SELECT
      day `orders__created_at_day`,
      sum(n) `orders__number`
    FROM
      s.Data AS `main__orders__main`
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(9, 15, 1))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          sum(n) `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
        GROUP BY
          1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (9, None, None),
            (10, None, None),
            (11, Some(17), Some(17)),
            (12, Some(17), None),
            (13, Some(23), Some(23)),
            (14, Some(23), None),
            (15, Some(5), Some(5))
        ])
    );
    Ok(())
}

async fn rolling_window_extra_aggregate_timestamps(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day timestamp, name string, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(day, name, n)\
                        VALUES \
                         ('2021-01-01T00:00:00Z', 'john', 10), \
                         ('2021-01-01T00:00:00Z', 'sara', 7), \
                         ('2021-01-03T00:00:00Z', 'sara', 3), \
                         ('2021-01-03T00:00:00Z', 'john', 9), \
                         ('2021-01-03T00:00:00Z', 'john', 11), \
                         ('2021-01-05T00:00:00Z', 'timmy', 5)",
        )
        .await?;

    let mut jan = (1..=5)
        .map(|d| timestamp_from_string(&format!("2021-01-{:02}T00:00:00.000Z", d)).unwrap())
        .collect_vec();
    jan.insert(0, jan[1]); // jan[i] will correspond to i-th day of the month.

    let r = service
        .exec_query(
            "SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`,
  `orders__number` `orders__number`
FROM
  (
    SELECT
      day `orders__created_at_day`,
      sum(n) `orders__number`
    FROM
      s.Data AS `main__orders__main`
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + INTERVAL '1 day' AS `date_to`
        FROM (
            select unnest(generate_series(date_trunc('day', to_timestamp('2021-01-01T00:00:00Z')), date_trunc('day', to_timestamp('2021-01-05T00:00:00Z')), INTERVAL '1 day'))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          sum(n) `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
        GROUP BY
          1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` >= `orders.created_at_series`.`date_from` - INTERVAL '1 day'
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (jan[1], 17, Some(17)),
            (jan[2], 17, None),
            (jan[3], 23, Some(23)),
            (jan[4], 23, None),
            (jan[5], 5, Some(5))
        ])
    );
    Ok(())
}

async fn rolling_window_one_week_interval(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day timestamp, name string, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(day, name, n)\
                        VALUES \
                         ('2021-01-01T00:00:00Z', 'john', 10), \
                         ('2021-01-01T00:00:00Z', 'sara', 7), \
                         ('2021-01-03T00:00:00Z', 'sara', 3), \
                         ('2021-01-03T00:00:00Z', 'john', 9), \
                         ('2021-01-03T00:00:00Z', 'john', 11), \
                         ('2021-01-05T00:00:00Z', 'timmy', 5)",
        )
        .await?;

    let mut jan = (1..=11)
        .map(|d| timestamp_from_string(&format!("2021-01-{:02}T00:00:00.000Z", d)).unwrap())
        .collect_vec();
    jan.insert(0, jan[1]); // jan[i] will correspond to i-th day of the month.

    let r = service
        .exec_query(
            "SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`,
  `orders__number` `orders__number`
FROM
  (
    SELECT
      date_trunc('week', day) `orders__created_at_day`,
      SUM(CASE WHEN day >= to_timestamp('2021-01-04T00:00:00Z') AND day < to_timestamp('2021-01-11T00:00:00Z') THEN n END) `orders__number`
    FROM
      s.Data AS `main__orders__main`
    WHERE
      day >= to_timestamp('2021-01-04T00:00:00Z') AND day < to_timestamp('2021-01-11T00:00:00Z')
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + INTERVAL '1 week' AS `date_to`
        FROM (
            select unnest(generate_series(date_trunc('week', to_timestamp('2021-01-04T00:00:00Z')), date_trunc('week', to_timestamp('2021-01-11T00:00:00Z')), INTERVAL '1 week'))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          sum(n) `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
        GROUP BY
          1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    println!("{:?}", to_rows(&r));
    assert_eq!(
        to_rows(&r),
        rows(&[(jan[4], 40, Some(5)), (jan[11], 45, None),])
    );
    Ok(())
}

async fn rolling_window_one_quarter_interval(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day timestamp, name string, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(day, name, n)\
                        VALUES \
                         ('2021-01-01T00:00:00Z', 'john', 10), \
                         ('2021-01-01T00:00:00Z', 'sara', 7), \
                         ('2021-01-03T00:00:00Z', 'sara', 3), \
                         ('2021-01-03T00:00:00Z', 'john', 9), \
                         ('2021-01-03T00:00:00Z', 'john', 11), \
                         ('2021-01-05T00:00:00Z', 'timmy', 5), \
                         ('2021-04-01T00:00:00Z', 'ovr', 5)",
        )
        .await?;

    let r = service
        .exec_query(
            "SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`,
  `orders__number` `orders__number`
FROM
  (
    SELECT
      date_trunc('quarter', day) `orders__created_at_day`,
      SUM(CASE WHEN day >= to_timestamp('2021-01-01T00:00:00Z') AND day < to_timestamp('2021-08-31T00:00:00Z') THEN n END) `orders__number`
    FROM
      s.Data AS `main__orders__main`
    WHERE
      day >= to_timestamp('2021-01-01T00:00:00Z') AND day < to_timestamp('2021-08-31T00:00:00Z')
    GROUP BY
      1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + INTERVAL '3 month' AS `date_to`
        FROM (
            select unnest(generate_series(date_trunc('quarter', to_timestamp('2021-01-04T00:00:00Z')), date_trunc('quarter', to_timestamp('2021-08-31T00:00:00Z')), INTERVAL '3 month'))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          sum(n) `orders__rolling_number`
        FROM
          s.Data AS `main__orders__main`
        GROUP BY
          1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_from`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            // 2021-01-01T00:00:00.000Z
            (TimestampValue::new(1609459200000000000), 17, Some(45)),
            // 2021-04-01T00:00:00.000Z
            (TimestampValue::new(1617235200000000000), 50, Some(5)),
            // 2021-07-01T00:00:00.000Z
            (TimestampValue::new(1625097600000000000), 50, None),
        ])
    );
    Ok(())
}

async fn rolling_window_offsets(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(day int, n int)")
        .await?;

    service
        .exec_query("INSERT INTO s.data(day, n) VALUES (1, 1), (2, 2), (3, 3), (5, 5), (9, 9)")
        .await?;
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(0, 10, 2))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          n `orders__rolling_number`
        FROM s.data
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[(0, 1), (2, 6), (4, 11), (6, 11), (8, 20), (10, 20)])
    );
    let r = service
        .exec_query(
            "SELECT
  q_0.`orders__created_at_day`,
  `orders__rolling_number` `orders__rolling_number`
FROM
  (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`orders__rolling_number`) `orders__rolling_number`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + 1 AS `date_to`
        FROM (
            select unnest(generate_series(0, 10, 2))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
          day `orders__created_at_day`,
          n `orders__rolling_number`
        FROM s.data
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` > `orders.created_at_series`.`date_to` - 1
      AND `orders_rolling_number_cumulative__base`.`orders__created_at_day` <= `orders.created_at_series`.`date_to` + 1
    GROUP BY
      1
  ) as q_0
ORDER BY
  1 ASC
LIMIT
  5000",
        )
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (0, Some(3)),
            (2, Some(5)),
            (4, Some(5)),
            (6, None),
            (8, Some(9)),
            (10, None)
        ])
    );
    Ok(())
}

async fn rolling_window_filtered(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.data(category text, day timestamp, count int, claimed_count int)",
        )
        .await?;

    service
        .exec_query(
            "INSERT INTO s.data(category, day, count, claimed_count)\
                        VALUES \
                         ('eth', '2023-12-12T00:00:00.000Z', 1, 1), \
                         ('github', '2023-12-08T00:00:00.000Z', 1, 1), \
                         ('github', '2023-12-06T00:00:00.000Z', 2, 2), \
                         ('starkex', '2023-12-05T00:00:00.000Z', 0, 0), \
                         ('starkex', '2023-12-07T00:00:00.000Z', 1, 1)",
        )
        .await?;

    let r = service
        .exec_query(
            r#"
                SELECT
  COALESCE(q_0.`orders__created_at_day`, q_1.`orders__created_at_day`) `orders__created_at_day`,
  `claimed_count` `claimed_count`,
  `count` `count`
FROM
  (
    SELECT
        `day` `orders__created_at_day`,
        sum(
            `count`
        ) `count`
        FROM
        (
            SELECT
            *
            FROM
            s.data
        ) AS `starknet_test_provisions__eth_cumulative`
        WHERE `starknet_test_provisions__eth_cumulative`.category = 'github'
        GROUP BY
        1
  ) as q_0
  FULL JOIN (
    SELECT
      `orders.created_at_series`.`date_from` `orders__created_at_day`,
      sum(`claimed_count`) `claimed_count`
    FROM
      (
        SELECT
          date_from as `date_from`,
          date_from + INTERVAL '1 day' AS `date_to`
        FROM (
            select unnest(generate_series(date_trunc('day', to_timestamp('2023-12-04T00:00:00.000')), date_trunc('day', to_timestamp('2023-12-10T13:41:12.000')), INTERVAL '1 day'))
        ) AS series(date_from)
      ) AS `orders.created_at_series`
      LEFT JOIN (
        SELECT
        `day` `orders__created_at_day`,
        sum(
          `claimed_count`
        ) `claimed_count`
        FROM
        (
            SELECT
            *
            FROM
            s.data
        ) AS `starknet_test_provisions__eth_cumulative`
        WHERE `starknet_test_provisions__eth_cumulative`.category = 'github'
        GROUP BY
        1
      ) AS `orders_rolling_number_cumulative__base` ON `orders_rolling_number_cumulative__base`.`orders__created_at_day` < `orders.created_at_series`.`date_to`
    GROUP BY
      1
  ) as q_1 ON (
    q_0.`orders__created_at_day` = q_1.`orders__created_at_day`
    OR (
      q_0.`orders__created_at_day` IS NULL
      AND q_1.`orders__created_at_day` IS NULL
    )
  )
ORDER BY
  1 ASC
LIMIT
  5000
            "#,
        )
        .await
        ?;
    let days = (4..=10)
        .map(|d| timestamp_from_string(&format!("2023-12-{:02}T00:00:00.000Z", d)).unwrap())
        .collect_vec();
    assert_eq!(
        to_rows(&r),
        rows(&[
            (days[0], None, None),
            (days[1], None, None),
            (days[2], Some(2), Some(2)),
            (days[3], Some(2), None),
            (days[4], Some(3), Some(1)),
            (days[5], Some(3), None),
            (days[6], Some(3), None),
        ])
    );
    Ok(())
}

async fn decimal_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(x decimal, y decimal)")
        .await?;
    service
        .exec_query("CREATE INDEX reverse on s.Data(y, x)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data(x,y) VALUES (1, 2), (2, 3), (3, 4)")
        .await?;

    let r = service
        .exec_query("SELECT * FROM s.Data ORDER BY x")
        .await?;

    assert_eq!(
        to_rows(&r),
        rows(&[(dec5(1), dec5(2)), (dec5(2), dec5(3)), (dec5(3), dec5(4))])
    );

    let r = service
        .exec_query("SELECT * FROM s.Data ORDER BY y DESC")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[(dec5(3), dec5(4)), (dec5(2), dec5(3)), (dec5(1), dec5(2))])
    );
    Ok(())
}

async fn decimal_order(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(i decimal, j decimal)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(i, j) VALUES (1.0, -1.0), (2.0, 0.5), (0.5, 1.0), (100, -25.5)",
        )
        .await?;

    let r = service
        .exec_query("SELECT i FROM s.data ORDER BY 1 DESC")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[dec5(100), dec5(2), dec5(1), dec5f1(0, 5)])
    );

    // Two and more columns use a different code path, so test these too.
    let r = service
        .exec_query("SELECT i, j FROM s.data ORDER BY 2, 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            (dec5(100), dec5f1(-25, 5)),
            (dec5(1), dec5(-1)),
            (dec5(2), dec5f1(0, 5)),
            (dec5f1(0, 5), dec5(1))
        ])
    );
    Ok(())
}

async fn float_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data(x float, y float)")
        .await?;
    service
        .exec_query("CREATE INDEX reverse on s.Data(y, x)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data(x,y) VALUES (1, 2), (2, 3), (3, 4)")
        .await?;

    let r = service
        .exec_query("SELECT * FROM s.Data ORDER BY x")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(1., 2.), (2., 3.), (3., 4.)]));

    let r = service
        .exec_query("SELECT * FROM s.Data ORDER BY y DESC")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(3., 4.), (2., 3.), (1., 2.)]));
    Ok(())
}

/// Ensure DataFusion code consistently uses IEEE754 total order for comparing floats.
async fn float_order(s: Box<dyn SqlClient>) -> Result<(), CubeError> {
    s.exec_query("CREATE SCHEMA s").await?;
    s.exec_query("CREATE TABLE s.data(f float, i int)").await?;
    s.exec_query("INSERT INTO s.data(f, i) VALUES (0., -1), (-0., 1), (-0., 2), (0., -2)")
        .await?;

    // Sorting one and multiple columns use different code paths in DataFusion. Test both.
    let r = s.exec_query("SELECT f FROM s.data ORDER BY f").await?;
    assert_eq!(to_rows(&r), rows(&[-0., -0., 0., 0.]));
    let r = s
        .exec_query("SELECT f, i FROM s.data ORDER BY f, i")
        .await?;
    assert_eq!(to_rows(&r), rows(&[(-0., 1), (-0., 2), (0., -2), (0., -1)]));

    // DataFusion compares grouping keys with a separate code path.
    let _r = s
        .exec_query("SELECT f, min(i), max(i) FROM s.data GROUP BY f ORDER BY f")
        .await?;
    //FIXME it should be fixed later for InlineAggregate assert_eq!(to_rows(&r), rows(&[(-0., 1, 2), (0., -2, -1)]));
    Ok(())
}

async fn date_add(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let check_fun = |name, t, i, expected| {
        let service = &service;
        async move {
            let expected = timestamp_from_string(expected)?;
            let actual = service
                .exec_query(&format!(
                    "SELECT {}(CAST('{}' as TIMESTAMP), INTERVAL '{}')",
                    name, t, i
                ))
                .await?;
            assert_eq!(to_rows(&actual), rows(&[expected]));
            Ok::<_, CubeError>(())
        }
    };
    let check_adds_to = |t, i, expected| check_fun("DATE_ADD", t, i, expected);
    let check_subs_to = |t, i, expected| check_fun("DATE_SUB", t, i, expected);

    check_adds_to("2021-01-01T00:00:00Z", "1 second", "2021-01-01T00:00:01Z").await?;
    check_adds_to("2021-01-01T00:00:00Z", "1 minute", "2021-01-01T00:01:00Z").await?;
    check_adds_to("2021-01-01T00:00:00Z", "1 hour", "2021-01-01T01:00:00Z").await?;
    check_adds_to("2021-01-01T00:00:00Z", "1 day", "2021-01-02T00:00:00Z").await?;

    check_adds_to(
        "2021-01-01T00:00:00Z",
        "1 day 1 hour 1 minute 1 second",
        "2021-01-02T01:01:01Z",
    )
    .await?;
    check_subs_to(
        "2021-01-02T01:01:01Z",
        "1 day 1 hour 1 minute 1 second",
        "2021-01-01T00:00:00Z",
    )
    .await?;

    check_adds_to("2021-01-01T00:00:00Z", "1 month", "2021-02-01T00:00:00Z").await?;

    check_adds_to("2021-01-01T00:00:00Z", "1 year", "2022-01-01T00:00:00Z").await?;
    check_subs_to("2022-01-01T00:00:00Z", "1 year", "2021-01-01T00:00:00Z").await?;

    check_adds_to("2021-01-01T00:00:00Z", "13 month", "2022-02-01T00:00:00Z").await?;
    check_subs_to("2022-02-01T00:00:00Z", "13 month", "2021-01-01T00:00:00Z").await?;

    check_adds_to("2021-01-01T23:59:00Z", "1 minute", "2021-01-02T00:00:00Z").await?;
    check_subs_to("2021-01-02T00:00:00Z", "1 minute", "2021-01-01T23:59:00Z").await?;

    check_adds_to("2021-12-01T00:00:00Z", "1 month", "2022-01-01T00:00:00Z").await?;
    check_subs_to("2022-01-01T00:00:00Z", "1 month", "2021-12-01T00:00:00Z").await?;

    check_adds_to("2021-12-31T00:00:00Z", "1 day", "2022-01-01T00:00:00Z").await?;
    check_subs_to("2022-01-01T00:00:00Z", "1 day", "2021-12-31T00:00:00Z").await?;

    // Feb 29 on leap and non-leap years.
    check_adds_to("2020-02-29T00:00:00Z", "1 day", "2020-03-01T00:00:00Z").await?;
    check_subs_to("2020-03-01T00:00:00Z", "1 day", "2020-02-29T00:00:00Z").await?;

    check_adds_to("2020-02-28T00:00:00Z", "1 day", "2020-02-29T00:00:00Z").await?;
    check_subs_to("2020-02-29T00:00:00Z", "1 day", "2020-02-28T00:00:00Z").await?;

    check_adds_to("2021-02-28T00:00:00Z", "1 day", "2021-03-01T00:00:00Z").await?;
    check_subs_to("2021-03-01T00:00:00Z", "1 day", "2021-02-28T00:00:00Z").await?;

    check_adds_to("2020-02-29T00:00:00Z", "1 year", "2021-02-28T00:00:00Z").await?;
    check_subs_to("2020-02-29T00:00:00Z", "1 year", "2019-02-28T00:00:00Z").await?;

    check_adds_to("2020-01-30T00:00:00Z", "1 month", "2020-02-29T00:00:00Z").await?;
    check_subs_to("2020-03-30T00:00:00Z", "1 month", "2020-02-29T00:00:00Z").await?;

    check_adds_to("2020-01-29T00:00:00Z", "1 month", "2020-02-29T00:00:00Z").await?;
    check_subs_to("2020-03-29T00:00:00Z", "1 month", "2020-02-29T00:00:00Z").await?;

    check_adds_to("2021-01-29T00:00:00Z", "1 month", "2021-02-28T00:00:00Z").await?;
    check_subs_to("2021-03-29T00:00:00Z", "1 month", "2021-02-28T00:00:00Z").await?;

    // Nulls.
    let r = service
        .exec_query(
            "SELECT date_add(CAST(NULL as timestamp), INTERVAL '1 month'), \
                            date_sub(CAST(NULL as timestamp), INTERVAL '3 month')",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[(NULL, NULL)]));

    // Invalid types passed to date_add.
    service
        .exec_query("SELECT date_add(1, 2)")
        .await
        .unwrap_err();
    service
        .exec_query("SELECT date_add(CAST('2021-01-01T00:00:00Z' as TIMESTAMP), 2)")
        .await
        .unwrap_err();
    service
        .exec_query("SELECT date_add(1, INTERVAL '1 second')")
        .await
        .unwrap_err();
    // Too many arguments.
    service
        .exec_query("SELECT date_add(1, 2, 3)")
        .await
        .unwrap_err();
    // Too few arguments
    service.exec_query("SELECT date_add(1)").await.unwrap_err();

    // Must work on columnar data.
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(t timestamp)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(t) VALUES ('2020-01-01T00:00:00Z'), ('2020-02-01T00:00:00Z'), (NULL)",
        )
        .await
        ?;
    let r = service
        .exec_query("SELECT date_add(t, INTERVAL '1 year') FROM s.data ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            Some(timestamp_from_string("2021-01-01T00:00:00Z")?),
            Some(timestamp_from_string("2021-02-01T00:00:00Z")?),
            None,
        ]),
    );
    let r = service
        .exec_query("SELECT date_add(t, INTERVAL '1 hour') FROM s.data ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            Some(timestamp_from_string("2020-01-01T01:00:00Z")?),
            Some(timestamp_from_string("2020-02-01T01:00:00Z")?),
            None,
        ]),
    );

    // Check we tolerate NOW(), perhaps with +00:00 time zone.
    let r = service
        .exec_query("SELECT NOW(), date_add(NOW(), INTERVAL '1 day')")
        .await?;
    let rows = to_rows(&r);
    assert_eq!(1, rows.len());
    assert_eq!(2, rows[0].len());
    match (&rows[0][0], &rows[0][1]) {
        (TableValue::Timestamp(tv), TableValue::Timestamp(day_later)) => {
            assert_eq!(
                day_later.get_time_stamp(),
                tv.get_time_stamp() + 86400i64 * 1_000_000_000
            );
        }
        _ => panic!("row has wrong types: {:?}", rows[0]),
    }
    Ok(())
}

async fn date_bin(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let check_fn = |interval, source, origin, expected| {
        let service = &service;
        async move {
            let expected = timestamp_from_string(expected)?;
            let actual = service
                .exec_query(&format!(
                    "SELECT DATE_BIN(INTERVAL '{}', CAST('{}' as TIMESTAMP), CAST('{}' as TIMESTAMP))",
                    interval, source, origin
                ))
                .await?;
            assert_eq!(to_rows(&actual), rows(&[expected]));
            Ok::<_, CubeError>(())
        }
    };

    // Common dates
    check_fn(
        "1 month",
        "2024-01-21T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2024-01-01T01:00:00Z",
    )
    .await?;
    check_fn(
        "1 month",
        "2023-11-21T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2023-11-01T01:00:00Z",
    )
    .await?;
    check_fn(
        "1 month",
        "2024-02-21T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2024-02-01T01:00:00Z",
    )
    .await?;
    check_fn(
        "2 month",
        "2024-04-25T01:00:00Z",
        "2024-01-20T01:00:00Z",
        "2024-03-20T01:00:00Z",
    )
    .await?;
    check_fn(
        "2 month",
        "2024-04-15T01:00:00Z",
        "2024-01-20T01:00:00Z",
        "2024-03-20T01:00:00Z",
    )
    .await?;
    check_fn(
        "2 month",
        "2024-05-25T01:00:00Z",
        "2024-01-20T01:00:00Z",
        "2024-05-20T01:00:00Z",
    )
    .await?;
    check_fn(
        "2 month",
        "2024-05-15T01:00:00Z",
        "2024-01-20T01:00:00Z",
        "2024-03-20T01:00:00Z",
    )
    .await?;
    check_fn(
        "2 month",
        "2023-11-25T01:00:00Z",
        "2024-01-20T01:00:00Z",
        "2023-11-20T01:00:00Z",
    )
    .await?;
    check_fn(
        "2 month",
        "2023-11-15T01:00:00Z",
        "2024-01-20T01:00:00Z",
        "2023-09-20T01:00:00Z",
    )
    .await?;
    check_fn(
        "10 day",
        "2024-01-25T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2024-01-21T01:00:00Z",
    )
    .await?;
    check_fn(
        "10 day 2 hour 5 minute 10 second",
        "2024-01-15T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2024-01-11T03:05:10.000Z",
    )
    .await?;
    check_fn(
        "10 day 2 hour 5 minute 10 second",
        "2024-01-30T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2024-01-21T05:10:20.000Z",
    )
    .await?;
    check_fn(
        "10 day 2 hour 5 minute 10 second",
        "2023-12-30T01:00:00Z",
        "2024-01-01T01:00:00Z",
        "2023-12-21T22:54:50.000Z",
    )
    .await?;

    // Nulls
    let r = service
        .exec_query(
            "SELECT DATE_BIN(INTERVAL '1 month', CAST(NULL as timestamp), CAST('2023-12-30T01:00:00Z' AS timestamp))",
        )
        .await
        ?;
    assert_eq!(to_rows(&r), rows(&[(NULL)]));

    // Invalid number of args
    service.exec_query("SELECT DATE_BIN(1)").await.unwrap_err();
    service
        .exec_query("SELECT DATE_BIN(1, 2)")
        .await
        .unwrap_err();
    service
        .exec_query("SELECT DATE_BIN(1, 2, 3, 4, 5)")
        .await
        .unwrap_err();

    // Invalid types
    service
        .exec_query("SELECT DATE_BIN(NULL, CAST('2023-12-30T01:00:00Z' AS timestamp), CAST('2023-12-30T01:00:00Z' AS timestamp))")
        .await
        .unwrap_err();
    service
        .exec_query(
            "SELECT DATE_BIN(INTERVAL '1 month', 1, CAST('2023-12-30T01:00:00Z' AS timestamp))",
        )
        .await
        .unwrap_err();
    service
        .exec_query(
            "SELECT DATE_BIN(INTERVAL '1 month', CAST('2023-12-30T01:00:00Z' AS timestamp), true)",
        )
        .await
        .unwrap_err();

    // Columnar data
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(t timestamp)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(t) VALUES ('2024-01-21T01:00:00Z'), ('2023-11-21T01:00:00Z'), ('2024-02-21T01:00:00Z'), (NULL)",
        )
        .await
        ?;
    let r = service
        .exec_query("SELECT DATE_BIN(INTERVAL '1 month', t, CAST('2024-01-01T01:00:00Z' AS timestamp)) FROM s.data ORDER BY 1")
        .await
        ?;
    assert_eq!(
        to_rows(&r),
        rows(&[
            Some(timestamp_from_string("2023-11-01T01:00:00Z")?),
            Some(timestamp_from_string("2024-01-01T01:00:00Z")?),
            Some(timestamp_from_string("2024-02-01T01:00:00Z")?),
            None,
        ]),
    );
    Ok(())
}

async fn unsorted_merge_assertion(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1(x int, y int)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data1(x,y) VALUES (1, 4), (2, 3), (3, 2)")
        .await?;

    service
        .exec_query("CREATE TABLE s.Data2(x int, y int)")
        .await?;
    service
        .exec_query("INSERT INTO s.Data2(x,y) VALUES (1, 4), (2, 3), (3, 2)")
        .await?;

    let r = service
        .exec_query(
            "SELECT x, y, count(x) \
             FROM (SELECT * FROM s.Data1 UNION ALL \
                   SELECT * FROM s.Data2)\
             GROUP BY y, x \
             ORDER BY y, x",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[(3, 2, 2), (2, 3, 2), (1, 4, 2)]));
    Ok(())
}

async fn unsorted_data_timestamps(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.data(t timestamp, n string)")
        .await?;
    service
        .exec_query(
            "INSERT INTO s.data(t, n) VALUES \
            ('2020-01-01T00:00:00.000000005Z', 'a'), \
            ('2020-01-01T00:00:00.000000001Z', 'b'), \
            ('2020-01-01T00:00:00.000000002Z', 'c')",
        )
        .await?;

    // CubeStore currently truncs timestamps to millisecond precision.
    // This checks we sort trunced precisions on inserts. We rely on implementation details of
    // CubeStore here.
    let r = service.exec_query("SELECT t, n FROM s.data").await?;

    let t = timestamp_from_string("2020-01-01T00:00:00Z")?;
    assert_eq!(to_rows(&r), rows(&[(t, "a"), (t, "b"), (t, "c")]));

    // This ends up using MergeSortExec, make sure we see no assertions.
    let r = service
        .exec_query(
            "SELECT t, n FROM (SELECT * FROM s.data UNION ALL SELECT * FROM s.data) data \
        GROUP BY 1, 2 \
        ORDER BY 1, 2",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[(t, "a"), (t, "b"), (t, "c")]));
    Ok(())
}

async fn now(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    // This is no longer a UDF, so we're just testing DataFusion.
    let r = service.exec_query("SELECT now()").await?;
    assert_eq!(r.get_rows().len(), 1);
    assert_eq!(r.get_rows()[0].values().len(), 1);
    match &r.get_rows()[0].values()[0] {
        TableValue::Timestamp(_) => {} // all ok.
        v => panic!("not a timestamp: {:?}", v),
    }

    service.exec_query("CREATE SCHEMA s").await?;
    service.exec_query("CREATE TABLE s.Data(i int)").await?;
    service
        .exec_query("INSERT INTO s.Data(i) VALUES (1), (2), (3)")
        .await?;

    let r = service.exec_query("SELECT i, now() FROM s.Data").await?;
    assert_eq!(r.len(), 3);
    let mut seen = None;
    for r in r.get_rows() {
        match &r.values()[1] {
            TableValue::Timestamp(v) => match &seen {
                None => seen = Some(v),
                Some(seen) => assert_eq!(seen, &v),
            },
            v => panic!("not a timestamp: {:?}", v),
        }
    }

    let r = service
        .exec_query("SELECT i, now() FROM s.Data WHERE now() = now()")
        .await?;
    assert_eq!(r.len(), 3);

    let r = service.exec_query("SELECT now(), unix_timestamp()").await?;
    match r.get_rows()[0].values().as_slice() {
        &[TableValue::Timestamp(v), TableValue::Int(t)] => {
            assert_eq!(v.get_time_stamp() / 1_000_000_000, t)
        }
        _ => panic!("unexpected values: {:?}", r.get_rows()[0]),
    }
    Ok(())
}

async fn dump(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let r = service.exec_query("DUMP SELECT 1").await?;
    let dump_dir = match &r.get_rows()[0].values()[0] {
        TableValue::String(d) => d,
        _ => panic!("invalid result"),
    };

    assert!(tokio::fs::metadata(dump_dir).await?.is_dir());
    assert!(
        tokio::fs::metadata(Path::new(dump_dir).join("metastore-backup"))
            .await?
            .is_dir()
    );
    Ok(())
}

#[allow(dead_code)]
async fn ksql_simple(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let vars = env::var("TEST_KSQL_USER").and_then(|user| {
        env::var("TEST_KSQL_PASS")
            .and_then(|pass| env::var("TEST_KSQL_URL").map(|url| (user, pass, url)))
    });
    if let Ok((user, pass, url)) = vars {
        service
            .exec_query(&format!("CREATE SOURCE OR UPDATE ksql AS 'ksql' VALUES (user = '{}', password = '{}', url = '{}')", user, pass, url))
            .await
            ?;

        service.exec_query("CREATE SCHEMA test").await?;
        service.exec_query("CREATE TABLE test.events_by_type (`EVENT` text, `KSQL_COL_0` int) unique key (`EVENT`) location 'stream://ksql/EVENTS_BY_TYPE'").await?;
        for _ in 0..100 {
            let res = service
                .exec_query(
                    "SELECT * FROM test.events_by_type WHERE `EVENT` = 'load_request_success'",
                )
                .await?;
            if res.len() == 0 {
                tokio::time::sleep(Duration::from_millis(100)).await;
                continue;
            }
            if res.len() == 1 {
                return Ok(());
            }
        }
        panic!("Can't load data from ksql");
    }
    Ok(())
}

async fn dimension_only_queries_for_stream_table(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA test").await?;
    service.exec_query("CREATE TABLE test.events_by_type (foo text, bar timestamp, bar_id text, measure1 int) unique key (foo, bar, bar_id)").await?;
    for i in 0..2 {
        for j in 0..2 {
            service
                .exec_query(&format!("INSERT INTO test.events_by_type (foo, bar, bar_id, measure1, __seq) VALUES ('a', '2021-01-01T00:00:00.000', '{}', {}, {})", i, j, i * 10 + j))
                .await
                ?;
        }
    }
    let r = service
        .exec_query(
            "SELECT `bar_id` `bar_id` FROM test.events_by_type as `events` GROUP BY 1 ORDER BY 1 LIMIT 100",
        )
        .await
        ?;

    assert_eq!(to_rows(&r), rows(&[("0"), ("1")]));
    Ok(())
}

async fn unique_key_and_multi_measures_for_stream_table(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA test").await?;
    service.exec_query("CREATE TABLE test.events_by_type (foo text, bar timestamp, bar_id text, measure1 int, measure2 text) unique key (foo, bar, bar_id)").await?;
    for i in 0..2 {
        for j in 0..2 {
            service
                .exec_query(&format!("INSERT INTO test.events_by_type (foo, bar, bar_id, measure1, measure2, __seq) VALUES ('a', '2021-01-01T00:00:00.000', '{}', {}, '{}', {})", i, j, "text_value", i * 10 + j))
                .await
                ?;
        }
    }
    let r = service
        .exec_query(
            "SELECT bar_id, measure1, measure2 FROM test.events_by_type as `events` LIMIT 100",
        )
        .await?;

    assert_eq!(
        to_rows(&r),
        rows(&[("0", 1, "text_value"), ("1", 1, "text_value")])
    );
    Ok(())
}

async fn unique_key_and_multi_partitions(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA test").await?;
    service.exec_query("CREATE TABLE test.unique_parts1 (a int, b int, c int, e int, val int) unique key (a, b, c, e) ").await?;
    service.exec_query("CREATE TABLE test.unique_parts2 (a int, b int, c int, e int, val int) unique key (a, b, c, e) ").await?;
    service
        .exec_query(
            "
            INSERT INTO test.unique_parts1
            (a, b, c, e, val, __seq)
            VALUES
            (1, 1, 1, 1, 10, 1),
            (2, 2, 2, 2, 20, 2)
            ",
        )
        .await?;

    service
        .exec_query(
            "
            INSERT INTO test.unique_parts1
            (a, b, c, e, val, __seq)
            VALUES
            (11, 11, 11, 11, 110, 11),
            (22, 22, 22, 22, 220, 21)
            ",
        )
        .await?;

    service
        .exec_query(
            "
            INSERT INTO test.unique_parts2
            (a, b, c, e, val, __seq)
            VALUES
            (3, 3, 3, 3, 30, 3),
            (4, 4, 4, 4, 40, 4)
            ",
        )
        .await?;

    let query = "SELECT a, b FROM (
                    SELECT * FROM test.unique_parts1
                    UNION ALL
                    SELECT * FROM test.unique_parts2
                ) `tt` GROUP BY 1, 2 ORDER BY 1, 2 LIMIT 100";

    let r = service.exec_query(query).await?;

    assert_eq!(
        to_rows(&r),
        rows(&[(1, 1), (2, 2), (3, 3), (4, 4), (11, 11), (22, 22)])
    );

    let test_multiple_partitions = match service.prefix() {
        "cluster" => true,
        "in_process" => false,
        "multi_process" => false,
        "migration" => true,
        _ => false,
    };

    // Assert that we get a MergeSort node when there are multiple partitions.
    if test_multiple_partitions {
        let plan = service.plan_query(query).await?;

        assert_eq!(
            pp_phys_plan_ext(
                plan.router.as_ref(),
                &PPOptions {
                    show_partitions: true,
                    ..PPOptions::none()
                }
            ),
            "Sort, fetch: 100, partitions: 1\
            \n  InlineFinalAggregate, partitions: 1\
            \n    MergeSort, partitions: 1\
            \n      ClusterSend, partitions: [[2], [1]]"
        );
        assert_eq!(pp_phys_plan_ext(plan.worker.as_ref(), &PPOptions{ show_partitions: true, ..PPOptions::none()}),
            "Sort, fetch: 100, partitions: 1\
            \n  InlineFinalAggregate, partitions: 1\
            \n    MergeSort, partitions: 1\
            \n      Worker, partitions: 2\
            \n        MergeSort, partitions: 1\
            \n          LocalLimit, n: 100, partitions: 2\
            \n            InlinePartialAggregate, limit: 100, partitions: 2\
            \n              Union, partitions: 2\
            \n                Projection, [a, b], partitions: 1\
            \n                  LastRowByUniqueKey, partitions: 1\
            \n                    MergeSort, partitions: 1\
            \n                      Scan, index: default:1:[1]:sort_on[a, b], fields: [a, b, c, e, __seq], partitions: 2\
            \n                        FilterByKeyRange, partitions: 1\
            \n                          MemoryScan, partitions: 1\
            \n                        FilterByKeyRange, partitions: 1\
            \n                          MemoryScan, partitions: 1\
            \n                Projection, [a, b], partitions: 1\
            \n                  LastRowByUniqueKey, partitions: 1\
            \n                    Scan, index: default:2:[2]:sort_on[a, b], fields: [a, b, c, e, __seq], partitions: 1\
            \n                      FilterByKeyRange, partitions: 1\
            \n                        MemoryScan, partitions: 1");
    }
    Ok(())
}

async fn unique_key_and_multi_partitions_hash_aggregate(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA test").await?;
    service.exec_query("CREATE TABLE test.unique_parts1 (a int, b int, c int, e int, val int) unique key (a, b, c, e) ").await?;
    service.exec_query("CREATE TABLE test.unique_parts2 (a int, b int, c int, e int, val int) unique key (a, b, c, e) ").await?;
    service
        .exec_query(
            "
            INSERT INTO test.unique_parts1
            (a, b, c, e, val, __seq)
            VALUES
            (1, 1, 1, 1, 10, 1),
            (2, 2, 2, 2, 20, 2)
            ",
        )
        .await?;

    service
        .exec_query(
            "
            INSERT INTO test.unique_parts1
            (a, b, c, e, val, __seq)
            VALUES
            (11, 11, 1, 11, 110, 11),
            (22, 22, 2, 22, 220, 21)
            ",
        )
        .await?;

    service
        .exec_query(
            "
            INSERT INTO test.unique_parts2
            (a, b, c, e, val, __seq)
            VALUES
            (3, 3, 1, 3, 30, 3),
            (4, 4, 1, 4, 40, 4)
            ",
        )
        .await?;

    let r = service
        .exec_query(
            "SELECT c, sum(val) FROM (
                    SELECT * FROM test.unique_parts1
                    UNION ALL
                    SELECT * FROM test.unique_parts2
                ) `tt` GROUP BY 1 ORDER BY 1 LIMIT 100",
        )
        .await?;
    assert_eq!(to_rows(&r), rows(&[(1, 190), (2, 240)]));
    Ok(())
}

/// Filters referencing only unique key columns commute with the last-row deduplication
/// and get pushed below it: onto every scan stream and into the parquet pruning
/// predicate. Filters on non-key columns must stay above the dedup and out of pruning:
/// they apply to the last version of a row, acting on individual versions below the
/// dedup would resurrect overwritten ones.
async fn filter_pushdown_unique_key(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Versions (a int, b int, val int) unique key (a, b)")
        .await?;

    service
        .exec_query(
            "INSERT INTO s.Versions (a, b, val, __seq) VALUES (1, 1, 10, 1), (2, 2, 20, 2), (3, 3, 5, 3)",
        )
        .await?;
    // Newer versions of (1, 1) and (3, 3) in another chunk
    service
        .exec_query("INSERT INTO s.Versions (a, b, val, __seq) VALUES (1, 1, 30, 4), (3, 3, 50, 5)")
        .await?;

    // A key filter selects whole version groups: only the last version of (1, 1) counts.
    let query = "SELECT sum(val) FROM s.Versions WHERE a = 1";
    let r = service.exec_query(query).await?;
    assert_eq!(to_rows(&r), rows(&[(30)]));

    let p = service.plan_query(query).await?;
    let worker_plan = pp_phys_plan_ext(
        p.worker.as_ref(),
        &PPOptions {
            show_filters: true,
            ..PPOptions::none()
        },
    );
    assert!(
        filters_below_scan(&worker_plan) > 0,
        "the unique key filter must be pushed below the dedup onto scan streams:\n{}",
        worker_plan
    );
    assert!(
        scan_line(&worker_plan).contains("predicate:"),
        "the unique key filter must reach the parquet pruning predicate:\n{}",
        worker_plan
    );

    // A non-key filter: the overwritten versions (1, 1, 10) and (3, 3, 5) match val < 25
    // while their last versions don't. They must not be resurrected.
    let query = "SELECT sum(val) FROM s.Versions WHERE val < 25";
    let r = service.exec_query(query).await?;
    assert_eq!(to_rows(&r), rows(&[(20)]));

    let p = service.plan_query(query).await?;
    let worker_plan = pp_phys_plan_ext(
        p.worker.as_ref(),
        &PPOptions {
            show_filters: true,
            ..PPOptions::none()
        },
    );
    assert_eq!(
        filters_below_scan(&worker_plan),
        0,
        "a non-key filter must not be pushed below the dedup:\n{}",
        worker_plan
    );
    assert!(
        !scan_line(&worker_plan).contains("predicate:"),
        "a non-key filter must not reach the parquet pruning predicate:\n{}",
        worker_plan
    );

    // A mixed conjunction splits: the key conjunct goes below the dedup, the non-key one
    // stays above. The last version of (3, 3) is 50: it fails val <= 25, and the
    // overwritten (3, 3, 5) must not pass in its place.
    let query = "SELECT sum(val) FROM s.Versions WHERE a in (2, 3) AND val <= 25";
    let r = service.exec_query(query).await?;
    assert_eq!(to_rows(&r), rows(&[(20)]));

    let p = service.plan_query(query).await?;
    let worker_plan = pp_phys_plan_ext(
        p.worker.as_ref(),
        &PPOptions {
            show_filters: true,
            ..PPOptions::none()
        },
    );
    assert!(
        filters_below_scan(&worker_plan) > 0,
        "the key conjunct must be pushed below the dedup:\n{}",
        worker_plan
    );
    assert!(
        !below_scan(&worker_plan).contains("val"),
        "the non-key conjunct must not appear below the dedup:\n{}",
        worker_plan
    );
    return Ok(());

    fn below_scan(plan: &str) -> &str {
        let scan_start = plan
            .find(scan_line(plan))
            .expect("scan line is a substring of the plan");
        &plan[scan_start + scan_line(plan).len()..]
    }

    fn scan_line(plan: &str) -> &str {
        plan.lines()
            .find(|l| l.trim_start().starts_with("Scan"))
            .expect("no Scan in the worker plan")
    }

    fn filters_below_scan(plan: &str) -> usize {
        plan.lines()
            .skip_while(|l| !l.trim_start().starts_with("Scan"))
            .filter(|l| {
                let l = l.trim_start();
                l.starts_with("Filter") && !l.starts_with("FilterByKeyRange")
            })
            .count()
    }
}

/// Correctness of the sorted partial aggregate executed per partition below the merge: group
/// keys present in several partitions must combine to the same totals as without the
/// optimization, with and without LIMIT.
async fn group_by_prefix_sorted_aggregate_multi_partition(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1 (a int, b int, val int, fval double)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Data2 (a int, b int, val int, fval double)")
        .await?;

    // Group keys with `a` in 4..8 get rows from both tables, i.e. from both partitions of the
    // union.
    let mut raw_rows = Vec::new();
    let mut values1 = Vec::new();
    for a in 0i64..8 {
        for b in 0i64..3 {
            for r in 0i64..2 {
                let val = a * 31 + b * 17 + r;
                values1.push(format!("({}, {}, {}, {}.5)", a, b, val, val));
                raw_rows.push((a, b, val));
            }
        }
    }
    let mut values2 = Vec::new();
    for a in 4i64..12 {
        for b in 0i64..3 {
            for r in 0i64..2 {
                let val = a * 13 + b * 7 + r;
                values2.push(format!("({}, {}, {}, {}.5)", a, b, val, val));
                raw_rows.push((a, b, val));
            }
        }
    }
    service
        .exec_query(&format!(
            "INSERT INTO s.Data1 (a, b, val, fval) VALUES {}",
            values1.join(", ")
        ))
        .await?;
    service
        .exec_query(&format!(
            "INSERT INTO s.Data2 (a, b, val, fval) VALUES {}",
            values2.join(", ")
        ))
        .await?;

    // Expected aggregates per group; fval = val + 0.5 keeps float sums exactly representable,
    // so the assertions stay byte-exact regardless of the summation order.
    let mut groups = std::collections::BTreeMap::<(i64, i64), (i64, i64, i64, i64)>::new();
    for (a, b, val) in raw_rows {
        let group = groups.entry((a, b)).or_insert((0, i64::MAX, i64::MIN, 0));
        group.0 += val;
        group.1 = group.1.min(val);
        group.2 = group.2.max(val);
        group.3 += 1;
    }
    let group_row = |((a, b), (sum, min, max, count)): (&(i64, i64), &(i64, i64, i64, i64))| {
        vec![
            TableValue::Int(*a),
            TableValue::Int(*b),
            TableValue::Int(*sum),
            TableValue::Int(*min),
            TableValue::Int(*max),
            TableValue::Float((*sum as f64 + 0.5 * *count as f64).into()),
        ]
    };
    let expected: Vec<Vec<TableValue>> = groups.iter().map(group_row).collect();

    let query = "SELECT a, b, sum(val), min(val), max(val), sum(fval) FROM (\
                     SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2\
                 ) `t` GROUP BY 1, 2";

    let full = service
        .exec_query(&format!("{} ORDER BY 1, 2", query))
        .await?;
    assert_eq!(to_rows(&full), expected);

    // LIMIT must return the same prefix of the full result
    let limited = service
        .exec_query(&format!("{} ORDER BY 1, 2 LIMIT 5", query))
        .await?;
    assert_eq!(to_rows(&limited), expected[..5]);

    // DESC ordering takes the tail groups
    let tail = service
        .exec_query(&format!("{} ORDER BY 1 DESC, 2 DESC LIMIT 5", query))
        .await?;
    let expected_tail: Vec<_> = expected.iter().rev().take(5).cloned().collect();
    assert_eq!(to_rows(&tail), expected_tail);

    // ORDER BY an aggregate must not use the group-order shortcut
    let by_sum = service
        .exec_query(&format!("{} ORDER BY 3, 1, 2 LIMIT 4", query))
        .await?;
    let mut by_sum_keys: Vec<(i64, i64, i64)> = groups
        .iter()
        .map(|((a, b), (sum, ..))| (*sum, *a, *b))
        .collect();
    by_sum_keys.sort();
    let expected_by_sum: Vec<Vec<TableValue>> = by_sum_keys
        .iter()
        .take(4)
        .map(|(_, a, b)| group_row(((&(*a, *b)), &groups[&(*a, *b)])))
        .collect();
    assert_eq!(to_rows(&by_sum), expected_by_sum);
    Ok(())
}

/// LIMIT short-circuit correctness on group counts below and above the execution batch size
/// (4096), with duplicate group keys across partitions.
async fn group_by_prefix_limit_high_cardinality(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Data1 (a int, val int)")
        .await?;
    service
        .exec_query("CREATE TABLE s.Data2 (a int, val int)")
        .await?;

    // 7500 groups in total, above the 4096 execution batch size; keys 2500..5000 are present
    // in both tables.
    for chunk in (0i64..5000).collect::<Vec<_>>().chunks(1000) {
        let values = chunk
            .iter()
            .map(|a| format!("({}, {})", a, a * 3 + 1))
            .join(", ");
        service
            .exec_query(&format!("INSERT INTO s.Data1 (a, val) VALUES {}", values))
            .await?;
    }
    for chunk in (2500i64..7500).collect::<Vec<_>>().chunks(1000) {
        let values = chunk
            .iter()
            .map(|a| format!("({}, {})", a, a * 5 + 2))
            .join(", ");
        service
            .exec_query(&format!("INSERT INTO s.Data2 (a, val) VALUES {}", values))
            .await?;
    }

    let expected = |range: std::ops::Range<i64>| -> Vec<Vec<TableValue>> {
        range
            .map(|a| {
                let mut sum = 0;
                if a < 5000 {
                    sum += a * 3 + 1;
                }
                if a >= 2500 {
                    sum += a * 5 + 2;
                }
                vec![TableValue::Int(a), TableValue::Int(sum)]
            })
            .collect()
    };

    let query = "SELECT a, sum(val) FROM (\
                     SELECT * FROM s.Data1 UNION ALL SELECT * FROM s.Data2\
                 ) `t` GROUP BY 1";

    // LIMIT far below the batch size
    let r = service
        .exec_query(&format!("{} ORDER BY 1 LIMIT 10", query))
        .await?;
    assert_eq!(to_rows(&r), expected(0..10));

    // LIMIT above the batch size
    let r = service
        .exec_query(&format!("{} ORDER BY 1 LIMIT 5000", query))
        .await?;
    assert_eq!(to_rows(&r), expected(0..5000));

    // DESC takes the last groups (per-partition tail path)
    let r = service
        .exec_query(&format!("{} ORDER BY 1 DESC LIMIT 10", query))
        .await?;
    let mut expected_tail = expected(7490..7500);
    expected_tail.reverse();
    assert_eq!(to_rows(&r), expected_tail);

    // No limit: all the groups
    let r = service.exec_query(&format!("{} ORDER BY 1", query)).await?;
    assert_eq!(to_rows(&r), expected(0..7500));

    // No ORDER BY: the limit still descends (the group key is the index sort prefix), so groups
    // come out in key order and the first 10 are returned.
    let r = service.exec_query(&format!("{} LIMIT 10", query)).await?;
    assert_eq!(to_rows(&r), expected(0..10));
    Ok(())
}

/// The sorted partial aggregate runs per partition below the merge; the worker limit becomes a
/// group limit on the aggregate plus a widened row budget on the merge (duplicate group keys
/// from different partitions make a plain row limit incorrect).
async fn planning_aggregate_below_merge_with_limit(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Orders(a int, b int, amount int)")
        .await?;

    let p = service
        .plan_query(
            "SELECT a, b, SUM(amount) FROM (\
                 SELECT * FROM s.Orders UNION ALL SELECT * FROM s.Orders\
             ) `t` GROUP BY 1, 2",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    MergeSort\
        \n      InlinePartialAggregate\
        \n        Union\
        \n          Scan, index: default:1:[1]:sort_on[a, b], fields: *\
        \n            Sort\
        \n              Empty\
        \n          Scan, index: default:1:[1]:sort_on[a, b], fields: *\
        \n            Sort\
        \n              Empty"
    );

    let p = service
        .plan_query(
            "SELECT a, b, SUM(amount) FROM (\
                 SELECT * FROM s.Orders UNION ALL SELECT * FROM s.Orders\
             ) `t` GROUP BY 1, 2 ORDER BY 1, 2 LIMIT 5",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Sort, fetch: 5\
        \n  InlineFinalAggregate\
        \n    Worker\
        \n      MergeSort\
        \n        LocalLimit, n: 5\
        \n          InlinePartialAggregate, limit: 5\
        \n            Union\
        \n              Scan, index: default:1:[1]:sort_on[a, b], fields: *\
        \n                Sort\
        \n                  Empty\
        \n              Scan, index: default:1:[1]:sort_on[a, b], fields: *\
        \n                Sort\
        \n                  Empty"
    );

    // Reverse limit: a per-partition tail below the merge instead of a group limit (the last
    // groups are unknown until the input ends, so the aggregate can't stop early)
    let p = service
        .plan_query(
            "SELECT a, b, SUM(amount) FROM (\
                 SELECT * FROM s.Orders UNION ALL SELECT * FROM s.Orders\
             ) `t` GROUP BY 1, 2 ORDER BY 1 DESC, 2 DESC LIMIT 5",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "Sort, fetch: 5\
        \n  InlineFinalAggregate\
        \n    Worker\
        \n      MergeSort\
        \n        TailLimit, n: 5\
        \n          InlinePartialAggregate\
        \n            Union\
        \n              Scan, index: default:1:[1]:sort_on[a, b], fields: *\
        \n                Sort\
        \n                  Empty\
        \n              Scan, index: default:1:[1]:sort_on[a, b], fields: *\
        \n                Sort\
        \n                  Empty"
    );
    Ok(())
}

/// A no-GROUP BY (hash) aggregate over a multi-chunk scan: the chunks must be coalesced, not
/// merge-sorted -- the aggregate doesn't use the order, and the equality filters on the sort key
/// prefix make every merge comparison a full-length tie.
async fn global_aggregate_no_chunk_merge(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Batch (tenant_id int, deployment_id int, ts timestamp, hits int, hits_failed int)",
        )
        .await?;
    service
        .exec_query(
            "CREATE TABLE s.Stream (tenant_id int, deployment_id int, ts timestamp, hits int, hits_failed int)",
        )
        .await?;

    // Several inserts = several chunks, like a streaming table
    for i in 0..3 {
        let values = (0..50)
            .map(|j| {
                format!(
                    "(25358, 5, '2026-06-03T0{}:00:{:02}.000', {}, {})",
                    i,
                    j,
                    j,
                    j % 3
                )
            })
            .join(", ");
        service
            .exec_query(&format!(
                "INSERT INTO s.Stream (tenant_id, deployment_id, ts, hits, hits_failed) VALUES {}",
                values
            ))
            .await?;
    }

    let query = "SELECT sum(hits) hits, sum(hits_failed) hits_failed FROM (\
                     SELECT * FROM s.Batch WHERE 1 = 0 \
                     UNION ALL \
                     SELECT * FROM s.Stream\
                 ) `t` WHERE tenant_id = 25358 AND deployment_id = 5 \
                   AND ts >= to_timestamp('2026-06-03T00:00:00.000') \
                   AND ts <= to_timestamp('2026-06-03T23:59:59.999') \
                 LIMIT 10000";

    let r = service.exec_query(query).await?;
    assert_eq!(to_rows(&r), rows(&[(3675, 147)]));

    let p = service.plan_query(query).await?;
    let worker_plan = pp_phys_plan(p.worker.as_ref());
    assert!(
        !worker_plan.contains("MergeSort"),
        "hash aggregate must not merge-sort the chunks:\n{}",
        worker_plan
    );
    let coalesce_pos = worker_plan.find("CoalescePartitions");
    let aggregate_pos = worker_plan.find("LinearPartialAggregate");
    assert!(
        coalesce_pos.is_some() && aggregate_pos.is_some() && coalesce_pos < aggregate_pos,
        "the per-partition aggregation must be coalesced above:\n{}",
        worker_plan
    );
    Ok(())
}

/// A unique key table deduplicates row versions via LastRowByUniqueKey, which needs its input
/// merge-sorted to keep the versions of a key adjacent. That merge must survive the
/// no-ordering-needed rewrite under a global aggregate.
async fn global_aggregate_unique_key_keeps_merge(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Versions (a int, b int, val int) unique key (a, b)")
        .await?;
    service
        .exec_query("INSERT INTO s.Versions (a, b, val, __seq) VALUES (1, 1, 10, 1), (2, 2, 20, 2)")
        .await?;
    // A newer version of (1, 1) in another chunk
    service
        .exec_query("INSERT INTO s.Versions (a, b, val, __seq) VALUES (1, 1, 30, 3)")
        .await?;

    let query = "SELECT sum(val) FROM s.Versions";
    let r = service.exec_query(query).await?;
    // Only the last version of each key counts: 30 + 20, not 10 + 30 + 20
    assert_eq!(to_rows(&r), rows(&[(50)]));

    let p = service.plan_query(query).await?;
    let worker_plan = pp_phys_plan(p.worker.as_ref());
    assert!(
        worker_plan.contains("LastRowByUniqueKey") && worker_plan.contains("MergeSort"),
        "the deduplicating merge must stay in place:\n{}",
        worker_plan
    );
    Ok(())
}

async fn prefilter_chunks_shared_scan(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Versions (a int, b int, val int) unique key (a, b)")
        .await?;

    service
        .exec_query(
            "INSERT INTO s.Versions (a, b, val, __seq) VALUES (1, 1, 10, 1), (2, 2, 20, 2), (3, 3, 5, 3)",
        )
        .await?;
    // Newer versions of (1, 1) and (3, 3) in another in-memory chunk.
    service
        .exec_query("INSERT INTO s.Versions (a, b, val, __seq) VALUES (1, 1, 30, 4), (3, 3, 50, 5)")
        .await?;

    // Two scans of the same index over the same in-memory chunks: one carries a dedup-safe
    // key filter (a = 1), the other none. The worker must not trim the shared chunks by the
    // first scan's predicate, or the unfiltered branch loses rows. Filtered branch dedups to
    // (1,1)->30 = 30; unfiltered branch dedups to 30+20+50 = 100; total = 130.
    let query = "SELECT sum(val) FROM (\
        SELECT val FROM s.Versions WHERE a = 1 \
        UNION ALL \
        SELECT val FROM s.Versions\
    ) t";
    let r = service.exec_query(query).await?;
    assert_eq!(to_rows(&r), rows(&[(130)]));

    Ok(())
}

async fn divide_by_zero(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service.exec_query("CREATE TABLE s.t(i int, z int)").await?;
    service
        .exec_query("INSERT INTO s.t(i, z) VALUES (1, 0), (2, 0), (3, 0)")
        .await?;
    let r = service
        .exec_query("SELECT i / z FROM s.t")
        .await
        .err()
        .expect("expected error");
    assert_eq!(
        r.elide_backtrace(),
        CubeError::internal(
            "Execution error: Internal: Arrow error: Divide by zero error".to_string()
        )
    );
    Ok(())
}

async fn panic_worker(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let r = service.exec_query("SYS PANIC WORKER").await;
    assert_eq!(r, Err(CubeError::panic("worker panic".to_string())));
    Ok(())
}

async fn filter_multiple_in_for_decimal_setup(
    service: &dyn SqlClient,
) -> Result<&'static str, CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service.exec_query("CREATE TABLE s.t(i decimal)").await?;
    service
        .exec_query("INSERT INTO s.t(i) VALUES (1), (2), (3)")
        .await?;

    Ok(("SELECT count(*) FROM s.t WHERE i in ('2', '3')") as _)
}

async fn filter_multiple_in_for_decimal(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let query = filter_multiple_in_for_decimal_setup(service.as_ref()).await?;

    let r = service.exec_query(query).await?;

    assert_eq!(to_rows(&r), rows(&[(2)]));
    Ok(())
}

async fn planning_filter_multiple_in_for_decimal(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let query = filter_multiple_in_for_decimal_setup(service.as_ref()).await?;

    // Verify we're casting '2' and '3' to decimal type and not casting i to Utf8, with Cube-specific DF comparison coercion changes.
    let plans = service.plan_query(query).await?;
    let expected =
        "Projection, [count(Int64(1))@0:count(*)]\
        \n  LinearFinalAggregate\
        \n    CoalescePartitions\
        \n      ClusterSend, partitions: [[1]]\
        \n        CoalescePartitions\
        \n          LinearPartialAggregate\
        \n            Projection, []\
        \n              Filter, predicate: i@0 = Some(200000),18,5 OR i@0 = Some(300000),18,5\
        \n                Scan, index: default:1:[1], fields: *, predicate: i = Decimal128(Some(200000),18,5) OR i = Decimal128(Some(300000),18,5)\
        \n                  Sort\
        \n                    Empty";

    assert_eq!(
        expected,
        pp_phys_plan_ext(
            plans.router.as_ref(),
            &PPOptions {
                traverse_past_clustersend: true,
                show_filters: true,
                ..PPOptions::none()
            }
        ),
    );
    Ok(())
}

async fn planning_aggregate_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Orders(a int, b int, c int, a_sum int, a_max int, a_min int, a_merge HYPERLOGLOG)
                     AGGREGATIONS(sum(a_sum), max(a_max), min(a_min), merge(a_merge))
                     INDEX reg_index (a, b)
                     AGGREGATE INDEX aggr_index (a, b)
                     ")
        .await
        ?;

    let p = service
        .plan_query("SELECT a, b, sum(a_sum) FROM s.Orders GROUP BY 1, 2")
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Scan, index: aggr_index:2:[2]:sort_on[a, b], fields: [a, b, a_sum]\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query("SELECT a, b, sum(a_sum), max(a_max), min(a_min), merge(a_merge) FROM s.Orders GROUP BY 1, 2")
        .await
        ?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Scan, index: aggr_index:2:[2]:sort_on[a, b], fields: *\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query("SELECT a, b, sum(a_sum), max(a_max), min(a_min), merge(a_merge) FROM s.Orders WHERE c = 1 GROUP BY 1, 2")
        .await
        ?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Filter\
        \n        Scan, index: default:3:[3]:sort_on[a, b, c], fields: *\
        \n          Sort\
        \n            Empty"
    );

    let p = service
        .plan_query(
            "SELECT a, sum(a_sum), max(a_max), min(a_min), merge(a_merge) FROM s.Orders GROUP BY 1",
        )
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Scan, index: aggr_index:2:[2]:sort_on[a], fields: [a, a_sum, a_max, a_min, a_merge]\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query("SELECT a, avg(a_sum) FROM s.Orders GROUP BY 1")
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Scan, index: reg_index:1:[1]:sort_on[a], fields: [a, a_sum]\
        \n        Sort\
        \n          Empty"
    );

    let p = service
        .plan_query("SELECT a, sum(a_sum) FROM s.Orders WHERE b = 1 GROUP BY 1")
        .await?;
    assert_eq!(
        pp_phys_plan(p.worker.as_ref()),
        "InlineFinalAggregate\
        \n  Worker\
        \n    InlinePartialAggregate\
        \n      Filter\
        \n        Scan, index: aggr_index:2:[2]:sort_on[a, b], fields: [a, b, a_sum]\
        \n          Sort\
        \n            Empty"
    );
    Ok(())
}

async fn aggregate_index(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b int, c int, a_sum int, a_max int, a_min int)
                     AGGREGATIONS(sum(a_sum), max(a_max), min(a_min))
                     INDEX reg_index (a, b)
                     AGGREGATE INDEX aggr_index (a, b)
                     ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Orders (a, b, c, a_sum, a_max, a_min) VALUES (1, 10, 100, 10, 10, 10), \
                                                   (2, 20, 200, 10, 10, 10), \
                                                   (1, 10, 300, 20, 20, 20), \
                                                   (2, 30, 200, 100, 100, 100), \
                                                   (1, 10, 400, 20, 40, 40), \
                                                   (2, 20, 410, 20, 30, 30) \
                                                   ",
        )
        .await?;

    let res = service
        .exec_query("SELECT a, b, sum(a_sum) as sum, max(a_max) as max, min(a_min) as min FROM s.Orders GROUP BY 1, 2 ORDER BY 1, 2")
        .await
        ?;

    assert_eq!(
        to_rows(&res),
        [
            [
                TableValue::Int(1),
                TableValue::Int(10),
                TableValue::Int(50),
                TableValue::Int(40),
                TableValue::Int(10)
            ],
            [
                TableValue::Int(2),
                TableValue::Int(20),
                TableValue::Int(30),
                TableValue::Int(30),
                TableValue::Int(10)
            ],
            [
                TableValue::Int(2),
                TableValue::Int(30),
                TableValue::Int(100),
                TableValue::Int(100),
                TableValue::Int(100)
            ],
        ]
    );

    let res = service
        .exec_query("SELECT a, sum(a_sum) as sum, max(a_max) as max, min(a_min) as min FROM s.Orders GROUP BY 1 ORDER BY 1")
        .await
        ?;

    assert_eq!(
        to_rows(&res),
        [
            [
                TableValue::Int(1),
                TableValue::Int(50),
                TableValue::Int(40),
                TableValue::Int(10)
            ],
            [
                TableValue::Int(2),
                TableValue::Int(130),
                TableValue::Int(100),
                TableValue::Int(10)
            ],
        ]
    );

    let res = service
        .exec_query("SELECT a, sum(a_sum) as sum, max(a_max) as max, min(a_min) as min FROM s.Orders WHERE b = 20 GROUP BY 1 ORDER BY 1")
        .await
        ?;

    assert_eq!(
        to_rows(&res),
        [[
            TableValue::Int(2),
            TableValue::Int(30),
            TableValue::Int(30),
            TableValue::Int(10)
        ],]
    );
    Ok(())
}

async fn aggregate_index_with_hll_bytes(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b int, hll bytes)
                     AGGREGATIONS(merge(hll))
                     AGGREGATE INDEX agg_index (a, b)
                     ",
        )
        .await?;
    let sparse = "X'020C0200C02FF58941D5F0C6'";
    let dense = "X' '";

    service
        .exec_query(&format!(
            "INSERT INTO s.Orders (a, b, hll) VALUES (1, 10, {s}), \
                                                   (2, 20, {d}), \
                                                   (1, 10, {d}), \
                                                   (2, 30, {s}), \
                                                   (1, 10, {d}), \
                                                   (2, 20, {s}) \
                                                   ",
            s = sparse,
            d = dense
        ))
        .await?;

    let res = service
        .exec_query(
            "SELECT a, b, cardinality(merge(hll)) FROM s.Orders GROUP BY 1, 2 ORDER BY 1, 2",
        )
        .await?;

    assert_eq!(
        to_rows(&res),
        [
            [
                TableValue::Int(1),
                TableValue::Int(10),
                TableValue::Int(657),
            ],
            [
                TableValue::Int(2),
                TableValue::Int(20),
                TableValue::Int(657),
            ],
            [TableValue::Int(2), TableValue::Int(30), TableValue::Int(2),],
        ]
    );
    Ok(())
}

async fn aggregate_index_hll(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b int, a_hll hyperloglog)
                     AGGREGATIONS(merge(a_hll))
                     AGGREGATE INDEX aggr_index (a, b)
                     ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.Orders (a, b, a_hll) VALUES \
                                                    (1, 10, X'020C0200C02FF58941D5F0C6'), \
                                                    (1, 20, X'020C0200C02FF58941D5F0C6'), \
                                                    (1, 10, X'020C0200C02FF58941D5F0C6'), \
                                                    (1, 20, X'020C0200C02FF58941D5F0C6') \
                                                   ",
        )
        .await?;

    let res = service
        .exec_query("SELECT a, b, cardinality(merge(a_hll)) as hll FROM s.Orders GROUP BY 1, 2 ORDER BY 1, 2")
        .await
        ?;
    assert_eq!(
        to_rows(&res),
        [
            [TableValue::Int(1), TableValue::Int(10), TableValue::Int(2)],
            [TableValue::Int(1), TableValue::Int(20), TableValue::Int(2)],
        ]
    );

    let res = service
        .exec_query("SELECT a, cardinality(merge(a_hll)) as hll FROM s.Orders WHERE b = 20 GROUP BY 1 ORDER BY 1")
        .await
        ?;
    assert_eq!(to_rows(&res), [[TableValue::Int(1), TableValue::Int(2)],]);

    let res = service
        .exec_query(
            "SELECT a, cardinality(merge(a_hll)) as hll FROM s.Orders GROUP BY 1 ORDER BY 1",
        )
        .await?;
    assert_eq!(to_rows(&res), [[TableValue::Int(1), TableValue::Int(2)],]);
    Ok(())
}

async fn aggregate_index_errors(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.Orders(a int, b int, a_hll hyperloglog)
                     AGGREGATE INDEX aggr_index (a, b, a_hll)
                     ")
        .await
        .expect_err("Can't create aggregate index for table 'Orders' because aggregate columns (`AGGREGATIONS`) not specified for the table");

    service
        .exec_query("CREATE TABLE s.Orders(a int, b int, a_hll hyperloglog)
                     AGGREGATIONS(merge(a_hll))
                     AGGREGATE INDEX aggr_index (a, b, a_hll)
                     ")
        .await
        .expect_err("Column 'a_hll' in aggregate index 'aggr_index' is in aggregations list for table 'Orders'. Aggregate index columns must be outside of aggregations list.");

    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b string, a_hll hyperloglog)
                    AGGREGATIONS (sum(a), sum(b))
                     ",
        )
        .await
        .expect_err("Aggregate function SUM not allowed for column type text");

    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b string, a_hll hyperloglog)
                    AGGREGATIONS (sum(a), max(b), sum(a_hll))
                     ",
        )
        .await
        .expect_err("Aggregate function SUM not allowed for column type hyperloglog");

    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b string, a_hll hyperloglog)
                    AGGREGATIONS (max(a_hll))
                     ",
        )
        .await
        .expect_err("Aggregate function MAX not allowed for column type hyperloglog");

    service
        .exec_query(
            "CREATE TABLE s.Orders(a int, b string, a_hll hyperloglog)
                    AGGREGATIONS (merge(a))
                     ",
        )
        .await
        .expect_err("Aggregate function MERGE not allowed for column type integer");
    Ok(())
}

async fn aggregate_index_decimal(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;

    // Building an aggregating index runs sum() over the chunk data, and DataFusion widens a
    // decimal sum's precision. The result must be cast back to the declared column type
    // instead of failing the chunk build.
    let file = write_tmp_file(indoc! {"
        k,v,i
        books,900.00000,10
        books,100.50000,20
        toys,90.00000,5
        toys,9.50000,7
    "})?;
    let path = file.path().to_string_lossy();
    service
        .exec_query(
            format!(
                "CREATE TABLE s.Orders(k varchar, v decimal, i int)
                     AGGREGATIONS(sum(v), sum(i))
                     AGGREGATE INDEX k_agg (k)
                     LOCATION '{}'",
                path
            )
            .as_str(),
        )
        .await?;

    let res = service
        .exec_query("SELECT k, sum(v), sum(i) FROM s.Orders GROUP BY 1 ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&res),
        [
            [
                TableValue::String("books".to_string()),
                TableValue::Decimal(Decimal::new(100050000)),
                TableValue::Int(30)
            ],
            [
                TableValue::String("toys".to_string()),
                TableValue::Decimal(Decimal::new(9950000)),
                TableValue::Int(12)
            ],
        ]
    );

    // The same shape over INSERT-ed data exercises the in-memory ingestion path.
    service
        .exec_query(
            "CREATE TABLE s.OrdersIns(k varchar, v decimal, i int)
                     AGGREGATIONS(sum(v), sum(i))
                     AGGREGATE INDEX k_agg (k)",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.OrdersIns (k, v, i) VALUES ('books', 900.00000, 10), \
                                                      ('books', 100.50000, 20), \
                                                      ('toys', 90.00000, 5), \
                                                      ('toys', 9.50000, 7)",
        )
        .await?;

    let res = service
        .exec_query("SELECT k, sum(v), sum(i) FROM s.OrdersIns GROUP BY 1 ORDER BY 1")
        .await?;
    assert_eq!(
        to_rows(&res),
        [
            [
                TableValue::String("books".to_string()),
                TableValue::Decimal(Decimal::new(100050000)),
                TableValue::Int(30)
            ],
            [
                TableValue::String("toys".to_string()),
                TableValue::Decimal(Decimal::new(9950000)),
                TableValue::Int(12)
            ],
        ]
    );
    Ok(())
}

async fn aggregate_index_decimal_overflow(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query(
            "CREATE TABLE s.Orders(k varchar, v decimal(18, 5))
                     AGGREGATIONS(sum(v))
                     AGGREGATE INDEX k_agg (k)",
        )
        .await?;

    // Each value fits Decimal128(18, 5) (13 integer digits), but the per-key sum does
    // not. The aggregating index build runs that sum, and the cast back to the declared
    // type uses safe: false, so the overflow must surface as a diagnosable error — not a
    // panic in a background task and not a silently stored NULL.
    let res = service
        .exec_query("INSERT INTO s.Orders (k, v) VALUES ('a', 9000000000000), ('a', 9000000000000)")
        .await;
    match res {
        Ok(_) => panic!("a sum overflowing the declared decimal precision must fail the insert"),
        Err(e) => {
            let msg = e.to_string();
            assert!(
                msg.contains("too large to store"),
                "expected a decimal overflow error, got: {}",
                msg
            );
        }
    }
    Ok(())
}

async fn inline_tables(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA Foo").await?;
    service
        .exec_query(
            "CREATE TABLE Foo.Persons (
                ID int,
                FirstName varchar(255),
                LastName varchar(255),
                Timestamp timestamp
            )",
        )
        .await?;

    service
        .exec_query(
            "INSERT INTO Foo.Persons
        (ID, LastName, FirstName, Timestamp)
        VALUES
        (23, 'LastName 1', 'FirstName 1', '2020-01-01T00:00:00.000Z'),
        (24, 'LastName 3', 'FirstName 1', '2020-01-02T00:00:00.000Z'),
        (25, 'LastName 4', 'FirstName 1', '2020-01-03T00:00:00.000Z'),
        (26, 'LastName 5', 'FirstName 1', '2020-01-04T00:00:00.000Z'),
        (35, 'LastName 24', 'FirstName 2', '2020-01-01T00:00:00.000Z'),
        (36, 'LastName 23', 'FirstName 2', '2020-01-02T00:00:00.000Z'),
        (37, 'LastName 22', 'FirstName 2', '2020-01-03T00:00:00.000Z'),
        (38, 'LastName 21', 'FirstName 2', '2020-01-04T00:00:00.000Z')",
        )
        .await?;

    let result = service.exec_query("SELECT * FROM Foo.Persons").await?;
    assert_eq!(result.get_rows().len(), 8);
    assert_eq!(
        result.get_rows()[0],
        Row::new(vec![
            TableValue::Int(23),
            TableValue::String("FirstName 1".to_string()),
            TableValue::String("LastName 1".to_string()),
            TableValue::Timestamp(timestamp_from_string("2020-01-01T00:00:00.000Z")?),
        ])
    );

    let columns = vec![
        Column::new("ID".to_string(), ColumnType::Int, 0),
        Column::new("LastName".to_string(), ColumnType::String, 1),
        Column::new("FirstName".to_string(), ColumnType::String, 2),
        Column::new("Timestamp".to_string(), ColumnType::Timestamp, 3),
    ];
    let rows = vec![
        Row::new(vec![
            TableValue::Null,
            TableValue::String("last 1".to_string()),
            TableValue::String("first 1".to_string()),
            TableValue::Timestamp(timestamp_from_string("2020-01-01T00:00:00.000Z")?),
        ]),
        Row::new(vec![
            TableValue::Int(2),
            TableValue::Null,
            TableValue::String("first 2".to_string()),
            TableValue::Timestamp(timestamp_from_string("2020-01-02T00:00:00.000Z")?),
        ]),
        Row::new(vec![
            TableValue::Int(3),
            TableValue::String("last 3".to_string()),
            TableValue::String("first 3".to_string()),
            TableValue::Timestamp(timestamp_from_string("2020-01-03T00:00:00.000Z")?),
        ]),
        Row::new(vec![
            TableValue::Int(4),
            TableValue::String("last 4".to_string()),
            TableValue::String("first 4".to_string()),
            TableValue::Null,
        ]),
    ];
    let data = Arc::new(DataFrame::new(columns, rows.clone()));
    let inline_tables = vec![InlineTable::new(1000, "Persons".to_string(), data)];

    let context = SqlQueryContext::default().with_inline_tables(&inline_tables);
    let result = service
        .exec_query_with_context(context, "SELECT * FROM Persons")
        .await?;
    assert_eq!(result.get_rows(), &rows);

    let context = SqlQueryContext::default().with_inline_tables(&inline_tables);
    let result = service
        .exec_query_with_context(context, "SELECT LastName, Timestamp FROM Persons")
        .await?;
    assert_eq!(
        result.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("last 1".to_string()),
                TableValue::Timestamp(timestamp_from_string("2020-01-01T00:00:00.000Z")?),
            ]),
            Row::new(vec![
                TableValue::Null,
                TableValue::Timestamp(timestamp_from_string("2020-01-02T00:00:00.000Z")?),
            ]),
            Row::new(vec![
                TableValue::String("last 3".to_string()),
                TableValue::Timestamp(timestamp_from_string("2020-01-03T00:00:00.000Z")?),
            ]),
            Row::new(vec![
                TableValue::String("last 4".to_string()),
                TableValue::Null,
            ]),
        ]
    );

    let context = SqlQueryContext::default().with_inline_tables(&inline_tables);
    let result = service
        .exec_query_with_context(
            context,
            r#"
            SELECT *
            FROM (
                SELECT LastName, Timestamp FROM Foo.Persons
                UNION ALL SELECT LastName, Timestamp FROM Persons
            )
            ORDER BY LastName
        "#,
        )
        .await?;
    assert_eq!(
        result.get_rows()[8..12].to_vec(),
        vec![
            Row::new(vec![
                TableValue::String("last 1".to_string()),
                TableValue::Timestamp(timestamp_from_string("2020-01-01T00:00:00.000Z")?),
            ]),
            Row::new(vec![
                TableValue::String("last 3".to_string()),
                TableValue::Timestamp(timestamp_from_string("2020-01-03T00:00:00.000Z")?),
            ]),
            Row::new(vec![
                TableValue::String("last 4".to_string()),
                TableValue::Null,
            ]),
            Row::new(vec![
                TableValue::Null,
                TableValue::Timestamp(timestamp_from_string("2020-01-02T00:00:00.000Z")?),
            ]),
        ]
    );
    Ok(())
}

async fn inline_tables_2x(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA Foo").await?;
    service
        .exec_query("CREATE TABLE Foo.Persons (ID int, First varchar(255), Last varchar(255))")
        .await?;
    service
        .exec_query("CREATE TABLE Foo.Persons2 (ID int, First varchar(255), Last varchar(255))")
        .await?;

    service
        .exec_query(
            "INSERT INTO Foo.Persons
            (ID, Last, First)
            VALUES
            (11, 'last 11', 'first 11'),
            (12, 'last 12', 'first 12'),
            (13, 'last 13', 'first 13')",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO Foo.Persons2
            (ID, Last, First)
            VALUES
            (31, 'last 31', 'first 31'),
            (32, 'last 32', 'first 32'),
            (33, 'last 33', 'first 33')",
        )
        .await?;

    let columns = vec![
        Column::new("ID".to_string(), ColumnType::Int, 0),
        Column::new("Last".to_string(), ColumnType::String, 1),
        Column::new("First".to_string(), ColumnType::String, 2),
    ];
    let rows = vec![
        Row::new(vec![
            TableValue::Int(41),
            TableValue::String("last 41".to_string()),
            TableValue::String("first 41".to_string()),
        ]),
        Row::new(vec![
            TableValue::Int(42),
            TableValue::String("last 42".to_string()),
            TableValue::String("first 42".to_string()),
        ]),
        Row::new(vec![
            TableValue::Int(43),
            TableValue::String("last 43".to_string()),
            TableValue::String("first 43".to_string()),
        ]),
    ];
    let rows2 = vec![
        Row::new(vec![
            TableValue::Int(21),
            TableValue::String("last 21".to_string()),
            TableValue::String("first 21".to_string()),
        ]),
        Row::new(vec![
            TableValue::Int(22),
            TableValue::String("last 22".to_string()),
            TableValue::String("first 22".to_string()),
        ]),
        Row::new(vec![
            TableValue::Int(23),
            TableValue::String("last 23".to_string()),
            TableValue::String("first 23".to_string()),
        ]),
    ];
    let data = Arc::new(DataFrame::new(columns.clone(), rows.clone()));
    let data2 = Arc::new(DataFrame::new(columns.clone(), rows2.clone()));
    let inline_tables = vec![
        InlineTable::new(1000, "Persons".to_string(), data),
        InlineTable::new(1001, "Persons2".to_string(), data2),
    ];

    let context = SqlQueryContext::default().with_inline_tables(&inline_tables);
    let result = service
        .exec_query_with_context(
            context,
            r#"
            SELECT Last
            FROM (
                SELECT ID, Last FROM Foo.Persons
                UNION ALL SELECT ID, Last FROM Foo.Persons2
                UNION ALL SELECT ID, Last FROM Persons
                UNION ALL SELECT ID, Last FROM Persons2
            )
            ORDER BY Last
        "#,
        )
        .await?;
    assert_eq!(
        result.get_rows().to_vec(),
        vec![
            Row::new(vec![TableValue::String("last 11".to_string())]),
            Row::new(vec![TableValue::String("last 12".to_string())]),
            Row::new(vec![TableValue::String("last 13".to_string())]),
            Row::new(vec![TableValue::String("last 21".to_string())]),
            Row::new(vec![TableValue::String("last 22".to_string())]),
            Row::new(vec![TableValue::String("last 23".to_string())]),
            Row::new(vec![TableValue::String("last 31".to_string())]),
            Row::new(vec![TableValue::String("last 32".to_string())]),
            Row::new(vec![TableValue::String("last 33".to_string())]),
            Row::new(vec![TableValue::String("last 41".to_string())]),
            Row::new(vec![TableValue::String("last 42".to_string())]),
            Row::new(vec![TableValue::String("last 43".to_string())]),
        ]
    );
    Ok(())
}

async fn build_range_end(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;

    service.exec_query("CREATE TABLE s.t0(x string)").await?;

    service
        .exec_query("CREATE TABLE s.t1(x string) WITH(build_range_end = '2020-01-01T00:00:00.000')")
        .await?;

    let r = service
        .exec_query("SELECT table_schema, table_name, build_range_end FROM system.tables")
        .await?;

    assert_eq!(
        r.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("s".to_string()),
                TableValue::String("t0".to_string()),
                TableValue::Null,
            ]),
            Row::new(vec![
                TableValue::String("s".to_string()),
                TableValue::String("t1".to_string()),
                TableValue::Timestamp(timestamp_from_string("2020-01-01T00:00:00.000")?),
            ]),
        ]
    );

    let r = service
        .exec_query(
            "SELECT table_schema, table_name, build_range_end FROM information_schema.tables",
        )
        .await?;

    assert_eq!(
        r.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("s".to_string()),
                TableValue::String("t0".to_string()),
                TableValue::Null,
            ]),
            Row::new(vec![
                TableValue::String("s".to_string()),
                TableValue::String("t1".to_string()),
                TableValue::Timestamp(timestamp_from_string("2020-01-01T00:00:00.000")?),
            ]),
        ]
    );
    Ok(())
}

async fn assert_limit_pushdown_using_search_strings(
    service: &Box<dyn SqlClient>,
    query: &str,
    expected_index: Option<&str>,
    is_limit_expected: bool,
    search_strings: &[&str],
) -> Result<Vec<Row>, CubeError> {
    let res = service
        .exec_query(&format!("EXPLAIN ANALYZE {}", query))
        .await?;
    match &res.get_rows()[1].values()[2] {
        TableValue::String(s) => {
            if let Some(ind) = expected_index {
                if !s.contains(ind) {
                    return Err(CubeError::internal(format!(
                        "Expected index `{}` but it not found in the plan",
                        ind
                    )));
                }
            }
            if is_limit_expected {
                if !search_strings.iter().any(|expected| s.contains(expected)) {
                    return Err(CubeError::internal(format!(
                        "{} expected but not found",
                        search_strings.join(" or ")
                    )));
                }
            } else if let Some(found) = search_strings.iter().find(|e| s.contains(*e)) {
                return Err(CubeError::internal(format!(
                    "{} unexpected but found",
                    found
                )));
            }
        }
        _ => return Err(CubeError::internal("unexpected value".to_string())),
    };

    let res = service.exec_query(query).await?;
    Ok(res.get_rows().clone())
}

async fn assert_limit_pushdown_using_search_string(
    service: &Box<dyn SqlClient>,
    query: &str,
    expected_index: Option<&str>,
    is_limit_expected: bool,
    search_string: &str,
) -> Result<Vec<Row>, CubeError> {
    assert_limit_pushdown_using_search_strings(
        service,
        query,
        expected_index,
        is_limit_expected,
        &[search_string],
    )
    .await
}

async fn assert_limit_pushdown(
    service: &Box<dyn SqlClient>,
    query: &str,
    expected_index: Option<&str>,
    is_limit_expected: bool,
    is_tail_limit: bool,
) -> Result<Vec<Row>, CubeError> {
    assert_limit_pushdown_using_search_strings(
        service,
        query,
        expected_index,
        is_limit_expected,
        if is_tail_limit {
            &["TailLimit"]
        } else {
            // The worker limit is either a plain row limit or, for a partial aggregate running
            // per partition below the merge, a group limit on the aggregate.
            &["GlobalLimit", "InlinePartialAggregate, limit:"]
        },
    )
    .await
}

async fn cache_incr(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.note_non_idempotent_migration_test();
    let query = r#"CACHE INCR "prefix:key""#;

    service.migration_run_next_query();
    let r = service.exec_query(query).await?;

    assert_eq!(
        r.get_rows(),
        &vec![Row::new(vec![TableValue::String(
            (if !service.is_migration() { "1" } else { "3" }).to_string()
        ),]),]
    );

    service.migration_run_next_query();
    let r = service.exec_query(query).await?;

    assert_eq!(
        r.get_rows(),
        &vec![Row::new(vec![TableValue::String(
            (if !service.is_migration() { "2" } else { "4" }).to_string()
        ),]),]
    );
    Ok(())
}

async fn cache_set_get_rm(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.migration_run_next_query();
    service
        .exec_query("CACHE SET 'key_to_rm' 'myvalue';")
        .await?;

    let get_response = service.exec_query("CACHE GET 'key_to_rm'").await?;

    assert_eq!(
        get_response.get_columns(),
        &vec![Column::new("value".to_string(), ColumnType::String, 0),]
    );

    assert_eq!(
        get_response.get_rows(),
        &vec![Row::new(vec![TableValue::String("myvalue".to_string()),]),]
    );

    service.migration_run_next_query();
    service
        .exec_query("CACHE REMOVE 'key_to_rm' 'myvalue';")
        .await?;

    let get_response = service.exec_query("CACHE GET 'key_to_rm'").await?;

    assert_eq!(
        get_response.get_rows(),
        &vec![Row::new(vec![TableValue::Null,]),]
    );
    Ok(())
}

async fn cache_set_get_set_get(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    if service.is_migration() {
        let get_response = service.exec_query("CACHE GET 'key_for_update'").await?;

        assert_eq!(
            get_response.get_rows(),
            &vec![Row::new(vec![TableValue::String("2".to_string()),]),]
        );
    }

    // Initial set
    {
        service.migration_run_next_query();
        service
            .exec_query("CACHE SET 'key_for_update' '1';")
            .await?;

        let get_response = service.exec_query("CACHE GET 'key_for_update'").await?;

        assert_eq!(
            get_response.get_rows(),
            &vec![Row::new(vec![TableValue::String("1".to_string()),]),]
        );
    }

    // update
    {
        service.migration_run_next_query();
        service
            .exec_query("CACHE SET 'key_for_update' '2';")
            .await?;

        let get_response = service.exec_query("CACHE GET 'key_for_update'").await?;

        assert_eq!(
            get_response.get_rows(),
            &vec![Row::new(vec![TableValue::String("2".to_string()),]),]
        );
    }
    Ok(())
}

async fn cache_compaction(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    if !service.is_migration() {
        service
            .exec_query("CACHE SET NX TTL 4 'my_prefix:my_key' 'myvalue';")
            .await?;

        let get_response = service.exec_query("CACHE GET 'my_prefix:my_key'").await?;

        assert_eq!(
            get_response.get_rows(),
            &vec![Row::new(vec![TableValue::String("myvalue".to_string()),]),]
        );

        tokio::time::sleep(Duration::new(5, 0)).await;
    }
    service.tolerate_next_query_revisit();
    service.exec_query("SYS CACHESTORE COMPACTION;").await?;

    let get_response = service.exec_query("CACHE GET 'my_prefix:my_key'").await?;

    assert_eq!(
        get_response.get_rows(),
        &vec![Row::new(vec![TableValue::Null,]),]
    );

    // system.cache table doesn't check expire
    let cache_resp = service
        .exec_query(
            "select count(*) from system.cache where id = 'my_key' and prefix = 'my_prefix'",
        )
        .await?;

    assert_eq!(
        cache_resp.get_rows(),
        &vec![Row::new(vec![TableValue::Int(0)]),]
    );
    Ok(())
}

async fn cache_set_nx(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let set_nx_key_sql = "CACHE SET NX TTL 4 'mykey' 'myvalue';";

    service.migration_run_next_query();
    let set_response = service.exec_query(set_nx_key_sql).await?;

    assert_eq!(
        set_response.get_columns(),
        &vec![Column::new("success".to_string(), ColumnType::Boolean, 0),]
    );

    assert_eq!(
        set_response.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true),]),]
    );

    // key was already defined
    service.migration_run_next_query();
    let set_response = service.exec_query(set_nx_key_sql).await?;

    assert_eq!(
        set_response.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(false),]),]
    );

    tokio::time::sleep(Duration::new(5, 0)).await;

    // key was expired
    service.migration_run_next_query();
    let set_response = service.exec_query(set_nx_key_sql).await?;

    assert_eq!(
        set_response.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true),]),]
    );
    Ok(())
}

async fn cache_prefix_keys(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CACHE SET 'locks:key1' '1';").await?;
    service.exec_query("CACHE SET 'locks:key2' '2';").await?;
    service.exec_query("CACHE SET 'locks:key3' '3';").await?;

    let keys_response = service.exec_query("CACHE KEYS 'locks'").await?;
    assert_eq!(
        keys_response.get_columns(),
        &vec![Column::new("key".to_string(), ColumnType::String, 0),]
    );
    assert_eq!(
        keys_response.get_rows(),
        &vec![
            Row::new(vec![TableValue::String("locks:key1".to_string())]),
            Row::new(vec![TableValue::String("locks:key2".to_string())]),
            Row::new(vec![TableValue::String("locks:key3".to_string())]),
        ]
    );
    Ok(())
}

async fn limit_pushdown_group(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;
    service
        .exec_query("CREATE TABLE foo.pushdown1 (id int, n int)")
        .await?;
    service
        .exec_query("CREATE TABLE foo.pushdown2 (id int, n int)")
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown1
            (id, n)
            VALUES
            (11, 10),
            (11, 15),
            (11, 18),
            (12, 20),
            (12, 25)
            ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown2
            (id, n)
            VALUES
            (21, 10),
            (21, 15),
            (21, 15),
            (22, 20),
            (22, 25),
            (23, 30)",
        )
        .await?;

    // GROUP BY + LIMIT without ORDER BY: the group-by key is a prefix of the index sort key, so
    // the limit descends to the workers' sorted partial aggregate and groups come out key-ordered.
    let res = assert_limit_pushdown(
        &service,
        "SELECT id, SUM(n) FROM (
                SELECT * FROM foo.pushdown1
                union all
                SELECT * FROM foo.pushdown2
                ) as `tb` GROUP BY 1 LIMIT 3",
        None,
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(11), TableValue::Int(43)]),
            Row::new(vec![TableValue::Int(12), TableValue::Int(45)]),
            Row::new(vec![TableValue::Int(21), TableValue::Int(40)]),
        ]
    );
    Ok(())
}

// A HAVING (a filter above the aggregate) must block the group-by limit pushdown: the worker
// would truncate to its first `limit` groups before HAVING runs on the router, dropping qualifying
// groups that sort past the truncation point and undercounting the result.
async fn limit_pushdown_group_having(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;
    service
        .exec_query("CREATE TABLE foo.having (id int, n int)")
        .await?;
    // Odd ids sum to 100 (pass HAVING), even ids to 1 (filtered out). The first 3 ids (1, 2, 3)
    // contain only two passing groups, so a per-worker limit-before-HAVING would return 2 rows.
    let values = (1..=10).map(|id| format!("({}, {})", id, if id % 2 == 1 { 100 } else { 1 }));
    service
        .exec_query(&format!(
            "INSERT INTO foo.having (id, n) VALUES {}",
            values.collect::<Vec<_>>().join(", ")
        ))
        .await?;

    let res = assert_limit_pushdown(
        &service,
        "SELECT id, SUM(n) FROM foo.having GROUP BY 1 HAVING SUM(n) > 50 LIMIT 3",
        None,
        false,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(1), TableValue::Int(100)]),
            Row::new(vec![TableValue::Int(3), TableValue::Int(100)]),
            Row::new(vec![TableValue::Int(5), TableValue::Int(100)]),
        ]
    );
    Ok(())
}

// ORDER BY a group column that is not an index-sort prefix, with the same group spread across
// chunks: the limit can't ride the index, so the per-worker bounded sort kicks in. The sort key is
// the full group key, so every chunk keeps the global top groups' partial states and the sums stay
// correct (sorting by the ORDER BY column alone would drop partial states of groups split across
// chunks and undercount).
async fn limit_pushdown_group_nonprefix_order(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.ovl (a int, b int, val int) INDEX bidx (a, b)")
        .await?;
    // Three chunks; every b in {1, 2, 3} appears in each chunk under a different `a`, so each b
    // group spans all three chunks.
    service
        .exec_query("INSERT INTO s.ovl (a, b, val) VALUES (1, 1, 10), (1, 2, 20), (1, 3, 30)")
        .await?;
    service
        .exec_query("INSERT INTO s.ovl (a, b, val) VALUES (2, 1, 100), (2, 2, 200), (2, 3, 300)")
        .await?;
    service
        .exec_query("INSERT INTO s.ovl (a, b, val) VALUES (3, 1, 1000), (3, 2, 2000), (3, 3, 3000)")
        .await?;

    let r = service
        .exec_query("SELECT b, sum(val) FROM s.ovl GROUP BY b ORDER BY b LIMIT 2")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![
            vec![TableValue::Int(1), TableValue::Int(1110)],
            vec![TableValue::Int(2), TableValue::Int(2220)],
        ]
    );

    // DESC keeps the largest keys.
    let r = service
        .exec_query("SELECT b, sum(val) FROM s.ovl GROUP BY b ORDER BY b DESC LIMIT 2")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![
            vec![TableValue::Int(3), TableValue::Int(3330)],
            vec![TableValue::Int(2), TableValue::Int(2220)],
        ]
    );
    Ok(())
}

/// Multi-column GROUP BY where the appended (non-ORDER-BY) total-order column carries NULLs and the
/// group spans chunks. `GROUP BY b, c` is not the index prefix, so this is the hash trim path; the
/// worker cut and the router select both extend the order with the appended column `c`, and they must
/// agree on its NULL placement or the NULL group's partial states get dropped across chunks
/// (undercount). The NULL group's membership is fixed by `b` (uniquely smallest), so the assertion
/// does not depend on the tie order -- only on the sum being fully combined across all three chunks.
async fn limit_pushdown_group_null_appended(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.nag (a int, b int, c int, val int) INDEX bidx (a, b, c)")
        .await?;
    // Three chunks. The (b = 1, c = NULL) group appears in every chunk, so it spans all three and
    // its sum must combine to 1110. Five distinct (b, c) groups so the trim engages (5 > factor*k).
    service
        .exec_query(
            "INSERT INTO s.nag (a, b, c, val) VALUES \
             (1, 1, NULL, 10), (1, 2, 20, 1), (1, 3, 30, 1), (1, 4, 40, 1), (1, 5, 50, 1)",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.nag (a, b, c, val) VALUES \
             (1, 1, NULL, 100), (1, 2, 20, 1), (1, 3, 30, 1), (1, 4, 40, 1), (1, 5, 50, 1)",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.nag (a, b, c, val) VALUES \
             (1, 1, NULL, 1000), (1, 2, 20, 1), (1, 3, 30, 1), (1, 4, 40, 1), (1, 5, 50, 1)",
        )
        .await?;

    // Smallest b is the NULL-c group; its sum must combine across all three chunks.
    let r = service
        .exec_query("SELECT b, c, sum(val) FROM s.nag GROUP BY b, c ORDER BY b LIMIT 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![vec![
            TableValue::Int(1),
            TableValue::Null,
            TableValue::Int(1110),
        ]]
    );
    Ok(())
}

/// Explicit `NULLS FIRST` on the ORDER BY column, contradicting the ASC default null placement. The
/// NULL group must be the single smallest under the query's own order, so `LIMIT 1` must return it.
/// `GROUP BY b` (b not the index prefix) routes through the hash trim path; the worker cut and the
/// router select must both rank the NULL group first, or the worker trims it away and the query
/// silently returns the wrong group. Pins that the total order honors the query's NULL placement
/// rather than deriving it from the sort direction alone.
async fn limit_pushdown_group_null_order_first(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await?;
    service
        .exec_query("CREATE TABLE s.nf (a int, b int, val int) INDEX bidx (a, b)")
        .await?;
    // Three chunks; the NULL-b group appears in each (under a different `a`), so it spans all three
    // and its sum must combine to 1110. Five distinct b groups so the trim engages (5 > factor*k).
    service
        .exec_query(
            "INSERT INTO s.nf (a, b, val) VALUES \
             (1, NULL, 10), (1, 2, 1), (1, 3, 1), (1, 4, 1), (1, 5, 1)",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.nf (a, b, val) VALUES \
             (2, NULL, 100), (2, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1)",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO s.nf (a, b, val) VALUES \
             (3, NULL, 1000), (3, 2, 1), (3, 3, 1), (3, 4, 1), (3, 5, 1)",
        )
        .await?;

    // ASC NULLS FIRST: the NULL group ranks first, so LIMIT 1 must return it with its combined sum.
    let r = service
        .exec_query("SELECT b, sum(val) FROM s.nf GROUP BY b ORDER BY b ASC NULLS FIRST LIMIT 1")
        .await?;
    assert_eq!(
        to_rows(&r),
        vec![vec![TableValue::Null, TableValue::Int(1110)]]
    );
    Ok(())
}

async fn limit_pushdown_group_order(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.pushdown_group1 (a int, b int, n int) index ind1 (a, b) index ind2 (b)")
        .await
        ?;
    service
        .exec_query("CREATE TABLE foo.pushdown_group2 (a int, b int, n int) index ind1 (a, b) index ind2 (b)")
        .await
        ?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_group1
            (a, b, n)
            VALUES
            (11, 45, 1),
            (11, 18, 1),
            (11, 18, 1),
            (12, 20, 1),
            (12, 25, 1)
            ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_group2
            (a, b, n)
            VALUES
            (21, 10, 1),
            (21, 15, 1),
            (21, 15, 1),
            (22, 20, 1),
            (22, 25, 1),
            (23, 30, 1)",
        )
        .await?;

    let res = assert_limit_pushdown(
        &service,
        "SELECT `aa` FROM (SELECT a `aa`, b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2 ORDER BY 1 LIMIT 3) x",
        Some("ind1"),
        true,
        false,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                // TableValue::Int(18),
                // TableValue::Int(2)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                // TableValue::Int(45),
                // TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                // TableValue::Int(20),
                // TableValue::Int(1)
            ]),
        ]
    );

    // ===========================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2 ORDER BY 1, 2 LIMIT 3",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(2)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(1)
            ]),
        ]
    );

    // ============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2 ORDER BY 2 LIMIT 3",
        Some("ind1"),
        false,
        false,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(21),
                TableValue::Int(10),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(21),
                TableValue::Int(15),
                TableValue::Int(2)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(2)
            ]),
        ]
    );
    //============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2 ORDER BY 1, 2 DESC LIMIT 3",
        Some("ind1"),
        false,
        false,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(2)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(25),
                TableValue::Int(1)
            ]),
        ]
    );
    //============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2 ORDER BY 1 DESC, 2 DESC LIMIT 3",
        Some("ind1"),
        true,
        true,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(23),
                TableValue::Int(30),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(25),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(20),
                TableValue::Int(1)
            ]),
        ]
    );
    //============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a FROM (SELECT a, b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2 ORDER BY 1 DESC LIMIT 3) x",
        Some("ind1"),
        true,
        true,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(23),
                // TableValue::Int(30),
                // TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                // TableValue::Int(20),
                // TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                // TableValue::Int(25),
                // TableValue::Int(1)
            ]),
        ]
    );

    //============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, n FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2,3 ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 3",
        Some("default"),
        true,
        true,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(23),
                TableValue::Int(30),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(25),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(20),
                TableValue::Int(1)
            ]),
        ]
    );

    //============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT b, SUM(n) FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1 ORDER BY 1 LIMIT 3",
        Some("ind2"),
        true,
        false,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(10), TableValue::Int(1)]),
            Row::new(vec![TableValue::Int(15), TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(18), TableValue::Int(2)]),
        ]
    );
    //
    //============================

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, n FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 1, 2, 3 ORDER BY 1, 2 LIMIT 3",
        Some("default"),
        true,
        false,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(1)
            ]),
        ]
    );

    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, n FROM (
                SELECT * FROM foo.pushdown_group1
                union all
                SELECT * FROM foo.pushdown_group2
                ) as `tb` GROUP BY 3, 1, 2 ORDER BY 1, 2 LIMIT 3",
        Some("default"),
        true,
        false,
    )
    .await?;
    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(1)
            ]),
        ]
    );
    Ok(())
}

async fn limit_pushdown_group_where_order(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.pushdown_where_group1 (a int, b int, c int) index ind1 (a, b, c) index ind2 (c, b)")
        .await
        ?;
    service
        .exec_query("CREATE TABLE foo.pushdown_where_group2 (a int, b int, c int) index ind1 (a, b, c) index ind2 (c, b)")
        .await
        ?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group1
            (a, b, c)
            VALUES
            (11, 45, 1),
            (11, 18, 2),
            (11, 18, 3),
            (12, 20, 4),
            (12, 25, 5),
            (12, 25, 6)
            ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group2
            (a, b, c)
            VALUES
            (12, 30, 7),
            (21, 10, 8),
            (21, 15, 9),
            (21, 18, 10),
            (22, 20, 11),
            (22, 25, 12),
            (23, 30, 13)",
        )
        .await?;
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(c) FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 12
                GROUP BY 1, 2 ORDER BY 2 LIMIT 3",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(25),
                TableValue::Int(11)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(30),
                TableValue::Int(7)
            ]),
        ]
    );
    //======================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(c) FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 12
                GROUP BY 1, 2 ORDER BY 2 DESC LIMIT 3",
        Some("ind1"),
        true,
        true,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(30),
                TableValue::Int(7)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(25),
                TableValue::Int(11)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
        ]
    );
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE b = 18
                GROUP BY a, b, c ORDER BY a, c LIMIT 3",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(11), TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(11), TableValue::Int(3)]),
            Row::new(vec![TableValue::Int(21), TableValue::Int(10)]),
        ]
    );
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE b = 18
                GROUP BY a, b, c ORDER BY a DESC, c LIMIT 3",
        Some("ind1"),
        false,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(21), TableValue::Int(10)]),
            Row::new(vec![TableValue::Int(11), TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(11), TableValue::Int(3)]),
        ]
    );
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE b = 18
                GROUP BY a, b, c ORDER BY a DESC, c DESC LIMIT 3",
        Some("ind1"),
        true,
        true,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(21), TableValue::Int(10)]),
            Row::new(vec![TableValue::Int(11), TableValue::Int(3)]),
            Row::new(vec![TableValue::Int(11), TableValue::Int(2)]),
        ]
    );
    //
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 11 and b = 18
                GROUP BY a, b, c ORDER BY c LIMIT 3",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(3)]),
        ]
    );
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 11 and b = 18
                GROUP BY a, b, c ORDER BY c DESC LIMIT 3",
        Some("ind1"),
        true,
        true,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(3)]),
            Row::new(vec![TableValue::Int(2)]),
        ]
    );
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 11 and b = 18
                GROUP BY b, a, c ORDER BY c LIMIT 3",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(3)])
        ]
    );
    //
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a >= 11 and a < 12 and b = 18
                GROUP BY a, b, c ORDER BY c LIMIT 3",
        Some("ind1"),
        false,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![TableValue::Int(2)]),
            Row::new(vec![TableValue::Int(3)])
        ]
    );

    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT b FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE c = 11
                GROUP BY b, c ORDER BY b LIMIT 3",
        Some("ind2"),
        true,
        false,
    )
    .await?;

    assert_eq!(res, vec![Row::new(vec![TableValue::Int(20)]),]);
    Ok(())
}

async fn limit_pushdown_without_group(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.pushdown_where_group1 (a int, b int, c int) index ind1 (a, b, c) index ind2 (c, b)")
        .await
        ?;
    service
        .exec_query("CREATE TABLE foo.pushdown_where_group2 (a int, b int, c int) index ind1 (a, b, c) index ind2 (c, b)")
        .await
        ?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group1
            (a, b, c)
            VALUES
            (11, 45, 1),
            (11, 18, 2),
            (11, 18, 3),
            (12, 20, 4),
            (12, 25, 5),
            (12, 25, 6)
            ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group2
            (a, b, c)
            VALUES
            (12, 30, 7),
            (21, 10, 8),
            (21, 15, 9),
            (21, 18, 10),
            (22, 20, 11),
            (22, 25, 12),
            (23, 30, 13)",
        )
        .await?;
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a aaa, b bbbb, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 12
                ORDER BY 2 LIMIT 4",
        Some("ind1"),
        true,
        "Sort, fetch: 4",
    )
    .await?;

    let mut expected = vec![
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(20),
            TableValue::Int(4),
        ]),
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(25),
            TableValue::Int(5),
        ]),
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(25),
            TableValue::Int(6),
        ]),
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(30),
            TableValue::Int(7),
        ]),
    ];
    if res != expected {
        // Given the query, there are two valid orderings -- (12, 25, 5) and (12, 25, 6) can be swapped.

        let mut values1 = expected[1].values().clone();
        let mut values2 = expected[2].values().clone();
        std::mem::swap(&mut values1[2], &mut values2[2]);
        expected[1] = Row::new(values1);
        expected[2] = Row::new(values2);
        assert_eq!(res, expected);
    }

    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 3 LIMIT 3",
        Some("ind2"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(2)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(3)
            ]),
        ]
    );
    //
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 3 DESC LIMIT 3",
        Some("ind2"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(23),
                TableValue::Int(30),
                TableValue::Int(13)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(25),
                TableValue::Int(12)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(20),
                TableValue::Int(11)
            ]),
        ]
    );
    //
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b FROM (SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 1, 2 LIMIT 3) x",
        Some("ind1"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                // TableValue::Int(2)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                // TableValue::Int(3)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                // TableValue::Int(1)
            ]),
        ]
    );
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b FROM (SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 1, 2 LIMIT 2 OFFSET 1) x",
        Some("ind1"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                // TableValue::Int(3)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                // TableValue::Int(1)
            ]),
        ]
    );
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE b = 20
                ORDER BY 1 LIMIT 3",
        Some("ind1"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(20),
                TableValue::Int(11)
            ]),
        ]
    );
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE b = 20
                ORDER BY 1, 3 LIMIT 3",
        Some("ind1"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(20),
                TableValue::Int(11)
            ]),
        ]
    );
    Ok(())
}
async fn limit_pushdown_without_group_resort(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.pushdown_where_group1 (a int, b int, c int) index ind1 (a, b, c) index ind2 (c, b)")
        .await
        ?;
    service
        .exec_query("CREATE TABLE foo.pushdown_where_group2 (a int, b int, c int) index ind1 (a, b, c) index ind2 (c, b)")
        .await
        ?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group1
            (a, b, c)
            VALUES
            (11, 45, 1),
            (11, 18, 2),
            (11, 18, 3),
            (12, 20, 4),
            (12, 25, 5),
            (12, 25, 6)
            ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group2
            (a, b, c)
            VALUES
            (12, 30, 7),
            (21, 10, 8),
            (21, 15, 9),
            (21, 18, 10),
            (22, 20, 11),
            (22, 25, 12),
            (23, 30, 13)",
        )
        .await?;
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a aaa, b bbbb, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 12
                ORDER BY 2 desc LIMIT 4",
        Some("ind1"),
        true,
        "Sort, fetch: 4",
    )
    .await?;

    let mut expected = vec![
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(30),
            TableValue::Int(7),
        ]),
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(25),
            TableValue::Int(6),
        ]),
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(25),
            TableValue::Int(5),
        ]),
        Row::new(vec![
            TableValue::Int(12),
            TableValue::Int(20),
            TableValue::Int(4),
        ]),
    ];
    if res != expected {
        let mut values1 = expected[1].values().clone();
        let mut values2 = expected[2].values().clone();
        std::mem::swap(&mut values1[2], &mut values2[2]);
        expected[1] = Row::new(values1);
        expected[2] = Row::new(values2);
        assert_eq!(res, expected);
    }

    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a aaa, b bbbb, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 1 desc, 2 desc LIMIT 3",
        Some("ind1"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(23),
                TableValue::Int(30),
                TableValue::Int(13)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(25),
                TableValue::Int(12)
            ]),
            Row::new(vec![
                TableValue::Int(22),
                TableValue::Int(20),
                TableValue::Int(11)
            ]),
        ]
    );
    //
    // ====================================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 2 LIMIT 2",
        Some("ind1"),
        false,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(21),
                TableValue::Int(10),
                TableValue::Int(8)
            ]),
            Row::new(vec![
                TableValue::Int(21),
                TableValue::Int(15),
                TableValue::Int(9)
            ]),
        ]
    );
    Ok(())
}
async fn limit_pushdown_unique_key(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;

    service
        .exec_query("CREATE TABLE foo.pushdown_where_group1 (a int, b int, c int) unique key (a, b) index ind1 (a, b)")
        .await
        ?;
    service
        .exec_query("CREATE TABLE foo.pushdown_where_group2 (a int, b int, c int)  unique key (a, b) index ind1 (a, b)")
        .await
        ?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group1
            (a, b, __seq, c)
            VALUES
            (11, 45, 1, 1),
            (11, 18, 2, 2),
            (11, 18, 3, 3),
            (12, 20, 4, 4),
            (12, 25, 5, 5),
            (12, 25, 6, 6)
            ",
        )
        .await?;
    service
        .exec_query(
            "INSERT INTO foo.pushdown_where_group2
            (a, b, __seq, c)
            VALUES
            (12, 30, 7, 7),
            (21, 10, 8, 8),
            (21, 15, 9, 9),
            (21, 18, 10, 10),
            (22, 20, 11, 11),
            (22, 25, 12, 12),
            (23, 30, 13, 13)",
        )
        .await?;
    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 12
                ORDER BY 2 LIMIT 4",
        Some("ind1"),
        true,
        "Sort, fetch: 4",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(25),
                TableValue::Int(6)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(30),
                TableValue::Int(7)
            ]),
        ]
    );

    // ====================================
    let res = assert_limit_pushdown_using_search_string(
        &service,
        "SELECT a, b, c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                ORDER BY 3 LIMIT 3",
        Some("ind1"),
        true,
        "Sort, fetch: 3",
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(45),
                TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(3)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
        ]
    );
    //==========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT c FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 11 and b = 18
                GROUP BY b, a, c ORDER BY c LIMIT 3",
        Some("ind1"),
        false,
        false,
    )
    .await?;

    assert_eq!(res, vec![Row::new(vec![TableValue::Int(3)])]);
    //===========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(c) FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                WHERE a = 12
                GROUP BY 1, 2 ORDER BY 2 LIMIT 3",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(20),
                TableValue::Int(4)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(25),
                TableValue::Int(6)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                TableValue::Int(30),
                TableValue::Int(7)
            ]),
        ]
    );
    //
    //===========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a, b, SUM(c) FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                GROUP BY 1, 2 ORDER BY 2 LIMIT 3",
        Some("ind1"),
        false,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(21),
                TableValue::Int(10),
                TableValue::Int(8)
            ]),
            Row::new(vec![
                TableValue::Int(21),
                TableValue::Int(15),
                TableValue::Int(9)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                TableValue::Int(18),
                TableValue::Int(3)
            ]),
        ]
    );
    //===========================
    let res = assert_limit_pushdown(
        &service,
        "SELECT a FROM (SELECT a, b, SUM(c) FROM (
                SELECT * FROM foo.pushdown_where_group1
                union all
                SELECT * FROM foo.pushdown_where_group2
                ) as `tb`
                GROUP BY 1, 2 ORDER BY 1 LIMIT 3) x",
        Some("ind1"),
        true,
        false,
    )
    .await?;

    assert_eq!(
        res,
        vec![
            Row::new(vec![
                TableValue::Int(11),
                // TableValue::Int(18),
                // TableValue::Int(3)
            ]),
            Row::new(vec![
                TableValue::Int(11),
                // TableValue::Int(45),
                // TableValue::Int(1)
            ]),
            Row::new(vec![
                TableValue::Int(12),
                // TableValue::Int(20),
                // TableValue::Int(4)
            ]),
        ]
    );
    Ok(())
}

// Testing new rescheduling for old results which works on top of TTL
// With V1 API it should return latest result after each ACK
async fn queue_latest_result_v1(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let service = Arc::new(service);

    for interval_id in 1..5 {
        let add_response = service
            .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:1" "payload";"#)
            .await?;
        assert_queue_add_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String(interval_id.to_string()),
                TableValue::Boolean(true),
                TableValue::Int(1)
            ])]
        );

        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:1""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload".to_string()),
                TableValue::Null,
                TableValue::Int(0),
                TableValue::String("1".to_string()),
                // id
                TableValue::String(interval_id.to_string()),
            ]),]
        );

        let ack_res = service
            .exec_query(&format!(
                r#"QUEUE ACK "STANDALONE#queue:1" "result:{}""#,
                interval_id
            ))
            .await?;
        assert_eq!(
            ack_res.get_rows(),
            &vec![Row::new(vec![TableValue::Boolean(true)])]
        );

        let blocking_res = service
            .exec_query(r#"QUEUE RESULT_BLOCKING 5000 "STANDALONE#queue:1""#)
            .await?;
        assert_eq!(
            blocking_res.get_rows(),
            &vec![queue_result_row(
                &format!("result:{}", interval_id),
                &interval_id.to_string(),
                None
            )]
        );
    }
    Ok(())
}

async fn queue_list_v1(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let ctx_proc_a = SqlQueryContext::default().with_process_id(Some("process-a".to_string()));
    let ctx_proc_b = SqlQueryContext::default().with_process_id(Some("process-b".to_string()));

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
        .await?;
    assert_queue_add_columns(&add_response);

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_2" "payload2";"#)
        .await?;
    assert_queue_add_columns(&add_response);

    // Exclusive items owned by different processes
    let add_response = service
        .exec_query_with_context(
            ctx_proc_a.clone(),
            r#"QUEUE ADD EXCLUSIVE PRIORITY 1 "STANDALONE#queue:exclusive_key_a" "payload_a";"#,
        )
        .await?;
    assert_queue_add_columns(&add_response);

    let add_response = service
        .exec_query_with_context(
            ctx_proc_b.clone(),
            r#"QUEUE ADD EXCLUSIVE PRIORITY 1 "STANDALONE#queue:exclusive_key_b" "payload_b";"#,
        )
        .await?;
    assert_queue_add_columns(&add_response);

    {
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:queue_key_1""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload1".to_string()),
                TableValue::Null,
                TableValue::Int(3),
                // list of active keys
                TableValue::String("queue_key_1".to_string()),
                TableValue::String("1".to_string()),
            ]),]
        );
    }

    // List without process_id: should see only non-exclusive items
    let list_response = service
        .exec_query(r#"QUEUE LIST "STANDALONE#queue";"#)
        .await?;
    assert_eq!(
        list_response.get_columns(),
        &vec![
            Column::new("id".to_string(), ColumnType::String, 0),
            Column::new("queue_id".to_string(), ColumnType::String, 1),
            Column::new("status".to_string(), ColumnType::String, 2),
            Column::new("extra".to_string(), ColumnType::String, 3),
        ]
    );
    assert_eq!(
        list_response.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("queue_key_1".to_string()),
                TableValue::String("1".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null
            ]),
            Row::new(vec![
                TableValue::String("queue_key_2".to_string()),
                TableValue::String("2".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null
            ])
        ]
    );

    // List as process-a: should see non-exclusive items + exclusive_key_a only
    let list_response = service
        .exec_query_with_context(ctx_proc_a.clone(), r#"QUEUE LIST "STANDALONE#queue";"#)
        .await?;
    assert_eq!(
        list_response.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("queue_key_1".to_string()),
                TableValue::String("1".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null
            ]),
            Row::new(vec![
                TableValue::String("queue_key_2".to_string()),
                TableValue::String("2".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null
            ]),
            Row::new(vec![
                TableValue::String("exclusive_key_a".to_string()),
                TableValue::String("3".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null
            ])
        ]
    );

    // List as process-b: should see non-exclusive items + exclusive_key_b only
    let list_response = service
        .exec_query_with_context(ctx_proc_b.clone(), r#"QUEUE LIST "STANDALONE#queue";"#)
        .await?;
    assert_eq!(
        list_response.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("queue_key_1".to_string()),
                TableValue::String("1".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null
            ]),
            Row::new(vec![
                TableValue::String("queue_key_2".to_string()),
                TableValue::String("2".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null
            ]),
            Row::new(vec![
                TableValue::String("exclusive_key_b".to_string()),
                TableValue::String("4".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null
            ])
        ]
    );

    let list_response = service
        .exec_query(r#"QUEUE LIST WITH_PAYLOAD "STANDALONE#queue";"#)
        .await?;
    assert_eq!(
        list_response.get_columns(),
        &vec![
            Column::new("id".to_string(), ColumnType::String, 0),
            Column::new("queue_id".to_string(), ColumnType::String, 1),
            Column::new("status".to_string(), ColumnType::String, 2),
            Column::new("extra".to_string(), ColumnType::String, 3),
            Column::new("payload".to_string(), ColumnType::String, 4),
        ]
    );
    assert_eq!(
        list_response.get_rows(),
        &vec![
            Row::new(vec![
                TableValue::String("queue_key_1".to_string()),
                TableValue::String("1".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null,
                TableValue::String("payload1".to_string())
            ]),
            Row::new(vec![
                TableValue::String("queue_key_2".to_string()),
                TableValue::String("2".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null,
                TableValue::String("payload2".to_string())
            ])
        ]
    );
    Ok(())
}

async fn queue_full_workflow_v1(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("1".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(1)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 10 "STANDALONE#queue:queue_key_2" "payload2";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("2".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(2)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 100 "STANDALONE#queue:queue_key_3" "payload3";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("3".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(3)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 50 "STANDALONE#queue:queue_key_4" "payload4";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("4".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(4)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY -1 "STANDALONE#queue:queue_key_5" "payload5";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("5".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(5)
        ])]
    );

    // deduplication check
    {
        let add_response = service
            .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
            .await?;
        assert_queue_add_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("1".to_string()),
                TableValue::Boolean(false),
                TableValue::Int(5)
            ])]
        );
    }

    {
        let pending_response = service
            .exec_query(r#"QUEUE PENDING "STANDALONE#queue""#)
            .await?;
        assert_eq!(
            pending_response.get_columns(),
            &vec![
                Column::new("id".to_string(), ColumnType::String, 0),
                Column::new("queue_id".to_string(), ColumnType::String, 1),
                Column::new("status".to_string(), ColumnType::String, 2),
                Column::new("extra".to_string(), ColumnType::String, 3),
            ]
        );
        assert_eq!(
            pending_response.get_rows(),
            &vec![
                Row::new(vec![
                    TableValue::String("queue_key_3".to_string()),
                    TableValue::String("3".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_4".to_string()),
                    TableValue::String("4".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_2".to_string()),
                    TableValue::String("2".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_1".to_string()),
                    TableValue::String("1".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_5".to_string()),
                    TableValue::String("5".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
            ]
        );
    }

    {
        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(active_response.get_rows().len(), 0);
    }

    {
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:queue_key_3""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload3".to_string()),
                TableValue::Null,
                TableValue::Int(4),
                // list of active keys
                TableValue::String("queue_key_3".to_string()),
                TableValue::String("3".to_string()),
            ]),]
        );
    }

    {
        // concurrency limit
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:queue_key_4""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(retrieve_response.get_rows().len(), 0);
    }

    {
        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(
            active_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("queue_key_3".to_string()),
                TableValue::String("3".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null
            ]),]
        );
    }

    let service = Arc::new(service);

    {
        let service_to_move = service.clone();
        let blocking = async move {
            service_to_move
                .exec_query(r#"QUEUE RESULT_BLOCKING 5000 "STANDALONE#queue:queue_key_3""#)
                .await
                .unwrap()
        };

        let service_to_move = service.clone();
        let ack = async move {
            tokio::time::sleep(Duration::from_millis(1000)).await;

            let ack_result = service_to_move
                .exec_query(r#"QUEUE ACK "STANDALONE#queue:queue_key_3" "result:3""#)
                .await
                .unwrap();
            assert_eq!(
                ack_result.get_rows(),
                &vec![Row::new(vec![TableValue::Boolean(true)])]
            )
        };

        let (blocking_res, _ack_res) = join!(blocking, ack);
        assert_eq!(
            blocking_res.get_rows(),
            &vec![queue_result_row("result:3", "3", None)]
        );
    }

    // previous job was finished
    {
        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(active_response.get_rows().len(), 0);
    }

    // get
    {
        let get_response = service
            .exec_query(r#"QUEUE GET "STANDALONE#queue:queue_key_2""#)
            .await?;
        assert_eq!(
            get_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload2".to_string()),
                TableValue::Null
            ]),]
        );
    }

    // cancel job
    {
        let cancel_response = service
            .exec_query(r#"QUEUE CANCEL "STANDALONE#queue:queue_key_2""#)
            .await?;
        assert_eq!(
            cancel_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload2".to_string()),
                TableValue::Null
            ]),]
        );

        // assertion that job was removed
        let get_response = service
            .exec_query(r#"QUEUE GET "STANDALONE#queue:queue_key_2""#)
            .await?;
        assert_eq!(get_response.get_rows().len(), 0);
    }
    Ok(())
}

async fn queue_full_workflow_v2(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("1".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(1)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 10 "STANDALONE#queue:queue_key_2" "payload2";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("2".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(2)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 100 "STANDALONE#queue:queue_key_3" "payload3";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("3".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(3)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 50 "STANDALONE#queue:queue_key_4" "payload4";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("4".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(4)
        ])]
    );

    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY -1 "STANDALONE#queue:queue_key_5" "payload5";"#)
        .await?;
    assert_queue_add_columns(&add_response);
    assert_eq!(
        add_response.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("5".to_string()),
            TableValue::Boolean(true),
            TableValue::Int(5)
        ])]
    );

    // deduplication check
    {
        let add_response = service
            .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
            .await?;
        assert_queue_add_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("1".to_string()),
                TableValue::Boolean(false),
                TableValue::Int(5)
            ])]
        );
    }

    {
        let pending_response = service
            .exec_query(r#"QUEUE PENDING "STANDALONE#queue""#)
            .await?;
        assert_eq!(
            pending_response.get_columns(),
            &vec![
                Column::new("id".to_string(), ColumnType::String, 0),
                Column::new("queue_id".to_string(), ColumnType::String, 1),
                Column::new("status".to_string(), ColumnType::String, 2),
                Column::new("extra".to_string(), ColumnType::String, 3),
            ]
        );
        assert_eq!(
            pending_response.get_rows(),
            &vec![
                Row::new(vec![
                    TableValue::String("queue_key_3".to_string()),
                    TableValue::String("3".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_4".to_string()),
                    TableValue::String("4".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_2".to_string()),
                    TableValue::String("2".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_1".to_string()),
                    TableValue::String("1".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
                Row::new(vec![
                    TableValue::String("queue_key_5".to_string()),
                    TableValue::String("5".to_string()),
                    TableValue::String("pending".to_string()),
                    TableValue::Null
                ]),
            ]
        );
    }

    {
        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(active_response.get_rows().len(), 0);
    }

    {
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:queue_key_3""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload3".to_string()),
                TableValue::Null,
                TableValue::Int(4),
                // array of active keys
                TableValue::String("queue_key_3".to_string()),
                TableValue::String("3".to_string()),
            ]),]
        );
    }

    {
        // concurrency limit
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:queue_key_4""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(retrieve_response.get_rows().len(), 0);
    }

    {
        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(
            active_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("queue_key_3".to_string()),
                TableValue::String("3".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null
            ]),]
        );
    }

    let service = Arc::new(service);

    {
        let service_to_move = service.clone();
        let blocking = async move {
            service_to_move
                .exec_query(r#"QUEUE RESULT_BLOCKING 5000 3"#)
                .await
                .unwrap()
        };

        let service_to_move = service.clone();
        let ack = async move {
            tokio::time::sleep(Duration::from_millis(1000)).await;

            let ack_result = service_to_move
                .exec_query(r#"QUEUE ACK 3 "result:3""#)
                .await
                .unwrap();
            assert_eq!(
                ack_result.get_rows(),
                &vec![Row::new(vec![TableValue::Boolean(true)])]
            )
        };

        let (blocking_res, _ack_res) = join!(blocking, ack);
        assert_eq!(
            blocking_res.get_rows(),
            &vec![queue_result_row("result:3", "3", None)]
        );
    }

    // previous job was finished
    {
        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(active_response.get_rows().len(), 0);
    }

    // get
    {
        let get_response = service.exec_query(r#"QUEUE GET 2"#).await?;
        assert_eq!(
            get_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload2".to_string()),
                TableValue::Null
            ]),]
        );
    }

    // cancel job
    {
        let cancel_response = service.exec_query(r#"QUEUE CANCEL 2"#).await?;
        assert_eq!(
            cancel_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload2".to_string()),
                TableValue::Null
            ]),]
        );

        // assertion that job was removed
        let get_response = service.exec_query(r#"QUEUE GET 2"#).await?;
        assert_eq!(get_response.get_rows().len(), 0);
    }
    Ok(())
}

fn assert_queue_add_columns(response: &Arc<DataFrame>) {
    assert_eq!(
        response.get_columns(),
        &vec![
            Column::new("id".to_string(), ColumnType::String, 0),
            Column::new("added".to_string(), ColumnType::Boolean, 1),
            Column::new("pending".to_string(), ColumnType::Int, 2),
        ]
    );
}

fn queue_add_and_retrieve_row(
    id: &str,
    added: bool,
    pending: i64,
    active: Option<&str>,
    payload: Option<&str>,
) -> Row {
    let to_value =
        |v: Option<&str>| v.map_or(TableValue::Null, |v| TableValue::String(v.to_string()));

    Row::new(vec![
        TableValue::String(id.to_string()),
        TableValue::Boolean(added),
        TableValue::Int(pending),
        to_value(active),
        to_value(payload),
        // extra is always empty for a freshly added item
        TableValue::Null,
    ])
}

fn assert_queue_add_and_retrieve_columns(response: &Arc<DataFrame>) {
    assert_eq!(
        response.get_columns(),
        &vec![
            Column::new("id".to_string(), ColumnType::String, 0),
            Column::new("added".to_string(), ColumnType::Boolean, 1),
            Column::new("pending".to_string(), ColumnType::Int, 2),
            Column::new("active".to_string(), ColumnType::String, 3),
            Column::new("payload".to_string(), ColumnType::String, 4),
            Column::new("extra".to_string(), ColumnType::String, 5),
        ]
    );
}

fn assert_queue_add_and_get_id(response: &Arc<DataFrame>) -> Result<String, CubeError> {
    assert_queue_add_columns(response);

    match &response.get_rows()[0].values()[0] {
        TableValue::String(s) => Ok(s.clone()),
        other => Err(CubeError::internal(format!(
            "Expected string id, got: {:?}",
            other
        ))),
    }
}

fn assert_queue_retrieve_columns(response: &Arc<DataFrame>) {
    assert_eq!(
        response.get_columns(),
        &vec![
            Column::new("payload".to_string(), ColumnType::String, 0),
            Column::new("extra".to_string(), ColumnType::String, 1),
            Column::new("pending".to_string(), ColumnType::Int, 2),
            Column::new("active".to_string(), ColumnType::String, 3),
            Column::new("id".to_string(), ColumnType::String, 4),
        ]
    );
}

fn assert_queue_result_columns(response: &Arc<DataFrame>) {
    assert_eq!(
        response.get_columns(),
        &vec![
            Column::new("payload".to_string(), ColumnType::String, 0),
            Column::new("type".to_string(), ColumnType::String, 1),
            Column::new("id".to_string(), ColumnType::String, 2),
            Column::new("external_id".to_string(), ColumnType::String, 3),
        ]
    );
}

fn queue_result_row(payload: &str, id: &str, external_id: Option<&str>) -> Row {
    Row::new(vec![
        TableValue::String(payload.to_string()),
        TableValue::String("success".to_string()),
        TableValue::String(id.to_string()),
        if let Some(ext_id) = external_id {
            TableValue::String(ext_id.to_string())
        } else {
            TableValue::Null
        },
    ])
}

async fn queue_retrieve_extended(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:1" "payload1";"#)
        .await?;

    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:2" "payload2";"#)
        .await?;

    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:3" "payload3";"#)
        .await?;

    {
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:1""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload1".to_string()),
                TableValue::Null,
                TableValue::Int(2),
                TableValue::String("1".to_string()),
                TableValue::String("1".to_string()),
            ]),]
        );
    }

    {
        // concurrency limit
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE EXTENDED CONCURRENCY 1 "STANDALONE#queue:2""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::Null,
                TableValue::Null,
                TableValue::Int(2),
                TableValue::String("1".to_string()),
                TableValue::Null,
            ]),]
        );
    }

    {
        let retrieve_response = service
            .exec_query(r#"QUEUE RETRIEVE EXTENDED CONCURRENCY 2 "STANDALONE#queue:2""#)
            .await?;
        assert_queue_retrieve_columns(&retrieve_response);
        assert_eq!(
            retrieve_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload2".to_string()),
                TableValue::Null,
                TableValue::Int(1),
                TableValue::String("1,2".to_string()),
                TableValue::String("2".to_string()),
            ]),]
        );
    }
    Ok(())
}

async fn queue_add_and_retrieve(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    {
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE PRIORITY 1 "STANDALONE#queue:key1" "payload1" 1"#)
            .await?;
        assert_queue_add_and_retrieve_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row(
                "1",
                true,
                0,
                Some("key1"),
                Some("payload1")
            )]
        );
    }

    {
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE "STANDALONE#queue:key2" "payload2" 1"#)
            .await?;
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row("2", true, 1, Some("key1"), None)]
        );
    }

    {
        // The stored payload is returned, not the payload of this call
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE "STANDALONE#queue:key2" "payload2-dup" 2"#)
            .await?;
        assert_queue_add_and_retrieve_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row(
                "2",
                false,
                0,
                Some("key1,key2"),
                Some("payload2")
            )]
        );
    }

    {
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE "STANDALONE#queue:key1" "payload1" 5"#)
            .await?;
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row(
                "1",
                false,
                0,
                Some("key1,key2"),
                None
            )]
        );
    }

    {
        let add_response = service
            .exec_query(r#"QUEUE ADD "STANDALONE#queue:key3" "payload3""#)
            .await?;
        assert_queue_add_columns(&add_response);
    }

    {
        let pending_response = service
            .exec_query(r#"QUEUE PENDING "STANDALONE#queue""#)
            .await?;
        assert_eq!(
            pending_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("key3".to_string()),
                TableValue::String("3".to_string()),
                TableValue::String("pending".to_string()),
                TableValue::Null,
            ]),]
        );

        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(active_response.get_rows().len(), 2);
    }

    {
        // A claimed item can be acknowledged without an explicit QUEUE RETRIEVE
        let ack_response = service
            .exec_query(r#"QUEUE ACK "STANDALONE#queue:key1" "result1""#)
            .await?;
        assert_eq!(
            ack_response.get_rows(),
            &vec![Row::new(vec![TableValue::Boolean(true)])]
        );

        let result_response = service
            .exec_query(r#"QUEUE RESULT "STANDALONE#queue:key1""#)
            .await?;
        assert_queue_result_columns(&result_response);
        assert_eq!(
            result_response.get_rows(),
            &vec![queue_result_row("result1", "1", None)]
        );
    }

    Ok(())
}

async fn queue_add_and_retrieve_backlog(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    for id in 1..=2 {
        service
            .exec_query(&format!(
                r#"QUEUE ADD "STANDALONE#queue:key{}" "payload{}""#,
                id, id
            ))
            .await?;
    }

    {
        // Every concurrency slot is free, but claiming would leave the 2 pending items
        // a single slot to share, so the item takes its place in the backlog instead
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE "STANDALONE#queue:key3" "payload3" 2"#)
            .await?;
        assert_queue_add_and_retrieve_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row("3", true, 3, None, None)]
        );
    }

    {
        // A slot is left over for every one of the 3 pending items, nothing is jumped over
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE "STANDALONE#queue:key4" "payload4" 4"#)
            .await?;
        assert_queue_add_and_retrieve_columns(&add_response);
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row(
                "4",
                true,
                3,
                Some("key4"),
                Some("payload4")
            )]
        );
    }

    {
        // The very same budget declines the next claim, the slot it took is now busy
        let add_response = service
            .exec_query(r#"QUEUE ADD_AND_RETRIEVE "STANDALONE#queue:key5" "payload5" 4"#)
            .await?;
        assert_eq!(
            add_response.get_rows(),
            &vec![queue_add_and_retrieve_row("5", true, 4, Some("key4"), None)]
        );
    }

    {
        let pending_response = service
            .exec_query(r#"QUEUE PENDING "STANDALONE#queue""#)
            .await?;
        assert_eq!(pending_response.get_rows().len(), 4);

        let active_response = service
            .exec_query(r#"QUEUE ACTIVE "STANDALONE#queue""#)
            .await?;
        assert_eq!(
            active_response.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("key4".to_string()),
                TableValue::String("4".to_string()),
                TableValue::String("active".to_string()),
                TableValue::Null,
            ]),]
        );
    }

    Ok(())
}

async fn queue_ack_then_result_v1(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:5555" "payload1";"#)
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let ack_result = service
        .exec_query(r#"QUEUE ACK "STANDALONE#queue:5555" "result:5555""#)
        .await?;
    assert_eq!(
        ack_result.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    // double ack for result
    {
        let ack_result = service
            .exec_query(r#"QUEUE ACK "STANDALONE#queue:5555" "result:5555""#)
            .await?;
        assert_eq!(
            ack_result.get_rows(),
            &vec![Row::new(vec![TableValue::Boolean(false)])]
        );
    }

    // ack on unknown queue item
    {
        let ack_result = service
            .exec_query(r#"QUEUE ACK "STANDALONE#queue:123456" "result:5555""#)
            .await?;
        assert_eq!(
            ack_result.get_rows(),
            &vec![Row::new(vec![TableValue::Boolean(false)])]
        );
    }

    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:5555""#)
        .await?;

    assert_queue_result_columns(&result);
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:5555", &id, None)]
    );

    // second call should not return anything, because first call should remove result
    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:5555""#)
        .await?;

    assert_eq!(result.get_rows().len(), 0);
    Ok(())
}

async fn queue_ack_then_result_v2(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:5555" "payload1";"#)
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let ack_result = service
        .exec_query(&format!(r#"QUEUE ACK {} "result:5555""#, id))
        .await?;
    assert_eq!(
        ack_result.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    // double ack for result, should be restricted
    {
        let ack_result = service
            .exec_query(&format!(r#"QUEUE ACK {} "result:5555""#, id))
            .await?;
        assert_eq!(
            ack_result.get_rows(),
            &vec![Row::new(vec![TableValue::Boolean(false)])]
        );
    }

    // ack on unknown queue item
    {
        let ack_result = service.exec_query(r#"QUEUE ACK 10 "result:5555""#).await?;
        assert_eq!(
            ack_result.get_rows(),
            &vec![Row::new(vec![TableValue::Boolean(false)])]
        );
    }

    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:5555""#)
        .await?;

    assert_queue_result_columns(&result);
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:5555", &id, None)]
    );

    // second call should not return anything, because first call should mark result as ready to delete
    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:5555""#)
        .await?;
    assert_eq!(result.get_rows().len(), 0);

    tokio::time::sleep(Duration::new(1, 0)).await;

    // should return, because we use id
    let result = service
        .exec_query(&format!("QUEUE RESULT_BLOCKING 1000 {}", id))
        .await?;
    assert_queue_result_columns(&result);
    assert_eq!(result.get_rows().len(), 1);
    Ok(())
}

async fn queue_ack_then_result_v2_by_id(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:12345" "payload1";"#)
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let ack_result = service
        .exec_query(&format!(r#"QUEUE ACK {} "result:12345""#, id))
        .await?;
    assert_eq!(
        ack_result.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    // QUEUE RESULT by id (v2 read-many semantics) — returns result
    let result = service.exec_query(&format!("QUEUE RESULT {}", id)).await?;
    assert_queue_result_columns(&result);
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:12345", &id, None)]
    );

    // second call by id should still return result (read-many, not consume-once)
    let result = service.exec_query(&format!("QUEUE RESULT {}", id)).await?;
    assert_eq!(result.get_rows().len(), 1);

    // by path (v1 consume-once) should also still work and consume the result
    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:12345""#)
        .await?;
    assert_eq!(result.get_rows().len(), 1);

    // after path-based consume, path lookup returns nothing
    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:12345""#)
        .await?;
    assert_eq!(result.get_rows().len(), 0);

    // but id-based lookup still returns (read-many semantics)
    let result = service.exec_query(&format!("QUEUE RESULT {}", id)).await?;
    assert_eq!(result.get_rows().len(), 1);

    Ok(())
}

async fn queue_orphaned_timeout(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    // CI is super slow, sometimes it can takes up to 1 second to bootstrap Cache Store
    // let's warmup it
    service.exec_query(r#"SYS CACHESTORE HEALTHCHECK;"#).await?;

    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
        .await?;

    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_2" "payload2";"#)
        .await?;

    let res = service
        .exec_query(r#"QUEUE TO_CANCEL 1000 1000 "STANDALONE#queue";"#)
        .await?;
    assert_eq!(res.len(), 0);

    // only active jobs can be orphaned
    // RETRIEVE updates heartbeat
    {
        service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 2 "STANDALONE#queue:queue_key_1""#)
            .await?;

        service
            .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 2 "STANDALONE#queue:queue_key_2""#)
            .await?;
    }

    tokio::time::sleep(Duration::from_millis(1000)).await;

    service
        .exec_query(r#"QUEUE HEARTBEAT "STANDALONE#queue:queue_key_2";"#)
        .await?;

    let res = service
        .exec_query(r#"QUEUE TO_CANCEL 1000 1000 "STANDALONE#queue""#)
        .await?;
    assert_eq!(
        res.get_columns(),
        &vec![
            Column::new("id".to_string(), ColumnType::String, 0),
            Column::new("queue_id".to_string(), ColumnType::String, 1),
        ]
    );
    assert_eq!(
        res.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("queue_key_1".to_string()),
            TableValue::String("1".to_string()),
        ]),]
    );

    // awaiting for expiring heart beat for queue:2
    tokio::time::sleep(Duration::from_millis(1000)).await;

    let res = service
        .exec_query(r#"QUEUE TO_CANCEL 1000 1000 "STANDALONE#queue""#)
        .await?;
    assert_eq!(res.len(), 2);
    Ok(())
}

async fn queue_heartbeat_by_path(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:1" "payload1";"#)
        .await?;

    let res = service
        .exec_query(r#"SELECT heartbeat FROM system.queue WHERE prefix = 'STANDALONE#queue'"#)
        .await?;
    assert_eq!(res.get_rows(), &vec![Row::new(vec![TableValue::Null,]),]);

    service
        .exec_query(r#"QUEUE HEARTBEAT "STANDALONE#queue:1";"#)
        .await?;

    let res = service
        .exec_query(r#"SELECT heartbeat FROM system.queue WHERE prefix = 'STANDALONE#queue'"#)
        .await?;

    let row = res.get_rows().first().expect("empty result");
    match row.values().first().expect("empty row") {
        TableValue::Timestamp(_) => {}
        other => panic!("heartbeat must be a timestamp type, actual: {:?}", other),
    }
    Ok(())
}

async fn queue_heartbeat_by_id(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:1" "payload1";"#)
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let res = service
        .exec_query(r#"SELECT heartbeat FROM system.queue WHERE prefix = 'STANDALONE#queue'"#)
        .await?;
    assert_eq!(res.get_rows(), &vec![Row::new(vec![TableValue::Null,]),]);

    service
        .exec_query(&format!("QUEUE HEARTBEAT {};", id))
        .await?;

    let res = service
        .exec_query(r#"SELECT heartbeat FROM system.queue WHERE prefix = 'STANDALONE#queue'"#)
        .await?;

    let row = res.get_rows().first().expect("empty result");
    match row.values().first().expect("empty row") {
        TableValue::Timestamp(_) => {}
        other => panic!("heartbeat must be a timestamp type, actual: {:?}", other),
    }
    Ok(())
}

async fn queue_merge_extra_by_path(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:1" "payload1";"#)
        .await?;

    // extra must be empty after creation
    {
        let res = service
            .exec_query(r#"QUEUE GET "STANDALONE#queue:1";"#)
            .await?;
        assert_eq!(
            res.get_columns(),
            &vec![
                Column::new("payload".to_string(), ColumnType::String, 0),
                Column::new("extra".to_string(), ColumnType::String, 1),
            ]
        );
        assert_eq!(
            res.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload1".to_string()),
                TableValue::Null
            ]),]
        );
    }

    service
        .exec_query(r#"QUEUE MERGE_EXTRA "STANDALONE#queue:1" '{"first": true}';"#)
        .await?;

    // extra should contains first field
    {
        let res = service
            .exec_query(r#"QUEUE GET "STANDALONE#queue:1";"#)
            .await?;
        assert_eq!(
            res.get_columns(),
            &vec![
                Column::new("payload".to_string(), ColumnType::String, 0),
                Column::new("extra".to_string(), ColumnType::String, 1),
            ]
        );
        assert_eq!(
            res.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload1".to_string()),
                TableValue::String("{\"first\": true}".to_string())
            ]),]
        );
    }
    Ok(())
}

async fn queue_merge_extra_by_id(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:1" "payload1";"#)
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    // extra must be empty after creation
    {
        let res = service.exec_query(&format!("QUEUE GET {};", id)).await?;
        assert_eq!(
            res.get_columns(),
            &vec![
                Column::new("payload".to_string(), ColumnType::String, 0),
                Column::new("extra".to_string(), ColumnType::String, 1),
            ]
        );
        assert_eq!(
            res.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload1".to_string()),
                TableValue::Null
            ]),]
        );
    }

    service
        .exec_query(&format!(r#"QUEUE MERGE_EXTRA {} '{{"first": true}}';"#, id))
        .await?;

    // extra should contains first field
    {
        let res = service.exec_query(&format!("QUEUE GET {};", id)).await?;
        assert_eq!(
            res.get_columns(),
            &vec![
                Column::new("payload".to_string(), ColumnType::String, 0),
                Column::new("extra".to_string(), ColumnType::String, 1),
            ]
        );
        assert_eq!(
            res.get_rows(),
            &vec![Row::new(vec![
                TableValue::String("payload1".to_string()),
                TableValue::String("{\"first\": true}".to_string())
            ]),]
        );
    }
    Ok(())
}

async fn queue_multiple_result_blocking(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:12345" "payload1";"#)
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let service = Arc::new(service);

    {
        let id_clone = id.clone();
        let service_to_move = service.clone();
        let blocking1 = async move {
            service_to_move
                .exec_query(r#"QUEUE RESULT_BLOCKING 5000 "STANDALONE#queue:12345""#)
                .await
                .unwrap()
        };

        let service_to_move = service.clone();
        let blocking2 = async move {
            service_to_move
                .exec_query(r#"QUEUE RESULT_BLOCKING 5000 "STANDALONE#queue:12345""#)
                .await
                .unwrap()
        };

        let service_to_move = service.clone();
        let ack = async move {
            tokio::time::sleep(Duration::from_millis(1000)).await;

            let ack_result = service_to_move
                .exec_query(r#"QUEUE ACK "STANDALONE#queue:12345" "result:12345""#)
                .await
                .unwrap();
            assert_eq!(
                ack_result.get_rows(),
                &vec![Row::new(vec![TableValue::Boolean(true)])]
            )
        };

        let (blocking1_res, blocking2_res, _ack_res) = join!(blocking1, blocking2, ack);
        assert_queue_result_columns(&blocking1_res);
        assert_eq!(
            blocking1_res.get_rows(),
            &vec![queue_result_row("result:12345", &id_clone, None)]
        );

        assert_queue_result_columns(&blocking2_res);
        assert_eq!(
            blocking2_res.get_rows(),
            &vec![queue_result_row("result:12345", &id_clone, None)]
        );
    }
    Ok(())
}

async fn queue_custom_orphaned(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:queue_key_1" "payload1";"#)
        .await?;

    service
        .exec_query(
            r#"QUEUE ADD PRIORITY 1 ORPHANED 60 "STANDALONE#queue:queue_key_2" "payload1";"#,
        )
        .await?;

    tokio::time::sleep(Duration::new(1, 0)).await;

    let res = service
        .exec_query(r#"QUEUE TO_CANCEL 100 100 "STANDALONE#queue""#)
        .await?;
    assert_eq!(
        res.get_columns(),
        &vec![
            Column::new("id".to_string(), ColumnType::String, 0),
            Column::new("queue_id".to_string(), ColumnType::String, 1),
        ]
    );

    assert_eq!(
        res.get_rows(),
        &vec![Row::new(vec![
            TableValue::String("queue_key_1".to_string()),
            TableValue::String("1".to_string()),
        ]),]
    );
    Ok(())
}

async fn queue_full_workflow_v2_with_external_id(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(
            r#"QUEUE ADD PRIORITY 1 EXTERNAL_ID 'ext-v2' "STANDALONE#queue:ext_v2" "payload_ext_v2";"#,
        )
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let retrieve_response = service
        .exec_query(r#"QUEUE RETRIEVE CONCURRENCY 1 "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_eq!(retrieve_response.get_rows().len(), 1);

    let ack_result = service
        .exec_query(&format!(r#"QUEUE ACK {} "result:ext_v2""#, id))
        .await?;
    assert_eq!(
        ack_result.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    // QUEUE RESULT EXTERNAL_ID "ext-v2" "path" — found by external_id (read-many)
    let result = service
        .exec_query(r#"QUEUE RESULT EXTERNAL_ID "ext-v2" "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_queue_result_columns(&result);
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:ext_v2", &id, Some("ext-v2"))]
    );

    // Second call still returns result (read-many semantics for external_id lookup)
    let result = service
        .exec_query(r#"QUEUE RESULT EXTERNAL_ID "ext-v2" "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:ext_v2", &id, Some("ext-v2"))]
    );

    // QUEUE RESULT by path still works (marks deleted on first read)
    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:ext_v2", &id, Some("ext-v2"))]
    );

    // QUEUE RESULT by path returns empty after mark-delete
    let result = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_eq!(result.get_rows().len(), 0);

    // QUEUE RESULT by id still works (read-many)
    let result = service.exec_query(&format!("QUEUE RESULT {}", id)).await?;
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:ext_v2", &id, Some("ext-v2"))]
    );

    // QUEUE RESULT by external_id still works after path mark-delete (read-many)
    let result = service
        .exec_query(r#"QUEUE RESULT EXTERNAL_ID "ext-v2" "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:ext_v2", &id, Some("ext-v2"))]
    );

    // Unknown external_id returns empty
    let result = service
        .exec_query(r#"QUEUE RESULT EXTERNAL_ID "unknown-ext" "STANDALONE#queue:ext_v2""#)
        .await?;
    assert_eq!(result.get_rows().len(), 0);

    Ok(())
}

async fn queue_add_external_id_max_len(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    let max_id = "x".repeat(QUEUE_ITEM_EXTERNAL_ID_MAX_LEN);
    let too_long_id = "x".repeat(QUEUE_ITEM_EXTERNAL_ID_MAX_LEN + 1);

    let add_response = service
        .exec_query(&format!(
            r#"QUEUE ADD EXTERNAL_ID '{}' "STANDALONE#queue:ext_max_len" "payload_max_len""#,
            max_id
        ))
        .await?;
    assert_queue_add_and_get_id(&add_response)?;

    for query in [
        format!(
            r#"QUEUE ADD EXTERNAL_ID '{}' "STANDALONE#queue:ext_too_long" "payload_too_long""#,
            too_long_id
        ),
        format!(
            r#"QUEUE ADD_AND_RETRIEVE EXTERNAL_ID '{}' "STANDALONE#queue:ext_too_long" "payload_too_long" 1"#,
            too_long_id
        ),
        format!(
            r#"QUEUE RESULT EXTERNAL_ID '{}' "STANDALONE#queue:ext_too_long""#,
            too_long_id
        ),
    ] {
        let err = service.exec_query(&query).await.unwrap_err();
        assert!(
            err.to_string()
                .contains("external_id exceeds maximum allowed length"),
            "unexpected error for {}: {}",
            query,
            err
        );
    }

    // The rejected items were never written, only the max-length one
    let pending = service
        .exec_query(r#"QUEUE PENDING "STANDALONE#queue""#)
        .await?;
    assert_eq!(pending.get_rows().len(), 1);

    Ok(())
}

async fn queue_result_by_id_external_id_mismatch(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    let add_response = service
        .exec_query(
            r#"QUEUE ADD PRIORITY 1 EXTERNAL_ID 'ext-match' "STANDALONE#queue:mismatch_test" "payload_mismatch";"#,
        )
        .await?;
    let id = assert_queue_add_and_get_id(&add_response)?;

    let ack_result = service
        .exec_query(&format!(r#"QUEUE ACK {} "result:mismatch""#, id))
        .await?;
    assert_eq!(
        ack_result.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    let result = service
        .exec_query(&format!(r#"QUEUE RESULT EXTERNAL_ID "ext-match" {}"#, id))
        .await?;
    assert_queue_result_columns(&result);
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:mismatch", &id, Some("ext-match"))]
    );

    // External_id allows many reads
    let result = service
        .exec_query(&format!(r#"QUEUE RESULT EXTERNAL_ID "ext-match" {}"#, id))
        .await?;
    assert_queue_result_columns(&result);
    assert_eq!(
        result.get_rows(),
        &vec![queue_result_row("result:mismatch", &id, Some("ext-match"))]
    );

    let err = service
        .exec_query(&format!(r#"QUEUE RESULT EXTERNAL_ID "wrong-ext" {}"#, id))
        .await;
    assert!(err.is_err(), "Expected error for external_id mismatch");
    let err_msg = err.unwrap_err().message;
    assert!(
        err_msg.contains("external_id mismatch"),
        "Error should mention external_id mismatch, got: {}",
        err_msg
    );

    Ok(())
}

async fn queue_result_ack_multiple_with_external_id(
    service: Box<dyn SqlClient>,
) -> Result<(), CubeError> {
    // 2 items with external_id
    let add1 = service
        .exec_query(
            r#"QUEUE ADD PRIORITY 1 EXTERNAL_ID 'ext-multi-1' "STANDALONE#queue:multi1" "payload1";"#,
        )
        .await?;
    let id1 = assert_queue_add_and_get_id(&add1)?;

    let add2 = service
        .exec_query(
            r#"QUEUE ADD PRIORITY 1 EXTERNAL_ID 'ext-multi-2' "STANDALONE#queue:multi2" "payload2";"#,
        )
        .await?;
    let id2 = assert_queue_add_and_get_id(&add2)?;

    // 2 items without external_id
    let add3 = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:multi3" "payload3";"#)
        .await?;
    let id3 = assert_queue_add_and_get_id(&add3)?;

    let add4 = service
        .exec_query(r#"QUEUE ADD PRIORITY 1 "STANDALONE#queue:multi4" "payload4";"#)
        .await?;
    let id4 = assert_queue_add_and_get_id(&add4)?;

    // ACK all 4 items
    let ack1 = service
        .exec_query(r#"QUEUE ACK "STANDALONE#queue:multi1" "result:multi1""#)
        .await?;
    assert_eq!(
        ack1.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    let ack2 = service
        .exec_query(r#"QUEUE ACK "STANDALONE#queue:multi2" "result:multi2""#)
        .await?;
    assert_eq!(
        ack2.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    let ack3 = service
        .exec_query(r#"QUEUE ACK "STANDALONE#queue:multi3" "result:multi3""#)
        .await?;
    assert_eq!(
        ack3.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    let ack4 = service
        .exec_query(r#"QUEUE ACK "STANDALONE#queue:multi4" "result:multi4""#)
        .await?;
    assert_eq!(
        ack4.get_rows(),
        &vec![Row::new(vec![TableValue::Boolean(true)])]
    );

    // Verify results for items with external_id
    let result1 = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:multi1""#)
        .await?;
    assert_queue_result_columns(&result1);
    assert_eq!(
        result1.get_rows(),
        &vec![queue_result_row("result:multi1", &id1, Some("ext-multi-1"))]
    );

    let result2 = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:multi2""#)
        .await?;
    assert_queue_result_columns(&result2);
    assert_eq!(
        result2.get_rows(),
        &vec![queue_result_row("result:multi2", &id2, Some("ext-multi-2"))]
    );

    // Verify results for items without external_id
    let result3 = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:multi3""#)
        .await?;
    assert_queue_result_columns(&result3);
    assert_eq!(
        result3.get_rows(),
        &vec![queue_result_row("result:multi3", &id3, None)]
    );

    let result4 = service
        .exec_query(r#"QUEUE RESULT "STANDALONE#queue:multi4""#)
        .await?;
    assert_queue_result_columns(&result4);
    assert_eq!(
        result4.get_rows(),
        &vec![queue_result_row("result:multi4", &id4, None)]
    );

    Ok(())
}

async fn sys_cachestore_info(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.migration_run_next_query();
    service.exec_query("SYS CACHESTORE INFO").await?;
    Ok(())
}

async fn sys_drop_cache(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.migration_run_next_query();
    service.exec_query(r#"SYS DROP QUERY CACHE;"#).await?;

    service.migration_run_next_query();
    service.exec_query(r#"SYS DROP CACHE;"#).await?;
    Ok(())
}

async fn sys_metastore_healthcheck(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.migration_run_next_query();
    service.exec_query(r#"SYS METASTORE HEALTHCHECK;"#).await?;
    Ok(())
}

async fn sys_cachestore_healthcheck(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.migration_run_next_query();
    service.exec_query(r#"SYS CACHESTORE HEALTHCHECK;"#).await?;
    Ok(())
}

async fn join_multi_partition_small(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await.unwrap();
    service
        .exec_query("CREATE TABLE s.orders (order_id int, customer_id int, amount int)")
        .await?;
    service
        .exec_query("CREATE INDEX orders_by_cid ON s.orders (customer_id)")
        .await?;
    service
        .exec_query("CREATE TABLE s.customers (customer_id int, customer_name text)")
        .await?;

    // Insert 60 orders in batches of 10 to trigger compaction/split cycles.
    // customer_id = id % 5 (5 distinct customers), amount = id * 7.
    for batch in 0..6 {
        let values: Vec<String> = (0..10)
            .map(|i| {
                let id = batch * 10 + i;
                let cid = id % 5;
                let amount = id * 7;
                format!("({}, {}, {})", id, cid, amount)
            })
            .collect();
        service
            .exec_query(&format!(
                "INSERT INTO s.orders (order_id, customer_id, amount) VALUES {}",
                values.join(", ")
            ))
            .await?;
    }

    // Insert 10 customers in 1 batch.
    let values: Vec<String> = (0..5)
        .map(|cid| format!("({}, 'customer_{}')", cid, cid))
        .collect();
    service
        .exec_query(&format!(
            "INSERT INTO s.customers (customer_id, customer_name) VALUES {}",
            values.join(", ")
        ))
        .await?;

    // Wait for compaction/split to complete.
    tokio::time::sleep(Duration::from_secs(4)).await;

    // Per-customer sums:
    // cid 0: ids 0,5,10,15,20,25,30,35,40,45,50,55 -> sum(i*7) = 7*(0+5+10+15+20+25+30+35+40+45+50+55) = 7*330 = 2310
    // cid 1: ids 1,6,11,16,21,26,31,36,41,46,51,56 -> 7*(1+6+11+16+21+26+31+36+41+46+51+56) = 7*342 = 2394
    // cid 2: ids 2,7,12,17,22,27,32,37,42,47,52,57 -> 7*354 = 2478
    // cid 3: ids 3,8,13,18,23,28,33,38,43,48,53,58 -> 7*366 = 2562
    // cid 4: ids 4,9,14,19,24,29,34,39,44,49,54,59 -> 7*378 = 2646
    let result = service
        .exec_query(
            "SELECT customer_name, sum(amount) FROM s.orders o \
             LEFT JOIN s.customers c ON o.customer_id = c.customer_id \
             GROUP BY 1 ORDER BY 2 DESC",
        )
        .await?;

    assert_eq!(
        to_rows(&result),
        vec![
            vec![
                TableValue::String("customer_4".to_string()),
                TableValue::Int(2646)
            ],
            vec![
                TableValue::String("customer_3".to_string()),
                TableValue::Int(2562)
            ],
            vec![
                TableValue::String("customer_2".to_string()),
                TableValue::Int(2478)
            ],
            vec![
                TableValue::String("customer_1".to_string()),
                TableValue::Int(2394)
            ],
            vec![
                TableValue::String("customer_0".to_string()),
                TableValue::Int(2310)
            ],
        ]
    );

    // Verify total: 2310+2394+2478+2562+2646 = 12390
    let total_result = service
        .exec_query(
            "SELECT sum(amount) FROM s.orders o \
             LEFT JOIN s.customers c ON o.customer_id = c.customer_id",
        )
        .await
        .unwrap();
    assert_eq!(to_rows(&total_result), vec![vec![TableValue::Int(12390)]]);
    Ok(())
}

async fn join_multi_partition_large(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA s").await.unwrap();
    service
        .exec_query(
            "CREATE TABLE s.orders (order_id int, customer_id int, product_id int, amount int)",
        )
        .await?;
    service
        .exec_query("CREATE INDEX orders_by_cid ON s.orders (customer_id)")
        .await?;
    service
        .exec_query("CREATE TABLE s.customers (customer_id int, customer_name text)")
        .await?;
    service
        .exec_query("CREATE TABLE s.products (product_id int, product_name text)")
        .await?;

    // Insert 150 orders in batches of 10.
    // customer_id = id % 3 (3 customers), product_id = id % 2 (2 products),
    // amount = cid * 10 + pid — easy to verify per-group sums.
    for batch in 0..15 {
        let values: Vec<String> = (0..10)
            .map(|i| {
                let id = batch * 10 + i;
                let cid = id % 3;
                let pid = id % 2;
                let amount = cid * 10 + pid;
                format!("({}, {}, {}, {})", id, cid, pid, amount)
            })
            .collect();
        service
            .exec_query(&format!(
                "INSERT INTO s.orders (order_id, customer_id, product_id, amount) VALUES {}",
                values.join(", ")
            ))
            .await?;
    }

    // Insert 30 customers in batches of 10 (more than needed, extras won't match).
    for batch in 0..3 {
        let values: Vec<String> = (0..10)
            .map(|i| {
                let cid = batch * 10 + i;
                format!("({}, 'customer_{}')", cid, cid)
            })
            .collect();
        service
            .exec_query(&format!(
                "INSERT INTO s.customers (customer_id, customer_name) VALUES {}",
                values.join(", ")
            ))
            .await?;
    }

    // Insert 20 products in 2 batches (more than needed, extras won't match).
    for batch in 0..2 {
        let values: Vec<String> = (0..10)
            .map(|i| {
                let pid = batch * 10 + i;
                format!("({}, 'product_{}')", pid, pid)
            })
            .collect();
        service
            .exec_query(&format!(
                "INSERT INTO s.products (product_id, product_name) VALUES {}",
                values.join(", ")
            ))
            .await?;
    }

    // Wait for compaction/split to complete.
    tokio::time::sleep(Duration::from_secs(4)).await;

    // 3-table LEFT JOIN grouped by customer and product.
    // 150 orders: cid = id%3, pid = id%2, amount = cid*10 + pid.
    // Each (cid, pid) group has 25 rows (150/6), sum = 25 * (cid*10 + pid).
    // (0,0): 25*0=0, (0,1): 25*1=25, (1,0): 25*10=250,
    // (1,1): 25*11=275, (2,0): 25*20=500, (2,1): 25*21=525.
    let result = service
        .exec_query(
            "SELECT customer_name, product_name, sum(amount), count(*) FROM s.orders o \
             LEFT JOIN s.customers c ON o.customer_id = c.customer_id \
             LEFT JOIN s.products p ON o.product_id = p.product_id \
             GROUP BY 1, 2 ORDER BY 1, 2",
        )
        .await?;

    assert_eq!(
        to_rows(&result),
        vec![
            vec![
                TableValue::String("customer_0".to_string()),
                TableValue::String("product_0".to_string()),
                TableValue::Int(0),
                TableValue::Int(25),
            ],
            vec![
                TableValue::String("customer_0".to_string()),
                TableValue::String("product_1".to_string()),
                TableValue::Int(25),
                TableValue::Int(25),
            ],
            vec![
                TableValue::String("customer_1".to_string()),
                TableValue::String("product_0".to_string()),
                TableValue::Int(250),
                TableValue::Int(25),
            ],
            vec![
                TableValue::String("customer_1".to_string()),
                TableValue::String("product_1".to_string()),
                TableValue::Int(275),
                TableValue::Int(25),
            ],
            vec![
                TableValue::String("customer_2".to_string()),
                TableValue::String("product_0".to_string()),
                TableValue::Int(500),
                TableValue::Int(25),
            ],
            vec![
                TableValue::String("customer_2".to_string()),
                TableValue::String("product_1".to_string()),
                TableValue::Int(525),
                TableValue::Int(25),
            ],
        ]
    );

    // Verify count - all 150 orders should be present (LEFT JOIN keeps all left rows).
    let count_result = service
        .exec_query(
            "SELECT count(*) FROM s.orders o \
             LEFT JOIN s.customers c ON o.customer_id = c.customer_id \
             LEFT JOIN s.products p ON o.product_id = p.product_id",
        )
        .await?;
    assert_eq!(to_rows(&count_result), vec![vec![TableValue::Int(150)]]);
    Ok(())
}

async fn repartition_multi_node_consistency(service: Box<dyn SqlClient>) -> Result<(), CubeError> {
    service.exec_query("CREATE SCHEMA foo").await?;
    service
        .exec_query("CREATE TABLE foo.numbers (num int)")
        .await?;

    let n: i64 = 200;
    for i in 0..n {
        service
            .exec_query(&format!("INSERT INTO foo.numbers (num) VALUES ({})", i))
            .await?;
    }

    // Wait until the table has split and repartition jobs have drained every
    // inactive parent partition (no active chunk left on an inactive partition),
    // while the full row set stays readable throughout.
    let mut stabilized = false;
    let (mut active_parts, mut pending, mut count) = (0i64, true, 0i64);
    for _ in 0..150 {
        let active_parts_df = service
            .exec_query("SELECT count(*) FROM system.partitions WHERE active = true")
            .await?;
        active_parts = scalar_i64(&active_parts_df);

        let inactive = service
            .exec_query("SELECT id FROM system.partitions WHERE active = false")
            .await?;
        let inactive_ids: HashSet<i64> = to_rows(&inactive)
            .iter()
            .filter_map(|r| match &r[0] {
                TableValue::Int(v) => Some(*v),
                _ => None,
            })
            .collect();

        let active_chunks = service
            .exec_query("SELECT partition_id FROM system.chunks WHERE active = true")
            .await?;
        pending = to_rows(&active_chunks).iter().any(|r| match &r[0] {
            TableValue::Int(v) => inactive_ids.contains(v),
            _ => false,
        });

        let count_df = service
            .exec_query("SELECT count(*) FROM foo.numbers")
            .await?;
        count = scalar_i64(&count_df);

        if active_parts > 1 && !pending && count == n {
            stabilized = true;
            break;
        }
        tokio::time::sleep(Duration::from_millis(100)).await;
    }
    assert!(
        stabilized,
        "repartition did not stabilize: active_partitions={}, pending_repartition={}, count={} (want active>1, pending=false, count={})",
        active_parts, pending, count, n
    );

    let sum = service
        .exec_query("SELECT sum(num) FROM foo.numbers")
        .await?;
    assert_eq!(to_rows(&sum), vec![vec![TableValue::Int(n * (n - 1) / 2)]]);

    Ok(())
}

fn scalar_i64(d: &DataFrame) -> i64 {
    match d.get_rows().get(0).map(|r| &r.values()[0]) {
        Some(TableValue::Int(v)) => *v,
        _ => 0,
    }
}

pub fn to_rows(d: &DataFrame) -> Vec<Vec<TableValue>> {
    d.get_rows()
        .iter()
        .map(|r| r.values().clone())
        .collect_vec()
}

fn dec5(i: i64) -> Decimal {
    dec5f1(i, 0)
}

fn dec5f1(i: i64, f: u64) -> Decimal {
    assert!(f < 10);
    let f = if i < 0 { -(f as i64) } else { f as i64 };
    Decimal::new((i * 100_000 + 10_000 * f) as i128)
}
