diff --git a/enginetest/queries/query_plans.go b/enginetest/queries/query_plans.go index fbc94de21b..9e76d454cd 100644 --- a/enginetest/queries/query_plans.go +++ b/enginetest/queries/query_plans.go @@ -1440,15 +1440,19 @@ where " ├─ columns: [style.assetId:1]\n" + " └─ LookupJoin\n" + " ├─ LookupJoin\n" + - " │ ├─ TableAlias(style)\n" + - " │ │ └─ IndexedTableAccess(asset)\n" + - " │ │ ├─ index: [asset.orgId,asset.name,asset.val]\n" + - " │ │ ├─ static: [{[org1, org1], [style, style], [curve, curve]}]\n" + - " │ │ ├─ colSet: (1-5)\n" + - " │ │ ├─ tableId: 1\n" + - " │ │ └─ Table\n" + - " │ │ ├─ name: asset\n" + - " │ │ └─ columns: [orgid assetid name val]\n" + + " │ ├─ Filter\n" + + " │ │ ├─ Eq\n" + + " │ │ │ ├─ style.val:3\n" + + " │ │ │ └─ curve (longtext)\n" + + " │ │ └─ TableAlias(style)\n" + + " │ │ └─ IndexedTableAccess(asset)\n" + + " │ │ ├─ index: [asset.orgId,asset.name,asset.assetId]\n" + + " │ │ ├─ static: [{[org1, org1], [style, style], [NULL, ∞)}]\n" + + " │ │ ├─ colSet: (1-5)\n" + + " │ │ ├─ tableId: 1\n" + + " │ │ └─ Table\n" + + " │ │ ├─ name: asset\n" + + " │ │ └─ columns: [orgid assetid name val]\n" + " │ └─ Filter\n" + " │ ├─ AND\n" + " │ │ ├─ AND\n" + @@ -1494,13 +1498,15 @@ where "", ExpectedEstimates: "Project\n" + " ├─ columns: [style.assetId]\n" + - " └─ LookupJoin (estimated cost=19.800 rows=6)\n" + - " ├─ LookupJoin (estimated cost=19.800 rows=6)\n" + - " │ ├─ TableAlias(style)\n" + - " │ │ └─ IndexedTableAccess(asset)\n" + - " │ │ ├─ index: [asset.orgId,asset.name,asset.val]\n" + - " │ │ ├─ filters: [{[org1, org1], [style, style], [curve, curve]}]\n" + - " │ │ └─ columns: [orgid assetid name val]\n" + + " └─ LookupJoin (estimated cost=16.500 rows=5)\n" + + " ├─ LookupJoin (estimated cost=16.500 rows=5)\n" + + " │ ├─ Filter\n" + + " │ │ ├─ (style.val = 'curve')\n" + + " │ │ └─ TableAlias(style)\n" + + " │ │ └─ IndexedTableAccess(asset)\n" + + " │ │ ├─ index: [asset.orgId,asset.name,asset.assetId]\n" + + " │ │ ├─ filters: [{[org1, org1], [style, style], [NULL, ∞)}]\n" + + " │ │ └─ columns: [orgid assetid name val]\n" + " │ └─ Filter\n" + " │ ├─ (((dimension.val = 'wide') AND (dimension.name = 'dimension')) AND (dimension.orgId = 'org1'))\n" + " │ └─ TableAlias(dimension)\n" + @@ -1518,13 +1524,15 @@ where "", ExpectedAnalysis: "Project\n" + " ├─ columns: [style.assetId]\n" + - " └─ LookupJoin (estimated cost=19.800 rows=6) (actual rows=1 loops=1)\n" + - " ├─ LookupJoin (estimated cost=19.800 rows=6) (actual rows=1 loops=1)\n" + - " │ ├─ TableAlias(style)\n" + - " │ │ └─ IndexedTableAccess(asset)\n" + - " │ │ ├─ index: [asset.orgId,asset.name,asset.val]\n" + - " │ │ ├─ filters: [{[org1, org1], [style, style], [curve, curve]}]\n" + - " │ │ └─ columns: [orgid assetid name val]\n" + + " └─ LookupJoin (estimated cost=16.500 rows=5) (actual rows=1 loops=1)\n" + + " ├─ LookupJoin (estimated cost=16.500 rows=5) (actual rows=1 loops=1)\n" + + " │ ├─ Filter\n" + + " │ │ ├─ (style.val = 'curve')\n" + + " │ │ └─ TableAlias(style)\n" + + " │ │ └─ IndexedTableAccess(asset)\n" + + " │ │ ├─ index: [asset.orgId,asset.name,asset.assetId]\n" + + " │ │ ├─ filters: [{[org1, org1], [style, style], [NULL, ∞)}]\n" + + " │ │ └─ columns: [orgid assetid name val]\n" + " │ └─ Filter\n" + " │ ├─ (((dimension.val = 'wide') AND (dimension.name = 'dimension')) AND (dimension.orgId = 'org1'))\n" + " │ └─ TableAlias(dimension)\n" + diff --git a/enginetest/server_engine_test.go b/enginetest/server_engine_test.go index 933175b8e6..52d606986b 100644 --- a/enginetest/server_engine_test.go +++ b/enginetest/server_engine_test.go @@ -267,6 +267,51 @@ func TestServerPreparedStatements(t *testing.T) { }, }, }, + { + name: "regression test for incorrectly setting QFlagMax1Row flag", + setup: []string{ + "create table test(c0 int not null, c1 int not null, pk int primary key, key (c0, c1));", + "insert into test values (2, 3, 1), (5, 6, 4), (2, 3, 7);", + }, + assertions: []serverScriptTestAssertion{ + { + query: "select * from test where c0 = 2 and c1 = 3;", + expectedRows: []any{ + []uint64{uint64(2), uint64(3), uint64(1)}, + []uint64{uint64(2), uint64(3), uint64(7)}, + }, + checkRows: func(rows *gosql.Rows, expectedRows []any) (bool, error) { + var c0, c1, pk uint64 + var rowNum int + for rows.Next() { + err := rows.Scan(&c0, &c1, &pk) + require.NoError(t, err) + if err != nil { + return false, err + } + require.Less(t, rowNum, len(expectedRows)) + if rowNum >= len(expectedRows) { + return false, nil + } + require.Equal(t, c0, expectedRows[rowNum].([]uint64)[0]) + if c0 != expectedRows[rowNum].([]uint64)[0] { + return false, nil + } + require.Equal(t, c1, expectedRows[rowNum].([]uint64)[1]) + if c1 != expectedRows[rowNum].([]uint64)[1] { + return false, nil + } + require.Equal(t, pk, expectedRows[rowNum].([]uint64)[2]) + if pk != expectedRows[rowNum].([]uint64)[2] { + return false, nil + } + rowNum++ + } + return true, nil + }, + }, + }, + }, } port, perr := findEmptyPort() @@ -315,6 +360,8 @@ func TestServerPreparedStatements(t *testing.T) { if assertion.expectErr { require.Error(t, err) return + } else { + require.NoError(t, err) } ok, err := assertion.checkRows(rows, assertion.expectedRows) require.NoError(t, err) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 3f1c51e639..deccdf64d8 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -1227,12 +1227,17 @@ func (c *indexCoster) costIndexScanAnd(filter *iScanAnd, s sql.Statistic, bucket } } + var conjFDs *sql.FuncDepSet + if idx.IsUnique() { + conjFDs = conj.getFds() + } + if exact.Len()+conj.applied.Len() == filter.childCnt() { // matched all filters - return conj.hist, conj.getFds(), sql.NewFastIntSet(int(filter.id)), conj.missingPrefix, nil + return conj.hist, conjFDs, sql.NewFastIntSet(int(filter.id)), conj.missingPrefix, nil } - return conj.hist, conj.getFds(), exact.Union(conj.applied), conj.missingPrefix, nil + return conj.hist, conjFDs, exact.Union(conj.applied), conj.missingPrefix, nil } func (c *indexCoster) costIndexScanOr(filter *iScanOr, s sql.Statistic, buckets []sql.HistogramBucket, ordinals map[string]int, idx sql.Index) ([]sql.HistogramBucket, *sql.FuncDepSet, bool, error) {