From 88706177a9e9bd5ab0154e823e5e82ae7515b371 Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Tue, 19 Mar 2024 21:38:12 +0800 Subject: [PATCH 1/5] This is an automated cherry-pick of #51903 Signed-off-by: ti-chi-bot --- pkg/planner/core/casetest/BUILD.bazel | 32 ++ pkg/planner/core/casetest/integration_test.go | 419 ++++++++++++++++++ planner/core/optimizer.go | 20 + 3 files changed, 471 insertions(+) create mode 100644 pkg/planner/core/casetest/BUILD.bazel create mode 100644 pkg/planner/core/casetest/integration_test.go diff --git a/pkg/planner/core/casetest/BUILD.bazel b/pkg/planner/core/casetest/BUILD.bazel new file mode 100644 index 0000000000000..a9cac84c4ba66 --- /dev/null +++ b/pkg/planner/core/casetest/BUILD.bazel @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_test") + +go_test( + name = "casetest_test", + timeout = "moderate", + srcs = [ + "integration_test.go", + "main_test.go", + "plan_test.go", + "stats_test.go", + "tiflash_selection_late_materialization_test.go", + ], + data = glob(["testdata/**"]), + flaky = True, + shard_count = 20, + deps = [ + "//pkg/domain", + "//pkg/parser", + "//pkg/parser/model", + "//pkg/planner/core", + "//pkg/planner/property", + "//pkg/testkit", + "//pkg/testkit/testdata", + "//pkg/testkit/testmain", + "//pkg/testkit/testsetup", + "//pkg/util/hint", + "//pkg/util/plancodec", + "@com_github_pingcap_failpoint//:failpoint", + "@com_github_stretchr_testify//require", + "@org_uber_go_goleak//:goleak", + ], +) diff --git a/pkg/planner/core/casetest/integration_test.go b/pkg/planner/core/casetest/integration_test.go new file mode 100644 index 0000000000000..09e5c3802cbd5 --- /dev/null +++ b/pkg/planner/core/casetest/integration_test.go @@ -0,0 +1,419 @@ +// Copyright 2023 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package casetest + +import ( + "strings" + "testing" + + "github.com/pingcap/failpoint" + "github.com/pingcap/tidb/pkg/domain" + "github.com/pingcap/tidb/pkg/parser/model" + "github.com/pingcap/tidb/pkg/testkit" + "github.com/pingcap/tidb/pkg/testkit/testdata" + "github.com/stretchr/testify/require" +) + +func TestVerboseExplain(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set tidb_cost_model_version=2") + tk.MustExec(`set tidb_opt_limit_push_down_threshold=0`) + tk.MustExec("drop table if exists t1, t2, t3") + tk.MustExec("create table t1(a int, b int)") + tk.MustExec("create table t2(a int, b int)") + tk.MustExec("create table t3(a int, b int, index c(b))") + tk.MustExec("insert into t1 values(1,2)") + tk.MustExec("insert into t1 values(3,4)") + tk.MustExec("insert into t1 values(5,6)") + tk.MustExec("insert into t2 values(1,2)") + tk.MustExec("insert into t2 values(3,4)") + tk.MustExec("insert into t2 values(5,6)") + tk.MustExec("insert into t3 values(1,2)") + tk.MustExec("insert into t3 values(3,4)") + tk.MustExec("insert into t3 values(5,6)") + tk.MustExec("analyze table t1") + tk.MustExec("analyze table t2") + tk.MustExec("analyze table t3") + + // Default RPC encoding may cause statistics explain result differ and then the test unstable. + tk.MustExec("set @@tidb_enable_chunk_rpc = on") + + // Create virtual tiflash replica info. + dom := domain.GetDomain(tk.Session()) + is := dom.InfoSchema() + db, exists := is.SchemaByName(model.NewCIStr("test")) + require.True(t, exists) + for _, tblInfo := range db.Tables { + if tblInfo.Name.L == "t1" || tblInfo.Name.L == "t2" { + tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ + Count: 1, + Available: true, + } + } + } + + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + res := tk.MustQuery(tt) + res.Check(testkit.Rows(output[i].Plan...)) + } +} + +func TestIsolationReadTiFlashNotChoosePointGet(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int, b int, primary key (a))") + + // Create virtual tiflash replica info. + dom := domain.GetDomain(tk.Session()) + is := dom.InfoSchema() + db, exists := is.SchemaByName(model.NewCIStr("test")) + require.True(t, exists) + for _, tblInfo := range db.Tables { + tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ + Count: 1, + Available: true, + } + } + + tk.MustExec("set @@session.tidb_isolation_read_engines=\"tiflash\"") + var input []string + var output []struct { + SQL string + Result []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Result = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + tk.MustQuery(tt).Check(testkit.Rows(output[i].Result...)) + } +} + +func TestIsolationReadDoNotFilterSystemDB(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("set @@tidb_isolation_read_engines = \"tiflash\"") + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + res := tk.MustQuery(tt) + res.Check(testkit.Rows(output[i].Plan...)) + } +} + +func TestMergeContinuousSelections(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists ts") + tk.MustExec("create table ts (col_char_64 char(64), col_varchar_64_not_null varchar(64) not null, col_varchar_key varchar(1), id int primary key, col_varchar_64 varchar(64),col_char_64_not_null char(64) not null);") + + // Create virtual tiflash replica info. + dom := domain.GetDomain(tk.Session()) + is := dom.InfoSchema() + db, exists := is.SchemaByName(model.NewCIStr("test")) + require.True(t, exists) + for _, tblInfo := range db.Tables { + if tblInfo.Name.L == "ts" { + tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ + Count: 1, + Available: true, + } + } + } + + tk.MustExec(" set @@tidb_allow_mpp=1;") + + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + res := tk.MustQuery(tt) + res.Check(testkit.Rows(output[i].Plan...)) + } +} + +func TestIssue31240(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("create table t31240(a int, b int);") + tk.MustExec("set @@tidb_allow_mpp = 0") + tk.MustExec("set tidb_cost_model_version=2") + // since allow-mpp is adjusted to false, there will be no physical plan if TiFlash cop is banned. + tk.MustExec("set @@session.tidb_allow_tiflash_cop=ON") + + tbl, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "t31240", L: "t31240"}) + require.NoError(t, err) + // Set the hacked TiFlash replica for explain tests. + tbl.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} + + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + }) + if strings.HasPrefix(tt, "set") { + tk.MustExec(tt) + continue + } + testdata.OnRecord(func() { + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) + } + tk.MustExec("drop table if exists t31240") +} + +func TestIssue32632(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("CREATE TABLE `partsupp` (" + + " `PS_PARTKEY` bigint(20) NOT NULL," + + "`PS_SUPPKEY` bigint(20) NOT NULL," + + "`PS_AVAILQTY` bigint(20) NOT NULL," + + "`PS_SUPPLYCOST` decimal(15,2) NOT NULL," + + "`PS_COMMENT` varchar(199) NOT NULL," + + "PRIMARY KEY (`PS_PARTKEY`,`PS_SUPPKEY`) /*T![clustered_index] NONCLUSTERED */)") + tk.MustExec("CREATE TABLE `supplier` (" + + "`S_SUPPKEY` bigint(20) NOT NULL," + + "`S_NAME` char(25) NOT NULL," + + "`S_ADDRESS` varchar(40) NOT NULL," + + "`S_NATIONKEY` bigint(20) NOT NULL," + + "`S_PHONE` char(15) NOT NULL," + + "`S_ACCTBAL` decimal(15,2) NOT NULL," + + "`S_COMMENT` varchar(101) NOT NULL," + + "PRIMARY KEY (`S_SUPPKEY`) /*T![clustered_index] CLUSTERED */)") + h := dom.StatsHandle() + require.NoError(t, h.HandleDDLEvent(<-h.DDLEventCh())) + tk.MustExec("set @@tidb_enforce_mpp = 1") + + tbl1, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "partsupp", L: "partsupp"}) + require.NoError(t, err) + tbl2, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "supplier", L: "supplier"}) + require.NoError(t, err) + // Set the hacked TiFlash replica for explain tests. + tbl1.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} + tbl2.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} + + statsTbl1 := h.GetTableStats(tbl1.Meta()) + statsTbl1.RealtimeCount = 800000 + statsTbl2 := h.GetTableStats(tbl2.Meta()) + statsTbl2.RealtimeCount = 10000 + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) + } + tk.MustExec("drop table if exists partsupp") + tk.MustExec("drop table if exists supplier") +} + +func TestTiFlashPartitionTableScan(t *testing.T) { + failpoint.Enable("github.com/pingcap/tidb/pkg/planner/core/forceDynamicPrune", `return(true)`) + defer failpoint.Disable("github.com/pingcap/tidb/pkg/planner/core/forceDynamicPrune") + + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set tidb_cost_model_version=1") + tk.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + tk.MustExec("set @@tidb_isolation_read_engines = 'tiflash'") + tk.MustExec("set @@tidb_enforce_mpp = on") + tk.MustExec("set @@tidb_allow_batch_cop = 2") + tk.MustExec("drop table if exists rp_t;") + tk.MustExec("drop table if exists hp_t;") + tk.MustExec("create table rp_t(a int) partition by RANGE (a) (PARTITION p0 VALUES LESS THAN (6),PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21));") + tk.MustExec("create table hp_t(a int) partition by hash(a) partitions 4;") + tbl1, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "rp_t", L: "rp_t"}) + require.NoError(t, err) + tbl2, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "hp_t", L: "hp_t"}) + require.NoError(t, err) + // Set the hacked TiFlash replica for explain tests. + tbl1.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} + tbl2.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) + } + tk.MustExec("drop table rp_t;") + tk.MustExec("drop table hp_t;") +} + +func TestTiFlashFineGrainedShuffle(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set tidb_cost_model_version=2") + tk.MustExec("set @@tidb_isolation_read_engines = 'tiflash'") + tk.MustExec("set @@tidb_enforce_mpp = on") + tk.MustExec("drop table if exists t1;") + tk.MustExec("create table t1(c1 int, c2 int)") + + tbl1, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "t1", L: "t1"}) + require.NoError(t, err) + // Set the hacked TiFlash replica for explain tests. + tbl1.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} + var input []string + var output []struct { + SQL string + Plan []string + } + integrationSuiteData := GetIntegrationSuiteData() + integrationSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) + }) + tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) + } +} + +func TestIssue51873(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec(`CREATE TABLE h1 ( + id bigint(20) NOT NULL AUTO_INCREMENT, + position_date date NOT NULL, + asset_id varchar(32) DEFAULT NULL, + portfolio_code varchar(50) DEFAULT NULL, + PRIMARY KEY (id,position_date) /*T![clustered_index] NONCLUSTERED */, + UNIQUE KEY uidx_posi_asset_balance_key (position_date,portfolio_code,asset_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30002 +PARTITION BY RANGE COLUMNS(position_date) +(PARTITION p202401 VALUES LESS THAN ('2024-02-01'))`) + tk.MustExec(`create table h2 like h1`) + tk.MustExec(`insert into h1 values(1,'2024-01-01',1,1)`) + tk.MustExec(`insert into h2 values(1,'2024-01-01',1,1)`) + tk.MustExec(`analyze table h1`) + tk.MustExec(`set @@tidb_skip_missing_partition_stats=0`) + tk.MustQuery(`with assetBalance AS + (SELECT asset_id, portfolio_code FROM h1 pab WHERE pab.position_date = '2024-01-01' ), +cashBalance AS (SELECT portfolio_code, asset_id + FROM h2 pcb WHERE pcb.position_date = '2024-01-01' ), +assetIdList AS (SELECT DISTINCT asset_id AS assetId + FROM assetBalance ) +SELECT main.portfolioCode +FROM (SELECT DISTINCT balance.portfolio_code AS portfolioCode + FROM assetBalance balance + LEFT JOIN assetIdList + ON balance.asset_id = assetIdList.assetId ) main`).Check(testkit.Rows("1")) +} + +func TestIssue50926(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t (a int)") + tk.MustExec("create or replace definer='root'@'localhost' view v (a,b) AS select 1 as a, json_object('k', '0') as b from t") + tk.MustQuery("select sum(json_extract(b, '$.path')) from v group by a").Check(testkit.Rows()) // no error +} + +func TestFixControl45132(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec(`create table t (a int, b int, key(a))`) + values := make([]string, 0, 101) + for i := 0; i < 100; i++ { + values = append(values, "(1, 1)") + } + values = append(values, "(2, 2)") // count(1) : count(2) == 100 : 1 + tk.MustExec(`insert into t values ` + strings.Join(values, ",")) + for i := 0; i < 7; i++ { + tk.MustExec(`insert into t select * from t`) + } + tk.MustExec(`analyze table t`) + // the cost model prefers to use TableScan instead of IndexLookup to avoid double requests. + tk.MustHavePlan(`select * from t where a=2`, `TableFullScan`) + + tk.MustExec(`set @@tidb_opt_fix_control = "45132:99"`) + tk.MustExec(`analyze table t`) + tk.EventuallyMustIndexLookup(`select * from t where a=2`) // index lookup + + tk.MustExec(`set @@tidb_opt_fix_control = "45132:500"`) + tk.MustHavePlan(`select * from t where a=2`, `TableFullScan`) + + tk.MustExec(`set @@tidb_opt_fix_control = "45132:0"`) + tk.MustHavePlan(`select * from t where a=2`, `TableFullScan`) +} diff --git a/planner/core/optimizer.go b/planner/core/optimizer.go index 4b30741c26200..d522115421996 100644 --- a/planner/core/optimizer.go +++ b/planner/core/optimizer.go @@ -285,9 +285,29 @@ func DoOptimize(ctx context.Context, sctx sessionctx.Context, flag uint64, logic } flag |= flagCollectPredicateColumnsPoint flag |= flagSyncWaitStatsLoadPoint +<<<<<<< HEAD:planner/core/optimizer.go logic, err := logicalOptimize(ctx, flag, logic) if err != nil { return nil, 0, err +======= + if !logic.SCtx().GetSessionVars().StmtCtx.UseDynamicPruneMode { + flag |= flagPartitionProcessor // apply partition pruning under static mode + } + return flag +} + +// DoOptimize optimizes a logical plan to a physical plan. +func DoOptimize( + ctx context.Context, + sctx PlanContext, + flag uint64, + logic LogicalPlan, +) (PhysicalPlan, float64, error) { + sessVars := sctx.GetSessionVars() + if sessVars.StmtCtx.EnableOptimizerDebugTrace { + debugtrace.EnterContextCommon(sctx) + defer debugtrace.LeaveContextCommon(sctx) +>>>>>>> f4e366ea0c3 (planner: apply rule_partition_pruning when optimizing CTE under static mode (#51903)):pkg/planner/core/optimizer.go } if !AllowCartesianProduct.Load() && existsCartesianProduct(logic) { return nil, 0, errors.Trace(ErrCartesianProductUnsupported) From 40122c3587b897ac45172831dcfecfc0d45bc213 Mon Sep 17 00:00:00 2001 From: qw4990 Date: Tue, 26 Mar 2024 17:33:25 +0800 Subject: [PATCH 2/5] fixup --- pkg/planner/core/casetest/BUILD.bazel | 32 -- pkg/planner/core/casetest/integration_test.go | 419 ------------------ planner/core/integration_test.go | 32 ++ planner/core/optimizer.go | 20 - 4 files changed, 32 insertions(+), 471 deletions(-) delete mode 100644 pkg/planner/core/casetest/BUILD.bazel delete mode 100644 pkg/planner/core/casetest/integration_test.go diff --git a/pkg/planner/core/casetest/BUILD.bazel b/pkg/planner/core/casetest/BUILD.bazel deleted file mode 100644 index a9cac84c4ba66..0000000000000 --- a/pkg/planner/core/casetest/BUILD.bazel +++ /dev/null @@ -1,32 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_test") - -go_test( - name = "casetest_test", - timeout = "moderate", - srcs = [ - "integration_test.go", - "main_test.go", - "plan_test.go", - "stats_test.go", - "tiflash_selection_late_materialization_test.go", - ], - data = glob(["testdata/**"]), - flaky = True, - shard_count = 20, - deps = [ - "//pkg/domain", - "//pkg/parser", - "//pkg/parser/model", - "//pkg/planner/core", - "//pkg/planner/property", - "//pkg/testkit", - "//pkg/testkit/testdata", - "//pkg/testkit/testmain", - "//pkg/testkit/testsetup", - "//pkg/util/hint", - "//pkg/util/plancodec", - "@com_github_pingcap_failpoint//:failpoint", - "@com_github_stretchr_testify//require", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/pkg/planner/core/casetest/integration_test.go b/pkg/planner/core/casetest/integration_test.go deleted file mode 100644 index 09e5c3802cbd5..0000000000000 --- a/pkg/planner/core/casetest/integration_test.go +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright 2023 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package casetest - -import ( - "strings" - "testing" - - "github.com/pingcap/failpoint" - "github.com/pingcap/tidb/pkg/domain" - "github.com/pingcap/tidb/pkg/parser/model" - "github.com/pingcap/tidb/pkg/testkit" - "github.com/pingcap/tidb/pkg/testkit/testdata" - "github.com/stretchr/testify/require" -) - -func TestVerboseExplain(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set tidb_cost_model_version=2") - tk.MustExec(`set tidb_opt_limit_push_down_threshold=0`) - tk.MustExec("drop table if exists t1, t2, t3") - tk.MustExec("create table t1(a int, b int)") - tk.MustExec("create table t2(a int, b int)") - tk.MustExec("create table t3(a int, b int, index c(b))") - tk.MustExec("insert into t1 values(1,2)") - tk.MustExec("insert into t1 values(3,4)") - tk.MustExec("insert into t1 values(5,6)") - tk.MustExec("insert into t2 values(1,2)") - tk.MustExec("insert into t2 values(3,4)") - tk.MustExec("insert into t2 values(5,6)") - tk.MustExec("insert into t3 values(1,2)") - tk.MustExec("insert into t3 values(3,4)") - tk.MustExec("insert into t3 values(5,6)") - tk.MustExec("analyze table t1") - tk.MustExec("analyze table t2") - tk.MustExec("analyze table t3") - - // Default RPC encoding may cause statistics explain result differ and then the test unstable. - tk.MustExec("set @@tidb_enable_chunk_rpc = on") - - // Create virtual tiflash replica info. - dom := domain.GetDomain(tk.Session()) - is := dom.InfoSchema() - db, exists := is.SchemaByName(model.NewCIStr("test")) - require.True(t, exists) - for _, tblInfo := range db.Tables { - if tblInfo.Name.L == "t1" || tblInfo.Name.L == "t2" { - tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ - Count: 1, - Available: true, - } - } - } - - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - res := tk.MustQuery(tt) - res.Check(testkit.Rows(output[i].Plan...)) - } -} - -func TestIsolationReadTiFlashNotChoosePointGet(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - - tk.MustExec("use test") - tk.MustExec("drop table if exists t") - tk.MustExec("create table t(a int, b int, primary key (a))") - - // Create virtual tiflash replica info. - dom := domain.GetDomain(tk.Session()) - is := dom.InfoSchema() - db, exists := is.SchemaByName(model.NewCIStr("test")) - require.True(t, exists) - for _, tblInfo := range db.Tables { - tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ - Count: 1, - Available: true, - } - } - - tk.MustExec("set @@session.tidb_isolation_read_engines=\"tiflash\"") - var input []string - var output []struct { - SQL string - Result []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Result = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - tk.MustQuery(tt).Check(testkit.Rows(output[i].Result...)) - } -} - -func TestIsolationReadDoNotFilterSystemDB(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - - tk.MustExec("use test") - tk.MustExec("set @@tidb_isolation_read_engines = \"tiflash\"") - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - res := tk.MustQuery(tt) - res.Check(testkit.Rows(output[i].Plan...)) - } -} - -func TestMergeContinuousSelections(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("drop table if exists ts") - tk.MustExec("create table ts (col_char_64 char(64), col_varchar_64_not_null varchar(64) not null, col_varchar_key varchar(1), id int primary key, col_varchar_64 varchar(64),col_char_64_not_null char(64) not null);") - - // Create virtual tiflash replica info. - dom := domain.GetDomain(tk.Session()) - is := dom.InfoSchema() - db, exists := is.SchemaByName(model.NewCIStr("test")) - require.True(t, exists) - for _, tblInfo := range db.Tables { - if tblInfo.Name.L == "ts" { - tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ - Count: 1, - Available: true, - } - } - } - - tk.MustExec(" set @@tidb_allow_mpp=1;") - - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - res := tk.MustQuery(tt) - res.Check(testkit.Rows(output[i].Plan...)) - } -} - -func TestIssue31240(t *testing.T) { - store, dom := testkit.CreateMockStoreAndDomain(t) - tk := testkit.NewTestKit(t, store) - - tk.MustExec("use test") - tk.MustExec("create table t31240(a int, b int);") - tk.MustExec("set @@tidb_allow_mpp = 0") - tk.MustExec("set tidb_cost_model_version=2") - // since allow-mpp is adjusted to false, there will be no physical plan if TiFlash cop is banned. - tk.MustExec("set @@session.tidb_allow_tiflash_cop=ON") - - tbl, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "t31240", L: "t31240"}) - require.NoError(t, err) - // Set the hacked TiFlash replica for explain tests. - tbl.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} - - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - }) - if strings.HasPrefix(tt, "set") { - tk.MustExec(tt) - continue - } - testdata.OnRecord(func() { - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) - } - tk.MustExec("drop table if exists t31240") -} - -func TestIssue32632(t *testing.T) { - store, dom := testkit.CreateMockStoreAndDomain(t) - tk := testkit.NewTestKit(t, store) - - tk.MustExec("use test") - tk.MustExec("CREATE TABLE `partsupp` (" + - " `PS_PARTKEY` bigint(20) NOT NULL," + - "`PS_SUPPKEY` bigint(20) NOT NULL," + - "`PS_AVAILQTY` bigint(20) NOT NULL," + - "`PS_SUPPLYCOST` decimal(15,2) NOT NULL," + - "`PS_COMMENT` varchar(199) NOT NULL," + - "PRIMARY KEY (`PS_PARTKEY`,`PS_SUPPKEY`) /*T![clustered_index] NONCLUSTERED */)") - tk.MustExec("CREATE TABLE `supplier` (" + - "`S_SUPPKEY` bigint(20) NOT NULL," + - "`S_NAME` char(25) NOT NULL," + - "`S_ADDRESS` varchar(40) NOT NULL," + - "`S_NATIONKEY` bigint(20) NOT NULL," + - "`S_PHONE` char(15) NOT NULL," + - "`S_ACCTBAL` decimal(15,2) NOT NULL," + - "`S_COMMENT` varchar(101) NOT NULL," + - "PRIMARY KEY (`S_SUPPKEY`) /*T![clustered_index] CLUSTERED */)") - h := dom.StatsHandle() - require.NoError(t, h.HandleDDLEvent(<-h.DDLEventCh())) - tk.MustExec("set @@tidb_enforce_mpp = 1") - - tbl1, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "partsupp", L: "partsupp"}) - require.NoError(t, err) - tbl2, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "supplier", L: "supplier"}) - require.NoError(t, err) - // Set the hacked TiFlash replica for explain tests. - tbl1.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} - tbl2.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} - - statsTbl1 := h.GetTableStats(tbl1.Meta()) - statsTbl1.RealtimeCount = 800000 - statsTbl2 := h.GetTableStats(tbl2.Meta()) - statsTbl2.RealtimeCount = 10000 - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) - } - tk.MustExec("drop table if exists partsupp") - tk.MustExec("drop table if exists supplier") -} - -func TestTiFlashPartitionTableScan(t *testing.T) { - failpoint.Enable("github.com/pingcap/tidb/pkg/planner/core/forceDynamicPrune", `return(true)`) - defer failpoint.Disable("github.com/pingcap/tidb/pkg/planner/core/forceDynamicPrune") - - store, dom := testkit.CreateMockStoreAndDomain(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set tidb_cost_model_version=1") - tk.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") - tk.MustExec("set @@tidb_isolation_read_engines = 'tiflash'") - tk.MustExec("set @@tidb_enforce_mpp = on") - tk.MustExec("set @@tidb_allow_batch_cop = 2") - tk.MustExec("drop table if exists rp_t;") - tk.MustExec("drop table if exists hp_t;") - tk.MustExec("create table rp_t(a int) partition by RANGE (a) (PARTITION p0 VALUES LESS THAN (6),PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21));") - tk.MustExec("create table hp_t(a int) partition by hash(a) partitions 4;") - tbl1, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "rp_t", L: "rp_t"}) - require.NoError(t, err) - tbl2, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "hp_t", L: "hp_t"}) - require.NoError(t, err) - // Set the hacked TiFlash replica for explain tests. - tbl1.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} - tbl2.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) - } - tk.MustExec("drop table rp_t;") - tk.MustExec("drop table hp_t;") -} - -func TestTiFlashFineGrainedShuffle(t *testing.T) { - store, dom := testkit.CreateMockStoreAndDomain(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("set tidb_cost_model_version=2") - tk.MustExec("set @@tidb_isolation_read_engines = 'tiflash'") - tk.MustExec("set @@tidb_enforce_mpp = on") - tk.MustExec("drop table if exists t1;") - tk.MustExec("create table t1(c1 int, c2 int)") - - tbl1, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "t1", L: "t1"}) - require.NoError(t, err) - // Set the hacked TiFlash replica for explain tests. - tbl1.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true} - var input []string - var output []struct { - SQL string - Plan []string - } - integrationSuiteData := GetIntegrationSuiteData() - integrationSuiteData.LoadTestCases(t, &input, &output) - for i, tt := range input { - testdata.OnRecord(func() { - output[i].SQL = tt - output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) - }) - tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) - } -} - -func TestIssue51873(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec(`use test`) - tk.MustExec(`CREATE TABLE h1 ( - id bigint(20) NOT NULL AUTO_INCREMENT, - position_date date NOT NULL, - asset_id varchar(32) DEFAULT NULL, - portfolio_code varchar(50) DEFAULT NULL, - PRIMARY KEY (id,position_date) /*T![clustered_index] NONCLUSTERED */, - UNIQUE KEY uidx_posi_asset_balance_key (position_date,portfolio_code,asset_id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30002 -PARTITION BY RANGE COLUMNS(position_date) -(PARTITION p202401 VALUES LESS THAN ('2024-02-01'))`) - tk.MustExec(`create table h2 like h1`) - tk.MustExec(`insert into h1 values(1,'2024-01-01',1,1)`) - tk.MustExec(`insert into h2 values(1,'2024-01-01',1,1)`) - tk.MustExec(`analyze table h1`) - tk.MustExec(`set @@tidb_skip_missing_partition_stats=0`) - tk.MustQuery(`with assetBalance AS - (SELECT asset_id, portfolio_code FROM h1 pab WHERE pab.position_date = '2024-01-01' ), -cashBalance AS (SELECT portfolio_code, asset_id - FROM h2 pcb WHERE pcb.position_date = '2024-01-01' ), -assetIdList AS (SELECT DISTINCT asset_id AS assetId - FROM assetBalance ) -SELECT main.portfolioCode -FROM (SELECT DISTINCT balance.portfolio_code AS portfolioCode - FROM assetBalance balance - LEFT JOIN assetIdList - ON balance.asset_id = assetIdList.assetId ) main`).Check(testkit.Rows("1")) -} - -func TestIssue50926(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec("use test") - tk.MustExec("create table t (a int)") - tk.MustExec("create or replace definer='root'@'localhost' view v (a,b) AS select 1 as a, json_object('k', '0') as b from t") - tk.MustQuery("select sum(json_extract(b, '$.path')) from v group by a").Check(testkit.Rows()) // no error -} - -func TestFixControl45132(t *testing.T) { - store := testkit.CreateMockStore(t) - tk := testkit.NewTestKit(t, store) - tk.MustExec(`use test`) - tk.MustExec(`create table t (a int, b int, key(a))`) - values := make([]string, 0, 101) - for i := 0; i < 100; i++ { - values = append(values, "(1, 1)") - } - values = append(values, "(2, 2)") // count(1) : count(2) == 100 : 1 - tk.MustExec(`insert into t values ` + strings.Join(values, ",")) - for i := 0; i < 7; i++ { - tk.MustExec(`insert into t select * from t`) - } - tk.MustExec(`analyze table t`) - // the cost model prefers to use TableScan instead of IndexLookup to avoid double requests. - tk.MustHavePlan(`select * from t where a=2`, `TableFullScan`) - - tk.MustExec(`set @@tidb_opt_fix_control = "45132:99"`) - tk.MustExec(`analyze table t`) - tk.EventuallyMustIndexLookup(`select * from t where a=2`) // index lookup - - tk.MustExec(`set @@tidb_opt_fix_control = "45132:500"`) - tk.MustHavePlan(`select * from t where a=2`, `TableFullScan`) - - tk.MustExec(`set @@tidb_opt_fix_control = "45132:0"`) - tk.MustHavePlan(`select * from t where a=2`, `TableFullScan`) -} diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index cc440ca20953f..5a66f2660eb38 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -7017,6 +7017,38 @@ func TestTiFlashFineGrainedShuffle(t *testing.T) { } } +func TestIssue51873(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec(`CREATE TABLE h1 ( + id bigint(20) NOT NULL AUTO_INCREMENT, + position_date date NOT NULL, + asset_id varchar(32) DEFAULT NULL, + portfolio_code varchar(50) DEFAULT NULL, + PRIMARY KEY (id,position_date) /*T![clustered_index] NONCLUSTERED */, + UNIQUE KEY uidx_posi_asset_balance_key (position_date,portfolio_code,asset_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30002 +PARTITION BY RANGE COLUMNS(position_date) +(PARTITION p202401 VALUES LESS THAN ('2024-02-01'))`) + tk.MustExec(`create table h2 like h1`) + tk.MustExec(`insert into h1 values(1,'2024-01-01',1,1)`) + tk.MustExec(`insert into h2 values(1,'2024-01-01',1,1)`) + tk.MustExec(`analyze table h1`) + tk.MustExec(`set @@tidb_skip_missing_partition_stats=0`) + tk.MustQuery(`with assetBalance AS + (SELECT asset_id, portfolio_code FROM h1 pab WHERE pab.position_date = '2024-01-01' ), +cashBalance AS (SELECT portfolio_code, asset_id + FROM h2 pcb WHERE pcb.position_date = '2024-01-01' ), +assetIdList AS (SELECT DISTINCT asset_id AS assetId + FROM assetBalance ) +SELECT main.portfolioCode +FROM (SELECT DISTINCT balance.portfolio_code AS portfolioCode + FROM assetBalance balance + LEFT JOIN assetIdList + ON balance.asset_id = assetIdList.assetId ) main`).Check(testkit.Rows("1")) +} + func TestTiFlashFineGrainedShuffleWithMaxTiFlashThreads(t *testing.T) { store, dom := testkit.CreateMockStoreAndDomain(t) tk := testkit.NewTestKit(t, store) diff --git a/planner/core/optimizer.go b/planner/core/optimizer.go index d522115421996..4b30741c26200 100644 --- a/planner/core/optimizer.go +++ b/planner/core/optimizer.go @@ -285,29 +285,9 @@ func DoOptimize(ctx context.Context, sctx sessionctx.Context, flag uint64, logic } flag |= flagCollectPredicateColumnsPoint flag |= flagSyncWaitStatsLoadPoint -<<<<<<< HEAD:planner/core/optimizer.go logic, err := logicalOptimize(ctx, flag, logic) if err != nil { return nil, 0, err -======= - if !logic.SCtx().GetSessionVars().StmtCtx.UseDynamicPruneMode { - flag |= flagPartitionProcessor // apply partition pruning under static mode - } - return flag -} - -// DoOptimize optimizes a logical plan to a physical plan. -func DoOptimize( - ctx context.Context, - sctx PlanContext, - flag uint64, - logic LogicalPlan, -) (PhysicalPlan, float64, error) { - sessVars := sctx.GetSessionVars() - if sessVars.StmtCtx.EnableOptimizerDebugTrace { - debugtrace.EnterContextCommon(sctx) - defer debugtrace.LeaveContextCommon(sctx) ->>>>>>> f4e366ea0c3 (planner: apply rule_partition_pruning when optimizing CTE under static mode (#51903)):pkg/planner/core/optimizer.go } if !AllowCartesianProduct.Load() && existsCartesianProduct(logic) { return nil, 0, errors.Trace(ErrCartesianProductUnsupported) From 78f87323483a9b3b4f8312f646fb539b10fa6ff1 Mon Sep 17 00:00:00 2001 From: qw4990 Date: Tue, 26 Mar 2024 17:36:22 +0800 Subject: [PATCH 3/5] fixup --- planner/core/optimizer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/planner/core/optimizer.go b/planner/core/optimizer.go index 4b30741c26200..36a49d79f4e85 100644 --- a/planner/core/optimizer.go +++ b/planner/core/optimizer.go @@ -285,6 +285,9 @@ func DoOptimize(ctx context.Context, sctx sessionctx.Context, flag uint64, logic } flag |= flagCollectPredicateColumnsPoint flag |= flagSyncWaitStatsLoadPoint + if !logic.SCtx().GetSessionVars().StmtCtx.UseDynamicPruneMode { + flag |= flagPartitionProcessor // apply partition pruning under static mode + } logic, err := logicalOptimize(ctx, flag, logic) if err != nil { return nil, 0, err From b7ac3dea959dd01e6e8c4a76dedd3839c5d4b331 Mon Sep 17 00:00:00 2001 From: qw4990 Date: Mon, 1 Apr 2024 13:43:41 +0800 Subject: [PATCH 4/5] fixp --- executor/sample_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/executor/sample_test.go b/executor/sample_test.go index ea8511ade197a..f195f6c2c5877 100644 --- a/executor/sample_test.go +++ b/executor/sample_test.go @@ -178,6 +178,7 @@ func TestTableSampleWithTiDBRowID(t *testing.T) { func TestTableSampleWithPartition(t *testing.T) { store := testkit.CreateMockStore(t) tk := createSampleTestkit(t, store) + tk.MustExec(`set @@tidb_opt_fix_control = "44262:ON"`) tk.MustExec("create table t (a int, b varchar(255), primary key (a)) partition by hash(a) partitions 2;") tk.MustExec("insert into t values (1, '1'), (2, '2'), (3, '3');") rows := tk.MustQuery("select * from t tablesample regions();").Rows() From aef0b56c9195acc3720d819de41c51ac1d9d9363 Mon Sep 17 00:00:00 2001 From: qw4990 Date: Mon, 1 Apr 2024 14:08:28 +0800 Subject: [PATCH 5/5] fixup --- planner/core/integration_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index 5a66f2660eb38..075f388809c7f 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -7035,7 +7035,6 @@ PARTITION BY RANGE COLUMNS(position_date) tk.MustExec(`insert into h1 values(1,'2024-01-01',1,1)`) tk.MustExec(`insert into h2 values(1,'2024-01-01',1,1)`) tk.MustExec(`analyze table h1`) - tk.MustExec(`set @@tidb_skip_missing_partition_stats=0`) tk.MustQuery(`with assetBalance AS (SELECT asset_id, portfolio_code FROM h1 pab WHERE pab.position_date = '2024-01-01' ), cashBalance AS (SELECT portfolio_code, asset_id