From dc9245a1bf311e1221319d9fb0cd79fda72e2ec4 Mon Sep 17 00:00:00 2001 From: James Cor Date: Wed, 26 Nov 2025 15:19:38 -0800 Subject: [PATCH 01/10] unique IN values --- sql/analyzer/costed_index_scan.go | 14 +++++++++++++- sql/index_builder.go | 13 +++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 3bc2ec2d23..496c59a834 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -848,6 +848,18 @@ func (b *indexScanRangeBuilder) buildRangeCollection(f indexFilter) (sql.MySQLRa case *iScanOr: ranges, err = b.rangeBuildOr(f, inScan) case *iScanLeaf: + // TODO: special case for in set. can skip overlapping ranges since it's a series of equality checks + // TODO: sequential integers can be converted to a single partition, but i guess that's harder? + if f.Op() == sql.IndexScanOpInSet { + bb := sql.NewMySQLIndexBuilder(b.idx) + b.rangeBuildDefaultLeaf(bb, f, inScan) + if _, err := bb.Build(b.ctx); err != nil { + return nil, err + } + ranges = bb.Ranges(b.ctx) + return ranges, nil + } + ranges, err = b.rangeBuildLeaf(f, inScan) default: return nil, fmt.Errorf("unknown indexFilter type: %T", f) @@ -1462,7 +1474,7 @@ func newLeaf(ctx *sql.Context, id indexScanId, e sql.Expression, underlying stri id: id, gf: gf, op: op, - setValues: litSet, + setValues: setVals, setTypes: setTypes, litType: litType, underlying: underlying, diff --git a/sql/index_builder.go b/sql/index_builder.go index 40ee878a7e..54330172e5 100644 --- a/sql/index_builder.go +++ b/sql/index_builder.go @@ -42,15 +42,17 @@ type MySQLIndexBuilder struct { // NewMySQLIndexBuilder returns a new MySQLIndexBuilder. Used internally to construct a range that will later be passed to // integrators through the Index function NewLookup. func NewMySQLIndexBuilder(idx Index) *MySQLIndexBuilder { - colExprTypes := make(map[string]Type) - ranges := make(map[string][]MySQLRangeColumnExpr) - for _, cet := range idx.ColumnExpressionTypes() { + cets := idx.ColumnExpressionTypes() + colExprTypes := make(map[string]Type, len(cets)) + ranges := make(map[string][]MySQLRangeColumnExpr, len(cets)) + for _, cet := range cets { typ := cet.Type if _, ok := typ.(StringType); ok { typ = typ.Promote() } - colExprTypes[strings.ToLower(cet.Expression)] = typ - ranges[strings.ToLower(cet.Expression)] = []MySQLRangeColumnExpr{AllRangeColumnExpr(typ)} + expr := strings.ToLower(cet.Expression) + colExprTypes[expr] = typ + ranges[expr] = []MySQLRangeColumnExpr{AllRangeColumnExpr(typ)} } return &MySQLIndexBuilder{ idx: idx, @@ -600,7 +602,6 @@ func (b *MySQLIndexBuilder) updateCol(ctx *Context, colExpr string, potentialRan var newRanges []MySQLRangeColumnExpr for _, currentRange := range currentRanges { for _, potentialRange := range potentialRanges { - newRange, ok, err := currentRange.TryIntersect(potentialRange) if err != nil { b.isInvalid = true From bd568572a66986beec1e80f36aa5402265248f83 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 2 Dec 2025 15:30:09 -0800 Subject: [PATCH 02/10] test --- enginetest/memory_engine_test.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index f1ec7b45d0..b447ecc83b 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -200,23 +200,18 @@ func TestSingleQueryPrepared(t *testing.T) { // Convenience test for debugging a single query. Unskip and set to the desired query. func TestSingleScript(t *testing.T) { - t.Skip() + //t.Skip() var scripts = []queries.ScriptTest{ { - Name: "AS OF propagates to nested CALLs", - SetUpScript: []string{}, + Name: "aaaaa", + SetUpScript: []string{ + "create table t (i int primary key, j int);", + "insert into t values (1, 1), (2, 2), (3, 3);", + }, Assertions: []queries.ScriptTestAssertion{ { - Query: "create procedure create_proc() create table t (i int primary key, j int);", - Expected: []sql.Row{ - {types.NewOkResult(0)}, - }, - }, - { - Query: "call create_proc()", - Expected: []sql.Row{ - {types.NewOkResult(0)}, - }, + Query: "select * from t where i in (1, 2, 3)", + Expected: []sql.Row{}, }, }, }, From ed6815b56010b4cc7a014e68b4aff6da06c79433 Mon Sep 17 00:00:00 2001 From: James Cor Date: Thu, 4 Dec 2025 15:45:06 -0800 Subject: [PATCH 03/10] rangetree and overlap check for some in queries --- sql/analyzer/costed_index_scan.go | 66 +++++++++++++++++++++++++------ sql/index_builder.go | 14 ++++--- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 496c59a834..23afc1ca66 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -21,6 +21,8 @@ import ( "strings" "time" + "github.com/shopspring/decimal" + "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/expression/function/spatial" @@ -835,6 +837,53 @@ type indexScanRangeBuilder struct { leftover []sql.Expression } +func castToInt64(v any) (int64, bool) { + switch v := v.(type) { + case int: + return int64(v), true + case int8: + return int64(v), true + case int16: + return int64(v), true + case int32: + return int64(v), true + case int64: + return v, true + case float32, float64, decimal.Decimal: + // TODO: return an empty range here + return 0, false + default: + return 0, false + } +} + +func setToSignedIntRange(setVals []any, colExprTypes []sql.ColumnExpressionType) (sql.MySQLRangeCollection, bool) { + if len(colExprTypes) != 1 { + return nil, false + } + typ := colExprTypes[0].Type + if !types.IsSigned(typ) { + return nil, false + } + var ok bool + keys := make([]int64, len(setVals)) + for i, val := range setVals { + keys[i], ok = castToInt64(val) + if !ok { + return nil, false + } + } + slices.Sort(keys) + slices.Compact(keys) + res := make(sql.MySQLRangeCollection, len(keys)) + for i, key := range keys { + res[i] = sql.MySQLRange{ + sql.ClosedRangeColumnExpr(key, key, typ), + } + } + return res, true +} + // buildRangeCollection converts our representation of the best index scan // into the format that represents an index lookup, a list of sql.Range. func (b *indexScanRangeBuilder) buildRangeCollection(f indexFilter) (sql.MySQLRangeCollection, error) { @@ -848,18 +897,13 @@ func (b *indexScanRangeBuilder) buildRangeCollection(f indexFilter) (sql.MySQLRa case *iScanOr: ranges, err = b.rangeBuildOr(f, inScan) case *iScanLeaf: - // TODO: special case for in set. can skip overlapping ranges since it's a series of equality checks - // TODO: sequential integers can be converted to a single partition, but i guess that's harder? + // TODO: special case for in set. can skip building range tree and overlapping range check since it's a series of equality checks if f.Op() == sql.IndexScanOpInSet { - bb := sql.NewMySQLIndexBuilder(b.idx) - b.rangeBuildDefaultLeaf(bb, f, inScan) - if _, err := bb.Build(b.ctx); err != nil { - return nil, err + cets := b.idx.ColumnExpressionTypes() + if ranges, ok := setToSignedIntRange(f.setValues, cets); ok { + return ranges, nil } - ranges = bb.Ranges(b.ctx) - return ranges, nil } - ranges, err = b.rangeBuildLeaf(f, inScan) default: return nil, fmt.Errorf("unknown indexFilter type: %T", f) @@ -1459,7 +1503,7 @@ func newLeaf(ctx *sql.Context, id indexScanId, e sql.Expression, underlying stri var litSet []interface{} var setTypes []sql.Type var litType sql.Type - for _, lit := range tup { + for i, lit := range tup { value, err := lit.Eval(ctx, nil) if err != nil { return nil, false @@ -1474,7 +1518,7 @@ func newLeaf(ctx *sql.Context, id indexScanId, e sql.Expression, underlying stri id: id, gf: gf, op: op, - setValues: setVals, + setValues: litSet, setTypes: setTypes, litType: litType, underlying: underlying, diff --git a/sql/index_builder.go b/sql/index_builder.go index 54330172e5..91b09b03fd 100644 --- a/sql/index_builder.go +++ b/sql/index_builder.go @@ -122,15 +122,19 @@ func (b *MySQLIndexBuilder) Equals(ctx *Context, colExpr string, keyType Type, k for i, k := range keys { // if converting from float to int results in rounding, then it's empty range if t, ok := colTyp.(NumberType); ok && t.IsNumericType() && !t.IsFloat() { - f, c := floor(k), ceil(k) - switch k.(type) { - case float32, float64: - if f != c { + switch k := k.(type) { + case float32: + if float32(int64(k)) != k { + potentialRanges[i] = EmptyRangeColumnExpr(colTyp) + continue + } + case float64: + if float64(int64(k)) != k { potentialRanges[i] = EmptyRangeColumnExpr(colTyp) continue } case decimal.Decimal: - if !f.(decimal.Decimal).Equals(c.(decimal.Decimal)) { + if !k.Equal(decimal.NewFromInt(k.IntPart())) { potentialRanges[i] = EmptyRangeColumnExpr(colTyp) continue } From c5f8854c3c726140267492ccb7fc9d6e28469fc7 Mon Sep 17 00:00:00 2001 From: James Cor Date: Fri, 5 Dec 2025 11:03:29 -0800 Subject: [PATCH 04/10] more optimizing --- sql/analyzer/costed_index_scan.go | 74 ++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 23afc1ca66..6b33227ee2 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -837,26 +837,6 @@ type indexScanRangeBuilder struct { leftover []sql.Expression } -func castToInt64(v any) (int64, bool) { - switch v := v.(type) { - case int: - return int64(v), true - case int8: - return int64(v), true - case int16: - return int64(v), true - case int32: - return int64(v), true - case int64: - return v, true - case float32, float64, decimal.Decimal: - // TODO: return an empty range here - return 0, false - default: - return 0, false - } -} - func setToSignedIntRange(setVals []any, colExprTypes []sql.ColumnExpressionType) (sql.MySQLRangeCollection, bool) { if len(colExprTypes) != 1 { return nil, false @@ -865,22 +845,64 @@ func setToSignedIntRange(setVals []any, colExprTypes []sql.ColumnExpressionType) if !types.IsSigned(typ) { return nil, false } - var ok bool - keys := make([]int64, len(setVals)) - for i, val := range setVals { - keys[i], ok = castToInt64(val) - if !ok { + + keys := make([]int64, 0, len(setVals)) + for _, val := range setVals { + switch v := val.(type) { + case int: + keys = append(keys, int64(v)) + case int8: + keys = append(keys, int64(v)) + case int16: + keys = append(keys, int64(v)) + case int32: + keys = append(keys, int64(v)) + case int64: + keys = append(keys, v) + case uint: + keys = append(keys, int64(v)) + case uint8: + keys = append(keys, int64(v)) + case uint16: + keys = append(keys, int64(v)) + case uint32: + keys = append(keys, int64(v)) + case uint64: + keys = append(keys, int64(v)) + // float32, float64, and decimal are ok as long as they don't round + case float32: + key := int64(v) + if float32(key) == v { + keys = append(keys, key) + } + case float64: + key := int64(v) + if float64(key) == v { + keys = append(keys, key) + } + case decimal.Decimal: + key := v.IntPart() + if v.Equal(decimal.NewFromInt(key)) { + keys = append(keys, key) + } + default: + // resort to default behavior for types that require more conversion return nil, false } } + slices.Sort(keys) - slices.Compact(keys) + keys = slices.Compact(keys) res := make(sql.MySQLRangeCollection, len(keys)) for i, key := range keys { res[i] = sql.MySQLRange{ sql.ClosedRangeColumnExpr(key, key, typ), } } + + if len(res) == 0 { + return nil, true + } return res, true } From 9335f14e01da700c0a15a3df907854debce1ef14 Mon Sep 17 00:00:00 2001 From: James Cor Date: Fri, 5 Dec 2025 12:09:20 -0800 Subject: [PATCH 05/10] use generics and apply to unsigned --- sql/analyzer/costed_index_scan.go | 98 ++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 6b33227ee2..52eab16362 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -15,6 +15,7 @@ package analyzer import ( + "cmp" "fmt" "slices" "sort" @@ -837,15 +838,24 @@ type indexScanRangeBuilder struct { leftover []sql.Expression } -func setToSignedIntRange(setVals []any, colExprTypes []sql.ColumnExpressionType) (sql.MySQLRangeCollection, bool) { - if len(colExprTypes) != 1 { - return nil, false +func keysToRangeColl[N cmp.Ordered](keys []N, typ sql.Type) sql.MySQLRangeCollection { + slices.Sort(keys) + keys = slices.Compact(keys) + // TODO: for integers, if len(keys) - 1 == keys[len(keys)-1] - keys[0], + // then we can just have one continuous range. unsure if this is worth + res := make(sql.MySQLRangeCollection, len(keys)) + for i, key := range keys { + res[i] = sql.MySQLRange{ + sql.ClosedRangeColumnExpr(key, key, typ), + } } - typ := colExprTypes[0].Type - if !types.IsSigned(typ) { - return nil, false + if len(res) == 0 { + return nil } + return res +} +func setToIntRangeColl(setVals []any, typ sql.Type) (sql.MySQLRangeCollection, bool) { keys := make([]int64, 0, len(setVals)) for _, val := range setVals { switch v := val.(type) { @@ -891,19 +901,55 @@ func setToSignedIntRange(setVals []any, colExprTypes []sql.ColumnExpressionType) } } - slices.Sort(keys) - keys = slices.Compact(keys) - res := make(sql.MySQLRangeCollection, len(keys)) - for i, key := range keys { - res[i] = sql.MySQLRange{ - sql.ClosedRangeColumnExpr(key, key, typ), - } - } + return keysToRangeColl(keys, typ), true +} - if len(res) == 0 { - return nil, true +func setToUintRangeColl(setVals []any, typ sql.Type) (sql.MySQLRangeCollection, bool) { + keys := make([]uint64, 0, len(setVals)) + for _, val := range setVals { + switch v := val.(type) { + case int: + keys = append(keys, uint64(v)) + case int8: + keys = append(keys, uint64(v)) + case int16: + keys = append(keys, uint64(v)) + case int32: + keys = append(keys, uint64(v)) + case int64: + keys = append(keys, uint64(v)) + case uint: + keys = append(keys, uint64(v)) + case uint8: + keys = append(keys, uint64(v)) + case uint16: + keys = append(keys, uint64(v)) + case uint32: + keys = append(keys, uint64(v)) + case uint64: + keys = append(keys, v) + // float32, float64, and decimal are ok as long as they don't round + case float32: + key := uint64(v) + if float32(key) == v { + keys = append(keys, key) + } + case float64: + key := uint64(v) + if float64(key) == v { + keys = append(keys, key) + } + case decimal.Decimal: + key := v.IntPart() + if v.Equal(decimal.NewFromInt(key)) { + keys = append(keys, uint64(key)) + } + default: + // resort to default behavior for types that require more conversion + return nil, false + } } - return res, true + return keysToRangeColl(keys, typ), true } // buildRangeCollection converts our representation of the best index scan @@ -919,11 +965,23 @@ func (b *indexScanRangeBuilder) buildRangeCollection(f indexFilter) (sql.MySQLRa case *iScanOr: ranges, err = b.rangeBuildOr(f, inScan) case *iScanLeaf: - // TODO: special case for in set. can skip building range tree and overlapping range check since it's a series of equality checks + // When the filter is a simple IN, we can skip costly checks like building the RangeTree. if f.Op() == sql.IndexScanOpInSet { cets := b.idx.ColumnExpressionTypes() - if ranges, ok := setToSignedIntRange(f.setValues, cets); ok { - return ranges, nil + if len(cets) == 1 { + typ := cets[0].Type + var ok bool + // TODO: it's possible to apply this optimization to other + // numeric types (float32, float64, decimal). + if types.IsSigned(typ) { + if ranges, ok = setToIntRangeColl(f.setValues, typ); ok { + return ranges, nil + } + } else if types.IsUnsigned(typ) { + if ranges, ok = setToUintRangeColl(f.setValues, typ); ok { + return ranges, nil + } + } } } ranges, err = b.rangeBuildLeaf(f, inScan) From 20575f26722d1dd74623b82f59fadfd4880f09f9 Mon Sep 17 00:00:00 2001 From: James Cor Date: Fri, 5 Dec 2025 12:49:26 -0800 Subject: [PATCH 06/10] revert --- enginetest/memory_engine_test.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index b447ecc83b..f1ec7b45d0 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -200,18 +200,23 @@ func TestSingleQueryPrepared(t *testing.T) { // Convenience test for debugging a single query. Unskip and set to the desired query. func TestSingleScript(t *testing.T) { - //t.Skip() + t.Skip() var scripts = []queries.ScriptTest{ { - Name: "aaaaa", - SetUpScript: []string{ - "create table t (i int primary key, j int);", - "insert into t values (1, 1), (2, 2), (3, 3);", - }, + Name: "AS OF propagates to nested CALLs", + SetUpScript: []string{}, Assertions: []queries.ScriptTestAssertion{ { - Query: "select * from t where i in (1, 2, 3)", - Expected: []sql.Row{}, + Query: "create procedure create_proc() create table t (i int primary key, j int);", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "call create_proc()", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, }, }, }, From d2f182262cff0b90904cc5236305af97c523456d Mon Sep 17 00:00:00 2001 From: James Cor Date: Wed, 10 Dec 2025 14:57:05 -0800 Subject: [PATCH 07/10] almost working --- sql/analyzer/costed_index_scan.go | 172 +++++++++++------------------- 1 file changed, 63 insertions(+), 109 deletions(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 52eab16362..95501955b2 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -17,6 +17,7 @@ package analyzer import ( "cmp" "fmt" + "github.com/dolthub/vitess/go/sqltypes" "slices" "sort" "strings" @@ -838,118 +839,80 @@ type indexScanRangeBuilder struct { leftover []sql.Expression } -func keysToRangeColl[N cmp.Ordered](keys []N, typ sql.Type) sql.MySQLRangeCollection { - slices.Sort(keys) - keys = slices.Compact(keys) - // TODO: for integers, if len(keys) - 1 == keys[len(keys)-1] - keys[0], - // then we can just have one continuous range. unsure if this is worth - res := make(sql.MySQLRangeCollection, len(keys)) - for i, key := range keys { - res[i] = sql.MySQLRange{ - sql.ClosedRangeColumnExpr(key, key, typ), - } - } - if len(res) == 0 { - return nil - } - return res -} - -func setToIntRangeColl(setVals []any, typ sql.Type) (sql.MySQLRangeCollection, bool) { - keys := make([]int64, 0, len(setVals)) - for _, val := range setVals { +func inValsToMySQLRangeCollHelper[N cmp.Ordered](ctx *sql.Context, vals []any, typ sql.Type, precise bool) (sql.MySQLRangeCollection, bool) { + keys := make([]N, 0, len(vals)) + for _, val := range vals { switch v := val.(type) { - case int: - keys = append(keys, int64(v)) - case int8: - keys = append(keys, int64(v)) - case int16: - keys = append(keys, int64(v)) - case int32: - keys = append(keys, int64(v)) - case int64: - keys = append(keys, v) - case uint: - keys = append(keys, int64(v)) - case uint8: - keys = append(keys, int64(v)) - case uint16: - keys = append(keys, int64(v)) - case uint32: - keys = append(keys, int64(v)) - case uint64: - keys = append(keys, int64(v)) - // float32, float64, and decimal are ok as long as they don't round + case int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64: case float32: - key := int64(v) - if float32(key) == v { - keys = append(keys, key) + if precise && float32(int(v)) != v { + continue } case float64: - key := int64(v) - if float64(key) == v { - keys = append(keys, key) + if precise && float64(int(v)) != v { + continue } case decimal.Decimal: - key := v.IntPart() - if v.Equal(decimal.NewFromInt(key)) { - keys = append(keys, key) + if precise && v.Equal(decimal.NewFromInt(v.IntPart())) { + continue } default: - // resort to default behavior for types that require more conversion return nil, false } + key, inRange, err := typ.Convert(ctx, val) + if err != nil { + return nil, false + } + if !inRange { + continue + } + keys = append(keys, key.(N)) } - return keysToRangeColl(keys, typ), true -} - -func setToUintRangeColl(setVals []any, typ sql.Type) (sql.MySQLRangeCollection, bool) { - keys := make([]uint64, 0, len(setVals)) - for _, val := range setVals { - switch v := val.(type) { - case int: - keys = append(keys, uint64(v)) - case int8: - keys = append(keys, uint64(v)) - case int16: - keys = append(keys, uint64(v)) - case int32: - keys = append(keys, uint64(v)) - case int64: - keys = append(keys, uint64(v)) - case uint: - keys = append(keys, uint64(v)) - case uint8: - keys = append(keys, uint64(v)) - case uint16: - keys = append(keys, uint64(v)) - case uint32: - keys = append(keys, uint64(v)) - case uint64: - keys = append(keys, v) - // float32, float64, and decimal are ok as long as they don't round - case float32: - key := uint64(v) - if float32(key) == v { - keys = append(keys, key) - } - case float64: - key := uint64(v) - if float64(key) == v { - keys = append(keys, key) - } - case decimal.Decimal: - key := v.IntPart() - if v.Equal(decimal.NewFromInt(key)) { - keys = append(keys, uint64(key)) - } - default: - // resort to default behavior for types that require more conversion - return nil, false + // TODO: for integers, if len(keys) - 1 == keys[len(keys)-1] - keys[0], + // then we can just have one continuous range. unsure if this is worth it + slices.Sort(keys) + keys = slices.Compact(keys) + res := make(sql.MySQLRangeCollection, len(keys)) + for i, key := range keys { + res[i] = sql.MySQLRange{ + sql.ClosedRangeColumnExpr(key, key, typ), } } - return keysToRangeColl(keys, typ), true + + if len(res) == 0 { + return nil, true + } + return res, true +} + +// inValsToMySQLRangeColl is a fast path for in filters over numeric columns. +func inValsToMySQLRangeColl(ctx *sql.Context, vals []any, typ sql.Type) (sql.MySQLRangeCollection, bool) { + switch typ.Type() { + case sqltypes.Int8: + return inValsToMySQLRangeCollHelper[int8](ctx, vals, typ, true) + case sqltypes.Int16: + return inValsToMySQLRangeCollHelper[int16](ctx, vals, typ, true) + case sqltypes.Int32: + return inValsToMySQLRangeCollHelper[int32](ctx, vals, typ, true) + case sqltypes.Int64: + return inValsToMySQLRangeCollHelper[int64](ctx, vals, typ, true) + case sqltypes.Uint8: + return inValsToMySQLRangeCollHelper[uint8](ctx, vals, typ, true) + case sqltypes.Uint16: + return inValsToMySQLRangeCollHelper[uint16](ctx, vals, typ, true) + case sqltypes.Uint32: + return inValsToMySQLRangeCollHelper[uint32](ctx, vals, typ, true) + case sqltypes.Uint64: + return inValsToMySQLRangeCollHelper[uint64](ctx, vals, typ, true) + case sqltypes.Float32: + return inValsToMySQLRangeCollHelper[float32](ctx, vals, typ, false) + case sqltypes.Float64: + return inValsToMySQLRangeCollHelper[float64](ctx, vals, typ, false) + default: + return nil, false + } } // buildRangeCollection converts our representation of the best index scan @@ -969,18 +932,9 @@ func (b *indexScanRangeBuilder) buildRangeCollection(f indexFilter) (sql.MySQLRa if f.Op() == sql.IndexScanOpInSet { cets := b.idx.ColumnExpressionTypes() if len(cets) == 1 { - typ := cets[0].Type var ok bool - // TODO: it's possible to apply this optimization to other - // numeric types (float32, float64, decimal). - if types.IsSigned(typ) { - if ranges, ok = setToIntRangeColl(f.setValues, typ); ok { - return ranges, nil - } - } else if types.IsUnsigned(typ) { - if ranges, ok = setToUintRangeColl(f.setValues, typ); ok { - return ranges, nil - } + if ranges, ok = inValsToMySQLRangeColl(b.ctx, f.setValues, cets[0].Type); ok { + return ranges, nil } } } From d39b6cd854754668d1a0bc72dafb975a161db6c3 Mon Sep 17 00:00:00 2001 From: James Cor Date: Wed, 10 Dec 2025 15:24:49 -0800 Subject: [PATCH 08/10] flipped --- sql/analyzer/costed_index_scan.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index 95501955b2..a135f6751e 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -854,7 +854,7 @@ func inValsToMySQLRangeCollHelper[N cmp.Ordered](ctx *sql.Context, vals []any, t continue } case decimal.Decimal: - if precise && v.Equal(decimal.NewFromInt(v.IntPart())) { + if precise && !v.Equal(decimal.NewFromInt(v.IntPart())) { continue } default: @@ -864,7 +864,7 @@ func inValsToMySQLRangeCollHelper[N cmp.Ordered](ctx *sql.Context, vals []any, t if err != nil { return nil, false } - if !inRange { + if inRange != sql.InRange { continue } keys = append(keys, key.(N)) From d8f3c144afbde6a6927cd6b702305543e739da2f Mon Sep 17 00:00:00 2001 From: jycor Date: Wed, 10 Dec 2025 23:26:50 +0000 Subject: [PATCH 09/10] [ga-format-pr] Run ./format_repo.sh to fix formatting --- sql/analyzer/costed_index_scan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index a135f6751e..ee506a7999 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -17,12 +17,12 @@ package analyzer import ( "cmp" "fmt" - "github.com/dolthub/vitess/go/sqltypes" "slices" "sort" "strings" "time" + "github.com/dolthub/vitess/go/sqltypes" "github.com/shopspring/decimal" "github.com/dolthub/go-mysql-server/sql" From 033bda47f313494aba799233e1b61d17bafa9035 Mon Sep 17 00:00:00 2001 From: James Cor Date: Thu, 11 Dec 2025 16:35:36 -0800 Subject: [PATCH 10/10] fix conflicts --- sql/analyzer/costed_index_scan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/analyzer/costed_index_scan.go b/sql/analyzer/costed_index_scan.go index ee506a7999..f27e6002a7 100644 --- a/sql/analyzer/costed_index_scan.go +++ b/sql/analyzer/costed_index_scan.go @@ -1537,7 +1537,7 @@ func newLeaf(ctx *sql.Context, id indexScanId, e sql.Expression, underlying stri var litSet []interface{} var setTypes []sql.Type var litType sql.Type - for i, lit := range tup { + for _, lit := range tup { value, err := lit.Eval(ctx, nil) if err != nil { return nil, false