mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	* Dropped unused codekit config * Integrated dynamic and static bindata for public * Ignore public bindata * Add a general generate make task * Integrated flexible public assets into web command * Updated vendoring, added all missiong govendor deps * Made the linter happy with the bindata and dynamic code * Moved public bindata definition to modules directory * Ignoring the new bindata path now * Updated to the new public modules import path * Updated public bindata command and drop the new prefix
		
			
				
	
	
		
			506 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			506 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 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,
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package plan
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 	"sort"
 | |
| 
 | |
| 	"github.com/juju/errors"
 | |
| 	"github.com/pingcap/tidb/ast"
 | |
| 	"github.com/pingcap/tidb/parser/opcode"
 | |
| 	"github.com/pingcap/tidb/util/types"
 | |
| )
 | |
| 
 | |
| type rangePoint struct {
 | |
| 	value types.Datum
 | |
| 	excl  bool // exclude
 | |
| 	start bool
 | |
| }
 | |
| 
 | |
| func (rp rangePoint) String() string {
 | |
| 	val := rp.value.GetValue()
 | |
| 	if rp.value.Kind() == types.KindMinNotNull {
 | |
| 		val = "-inf"
 | |
| 	} else if rp.value.Kind() == types.KindMaxValue {
 | |
| 		val = "+inf"
 | |
| 	}
 | |
| 	if rp.start {
 | |
| 		symbol := "["
 | |
| 		if rp.excl {
 | |
| 			symbol = "("
 | |
| 		}
 | |
| 		return fmt.Sprintf("%s%v", symbol, val)
 | |
| 	}
 | |
| 	symbol := "]"
 | |
| 	if rp.excl {
 | |
| 		symbol = ")"
 | |
| 	}
 | |
| 	return fmt.Sprintf("%v%s", val, symbol)
 | |
| }
 | |
| 
 | |
| type rangePointSorter struct {
 | |
| 	points []rangePoint
 | |
| 	err    error
 | |
| }
 | |
| 
 | |
| func (r *rangePointSorter) Len() int {
 | |
| 	return len(r.points)
 | |
| }
 | |
| 
 | |
| func (r *rangePointSorter) Less(i, j int) bool {
 | |
| 	a := r.points[i]
 | |
| 	b := r.points[j]
 | |
| 	cmp, err := a.value.CompareDatum(b.value)
 | |
| 	if err != nil {
 | |
| 		r.err = err
 | |
| 		return true
 | |
| 	}
 | |
| 	if cmp == 0 {
 | |
| 		return r.equalValueLess(a, b)
 | |
| 	}
 | |
| 	return cmp < 0
 | |
| }
 | |
| 
 | |
| func (r *rangePointSorter) equalValueLess(a, b rangePoint) bool {
 | |
| 	if a.start && b.start {
 | |
| 		return !a.excl && b.excl
 | |
| 	} else if a.start {
 | |
| 		return !b.excl
 | |
| 	} else if b.start {
 | |
| 		return a.excl || b.excl
 | |
| 	}
 | |
| 	return a.excl && !b.excl
 | |
| }
 | |
| 
 | |
| func (r *rangePointSorter) Swap(i, j int) {
 | |
| 	r.points[i], r.points[j] = r.points[j], r.points[i]
 | |
| }
 | |
| 
 | |
| type rangeBuilder struct {
 | |
| 	err error
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) build(expr ast.ExprNode) []rangePoint {
 | |
| 	switch x := expr.(type) {
 | |
| 	case *ast.BinaryOperationExpr:
 | |
| 		return r.buildFromBinop(x)
 | |
| 	case *ast.PatternInExpr:
 | |
| 		return r.buildFromIn(x)
 | |
| 	case *ast.ParenthesesExpr:
 | |
| 		return r.build(x.Expr)
 | |
| 	case *ast.BetweenExpr:
 | |
| 		return r.buildFromBetween(x)
 | |
| 	case *ast.IsNullExpr:
 | |
| 		return r.buildFromIsNull(x)
 | |
| 	case *ast.IsTruthExpr:
 | |
| 		return r.buildFromIsTruth(x)
 | |
| 	case *ast.PatternLikeExpr:
 | |
| 		rans := r.buildFromPatternLike(x)
 | |
| 		return rans
 | |
| 	case *ast.ColumnNameExpr:
 | |
| 		return r.buildFromColumnName(x)
 | |
| 	}
 | |
| 	return fullRange
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromBinop(x *ast.BinaryOperationExpr) []rangePoint {
 | |
| 	if x.Op == opcode.OrOr {
 | |
| 		return r.union(r.build(x.L), r.build(x.R))
 | |
| 	} else if x.Op == opcode.AndAnd {
 | |
| 		return r.intersection(r.build(x.L), r.build(x.R))
 | |
| 	}
 | |
| 	// This has been checked that the binary operation is comparison operation, and one of
 | |
| 	// the operand is column name expression.
 | |
| 	var value types.Datum
 | |
| 	var op opcode.Op
 | |
| 	if _, ok := x.L.(*ast.ValueExpr); ok {
 | |
| 		value = types.NewDatum(x.L.GetValue())
 | |
| 		switch x.Op {
 | |
| 		case opcode.GE:
 | |
| 			op = opcode.LE
 | |
| 		case opcode.GT:
 | |
| 			op = opcode.LT
 | |
| 		case opcode.LT:
 | |
| 			op = opcode.GT
 | |
| 		case opcode.LE:
 | |
| 			op = opcode.GE
 | |
| 		default:
 | |
| 			op = x.Op
 | |
| 		}
 | |
| 	} else {
 | |
| 		value = types.NewDatum(x.R.GetValue())
 | |
| 		op = x.Op
 | |
| 	}
 | |
| 	if value.Kind() == types.KindNull {
 | |
| 		return nil
 | |
| 	}
 | |
| 	switch op {
 | |
| 	case opcode.EQ:
 | |
| 		startPoint := rangePoint{value: value, start: true}
 | |
| 		endPoint := rangePoint{value: value}
 | |
| 		return []rangePoint{startPoint, endPoint}
 | |
| 	case opcode.NE:
 | |
| 		startPoint1 := rangePoint{value: types.MinNotNullDatum(), start: true}
 | |
| 		endPoint1 := rangePoint{value: value, excl: true}
 | |
| 		startPoint2 := rangePoint{value: value, start: true, excl: true}
 | |
| 		endPoint2 := rangePoint{value: types.MaxValueDatum()}
 | |
| 		return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
 | |
| 	case opcode.LT:
 | |
| 		startPoint := rangePoint{value: types.MinNotNullDatum(), start: true}
 | |
| 		endPoint := rangePoint{value: value, excl: true}
 | |
| 		return []rangePoint{startPoint, endPoint}
 | |
| 	case opcode.LE:
 | |
| 		startPoint := rangePoint{value: types.MinNotNullDatum(), start: true}
 | |
| 		endPoint := rangePoint{value: value}
 | |
| 		return []rangePoint{startPoint, endPoint}
 | |
| 	case opcode.GT:
 | |
| 		startPoint := rangePoint{value: value, start: true, excl: true}
 | |
| 		endPoint := rangePoint{value: types.MaxValueDatum()}
 | |
| 		return []rangePoint{startPoint, endPoint}
 | |
| 	case opcode.GE:
 | |
| 		startPoint := rangePoint{value: value, start: true}
 | |
| 		endPoint := rangePoint{value: types.MaxValueDatum()}
 | |
| 		return []rangePoint{startPoint, endPoint}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromIn(x *ast.PatternInExpr) []rangePoint {
 | |
| 	if x.Not {
 | |
| 		r.err = ErrUnsupportedType.Gen("NOT IN is not supported")
 | |
| 		return fullRange
 | |
| 	}
 | |
| 	var rangePoints []rangePoint
 | |
| 	for _, v := range x.List {
 | |
| 		startPoint := rangePoint{value: types.NewDatum(v.GetValue()), start: true}
 | |
| 		endPoint := rangePoint{value: types.NewDatum(v.GetValue())}
 | |
| 		rangePoints = append(rangePoints, startPoint, endPoint)
 | |
| 	}
 | |
| 	sorter := rangePointSorter{points: rangePoints}
 | |
| 	sort.Sort(&sorter)
 | |
| 	if sorter.err != nil {
 | |
| 		r.err = sorter.err
 | |
| 	}
 | |
| 	// check duplicates
 | |
| 	hasDuplicate := false
 | |
| 	isStart := false
 | |
| 	for _, v := range rangePoints {
 | |
| 		if isStart == v.start {
 | |
| 			hasDuplicate = true
 | |
| 			break
 | |
| 		}
 | |
| 		isStart = v.start
 | |
| 	}
 | |
| 	if !hasDuplicate {
 | |
| 		return rangePoints
 | |
| 	}
 | |
| 	// remove duplicates
 | |
| 	distinctRangePoints := make([]rangePoint, 0, len(rangePoints))
 | |
| 	isStart = false
 | |
| 	for i := 0; i < len(rangePoints); i++ {
 | |
| 		current := rangePoints[i]
 | |
| 		if isStart == current.start {
 | |
| 			continue
 | |
| 		}
 | |
| 		distinctRangePoints = append(distinctRangePoints, current)
 | |
| 		isStart = current.start
 | |
| 	}
 | |
| 	return distinctRangePoints
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromBetween(x *ast.BetweenExpr) []rangePoint {
 | |
| 	if x.Not {
 | |
| 		binop1 := &ast.BinaryOperationExpr{Op: opcode.LT, L: x.Expr, R: x.Left}
 | |
| 		binop2 := &ast.BinaryOperationExpr{Op: opcode.GT, L: x.Expr, R: x.Right}
 | |
| 		range1 := r.buildFromBinop(binop1)
 | |
| 		range2 := r.buildFromBinop(binop2)
 | |
| 		return r.union(range1, range2)
 | |
| 	}
 | |
| 	binop1 := &ast.BinaryOperationExpr{Op: opcode.GE, L: x.Expr, R: x.Left}
 | |
| 	binop2 := &ast.BinaryOperationExpr{Op: opcode.LE, L: x.Expr, R: x.Right}
 | |
| 	range1 := r.buildFromBinop(binop1)
 | |
| 	range2 := r.buildFromBinop(binop2)
 | |
| 	return r.intersection(range1, range2)
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromIsNull(x *ast.IsNullExpr) []rangePoint {
 | |
| 	if x.Not {
 | |
| 		startPoint := rangePoint{value: types.MinNotNullDatum(), start: true}
 | |
| 		endPoint := rangePoint{value: types.MaxValueDatum()}
 | |
| 		return []rangePoint{startPoint, endPoint}
 | |
| 	}
 | |
| 	startPoint := rangePoint{start: true}
 | |
| 	endPoint := rangePoint{}
 | |
| 	return []rangePoint{startPoint, endPoint}
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromIsTruth(x *ast.IsTruthExpr) []rangePoint {
 | |
| 	if x.True != 0 {
 | |
| 		if x.Not {
 | |
| 			// NOT TRUE range is {[null null] [0, 0]}
 | |
| 			startPoint1 := rangePoint{start: true}
 | |
| 			endPoint1 := rangePoint{}
 | |
| 			startPoint2 := rangePoint{start: true}
 | |
| 			startPoint2.value.SetInt64(0)
 | |
| 			endPoint2 := rangePoint{}
 | |
| 			endPoint2.value.SetInt64(0)
 | |
| 			return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
 | |
| 		}
 | |
| 		// TRUE range is {[-inf 0) (0 +inf]}
 | |
| 		startPoint1 := rangePoint{value: types.MinNotNullDatum(), start: true}
 | |
| 		endPoint1 := rangePoint{excl: true}
 | |
| 		endPoint1.value.SetInt64(0)
 | |
| 		startPoint2 := rangePoint{excl: true, start: true}
 | |
| 		startPoint2.value.SetInt64(0)
 | |
| 		endPoint2 := rangePoint{value: types.MaxValueDatum()}
 | |
| 		return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
 | |
| 	}
 | |
| 	if x.Not {
 | |
| 		startPoint1 := rangePoint{start: true}
 | |
| 		endPoint1 := rangePoint{excl: true}
 | |
| 		endPoint1.value.SetInt64(0)
 | |
| 		startPoint2 := rangePoint{start: true, excl: true}
 | |
| 		startPoint2.value.SetInt64(0)
 | |
| 		endPoint2 := rangePoint{value: types.MaxValueDatum()}
 | |
| 		return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
 | |
| 	}
 | |
| 	startPoint := rangePoint{start: true}
 | |
| 	startPoint.value.SetInt64(0)
 | |
| 	endPoint := rangePoint{}
 | |
| 	endPoint.value.SetInt64(0)
 | |
| 	return []rangePoint{startPoint, endPoint}
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromPatternLike(x *ast.PatternLikeExpr) []rangePoint {
 | |
| 	if x.Not {
 | |
| 		// Pattern not like is not supported.
 | |
| 		r.err = ErrUnsupportedType.Gen("NOT LIKE is not supported.")
 | |
| 		return fullRange
 | |
| 	}
 | |
| 	pattern, err := types.ToString(x.Pattern.GetValue())
 | |
| 	if err != nil {
 | |
| 		r.err = errors.Trace(err)
 | |
| 		return fullRange
 | |
| 	}
 | |
| 	lowValue := make([]byte, 0, len(pattern))
 | |
| 	// unscape the pattern
 | |
| 	var exclude bool
 | |
| 	for i := 0; i < len(pattern); i++ {
 | |
| 		if pattern[i] == x.Escape {
 | |
| 			i++
 | |
| 			if i < len(pattern) {
 | |
| 				lowValue = append(lowValue, pattern[i])
 | |
| 			} else {
 | |
| 				lowValue = append(lowValue, x.Escape)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		if pattern[i] == '%' {
 | |
| 			break
 | |
| 		} else if pattern[i] == '_' {
 | |
| 			exclude = true
 | |
| 			break
 | |
| 		}
 | |
| 		lowValue = append(lowValue, pattern[i])
 | |
| 	}
 | |
| 	if len(lowValue) == 0 {
 | |
| 		return []rangePoint{{value: types.MinNotNullDatum(), start: true}, {value: types.MaxValueDatum()}}
 | |
| 	}
 | |
| 	startPoint := rangePoint{start: true, excl: exclude}
 | |
| 	startPoint.value.SetBytesAsString(lowValue)
 | |
| 	highValue := make([]byte, len(lowValue))
 | |
| 	copy(highValue, lowValue)
 | |
| 	endPoint := rangePoint{excl: true}
 | |
| 	for i := len(highValue) - 1; i >= 0; i-- {
 | |
| 		highValue[i]++
 | |
| 		if highValue[i] != 0 {
 | |
| 			endPoint.value.SetBytesAsString(highValue)
 | |
| 			break
 | |
| 		}
 | |
| 		if i == 0 {
 | |
| 			endPoint.value = types.MaxValueDatum()
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	ranges := make([]rangePoint, 2)
 | |
| 	ranges[0] = startPoint
 | |
| 	ranges[1] = endPoint
 | |
| 	return ranges
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildFromColumnName(x *ast.ColumnNameExpr) []rangePoint {
 | |
| 	// column name expression is equivalent to column name is true.
 | |
| 	startPoint1 := rangePoint{value: types.MinNotNullDatum(), start: true}
 | |
| 	endPoint1 := rangePoint{excl: true}
 | |
| 	endPoint1.value.SetInt64(0)
 | |
| 	startPoint2 := rangePoint{excl: true, start: true}
 | |
| 	startPoint2.value.SetInt64(0)
 | |
| 	endPoint2 := rangePoint{value: types.MaxValueDatum()}
 | |
| 	return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) intersection(a, b []rangePoint) []rangePoint {
 | |
| 	return r.merge(a, b, false)
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) union(a, b []rangePoint) []rangePoint {
 | |
| 	return r.merge(a, b, true)
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) merge(a, b []rangePoint, union bool) []rangePoint {
 | |
| 	sorter := rangePointSorter{points: append(a, b...)}
 | |
| 	sort.Sort(&sorter)
 | |
| 	if sorter.err != nil {
 | |
| 		r.err = sorter.err
 | |
| 		return nil
 | |
| 	}
 | |
| 	var (
 | |
| 		merged               []rangePoint
 | |
| 		inRangeCount         int
 | |
| 		requiredInRangeCount int
 | |
| 	)
 | |
| 	if union {
 | |
| 		requiredInRangeCount = 1
 | |
| 	} else {
 | |
| 		requiredInRangeCount = 2
 | |
| 	}
 | |
| 	for _, val := range sorter.points {
 | |
| 		if val.start {
 | |
| 			inRangeCount++
 | |
| 			if inRangeCount == requiredInRangeCount {
 | |
| 				// just reached the required in range count, a new range started.
 | |
| 				merged = append(merged, val)
 | |
| 			}
 | |
| 		} else {
 | |
| 			if inRangeCount == requiredInRangeCount {
 | |
| 				// just about to leave the required in range count, the range is ended.
 | |
| 				merged = append(merged, val)
 | |
| 			}
 | |
| 			inRangeCount--
 | |
| 		}
 | |
| 	}
 | |
| 	return merged
 | |
| }
 | |
| 
 | |
| // buildIndexRanges build index ranges from range points.
 | |
| // Only the first column in the index is built, extra column ranges will be appended by
 | |
| // appendIndexRanges.
 | |
| func (r *rangeBuilder) buildIndexRanges(rangePoints []rangePoint) []*IndexRange {
 | |
| 	indexRanges := make([]*IndexRange, 0, len(rangePoints)/2)
 | |
| 	for i := 0; i < len(rangePoints); i += 2 {
 | |
| 		startPoint := rangePoints[i]
 | |
| 		endPoint := rangePoints[i+1]
 | |
| 		ir := &IndexRange{
 | |
| 			LowVal:      []types.Datum{startPoint.value},
 | |
| 			LowExclude:  startPoint.excl,
 | |
| 			HighVal:     []types.Datum{endPoint.value},
 | |
| 			HighExclude: endPoint.excl,
 | |
| 		}
 | |
| 		indexRanges = append(indexRanges, ir)
 | |
| 	}
 | |
| 	return indexRanges
 | |
| }
 | |
| 
 | |
| // appendIndexRanges appends additional column ranges for multi-column index.
 | |
| // The additional column ranges can only be appended to point ranges.
 | |
| // for example we have an index (a, b), if the condition is (a > 1 and b = 2)
 | |
| // then we can not build a conjunctive ranges for this index.
 | |
| func (r *rangeBuilder) appendIndexRanges(origin []*IndexRange, rangePoints []rangePoint) []*IndexRange {
 | |
| 	var newIndexRanges []*IndexRange
 | |
| 	for i := 0; i < len(origin); i++ {
 | |
| 		oRange := origin[i]
 | |
| 		if !oRange.IsPoint() {
 | |
| 			newIndexRanges = append(newIndexRanges, oRange)
 | |
| 		} else {
 | |
| 			newIndexRanges = append(newIndexRanges, r.appendIndexRange(oRange, rangePoints)...)
 | |
| 		}
 | |
| 	}
 | |
| 	return newIndexRanges
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) appendIndexRange(origin *IndexRange, rangePoints []rangePoint) []*IndexRange {
 | |
| 	newRanges := make([]*IndexRange, 0, len(rangePoints)/2)
 | |
| 	for i := 0; i < len(rangePoints); i += 2 {
 | |
| 		startPoint := rangePoints[i]
 | |
| 		lowVal := make([]types.Datum, len(origin.LowVal)+1)
 | |
| 		copy(lowVal, origin.LowVal)
 | |
| 		lowVal[len(origin.LowVal)] = startPoint.value
 | |
| 
 | |
| 		endPoint := rangePoints[i+1]
 | |
| 		highVal := make([]types.Datum, len(origin.HighVal)+1)
 | |
| 		copy(highVal, origin.HighVal)
 | |
| 		highVal[len(origin.HighVal)] = endPoint.value
 | |
| 
 | |
| 		ir := &IndexRange{
 | |
| 			LowVal:      lowVal,
 | |
| 			LowExclude:  startPoint.excl,
 | |
| 			HighVal:     highVal,
 | |
| 			HighExclude: endPoint.excl,
 | |
| 		}
 | |
| 		newRanges = append(newRanges, ir)
 | |
| 	}
 | |
| 	return newRanges
 | |
| }
 | |
| 
 | |
| func (r *rangeBuilder) buildTableRanges(rangePoints []rangePoint) []TableRange {
 | |
| 	tableRanges := make([]TableRange, 0, len(rangePoints)/2)
 | |
| 	for i := 0; i < len(rangePoints); i += 2 {
 | |
| 		startPoint := rangePoints[i]
 | |
| 		if startPoint.value.Kind() == types.KindNull || startPoint.value.Kind() == types.KindMinNotNull {
 | |
| 			startPoint.value.SetInt64(math.MinInt64)
 | |
| 		}
 | |
| 		startInt, err := types.ToInt64(startPoint.value.GetValue())
 | |
| 		if err != nil {
 | |
| 			r.err = errors.Trace(err)
 | |
| 			return tableRanges
 | |
| 		}
 | |
| 		startDatum := types.NewDatum(startInt)
 | |
| 		cmp, err := startDatum.CompareDatum(startPoint.value)
 | |
| 		if err != nil {
 | |
| 			r.err = errors.Trace(err)
 | |
| 			return tableRanges
 | |
| 		}
 | |
| 		if cmp < 0 || (cmp == 0 && startPoint.excl) {
 | |
| 			startInt++
 | |
| 		}
 | |
| 		endPoint := rangePoints[i+1]
 | |
| 		if endPoint.value.Kind() == types.KindNull {
 | |
| 			endPoint.value.SetInt64(math.MinInt64)
 | |
| 		} else if endPoint.value.Kind() == types.KindMaxValue {
 | |
| 			endPoint.value.SetInt64(math.MaxInt64)
 | |
| 		}
 | |
| 		endInt, err := types.ToInt64(endPoint.value.GetValue())
 | |
| 		if err != nil {
 | |
| 			r.err = errors.Trace(err)
 | |
| 			return tableRanges
 | |
| 		}
 | |
| 		endDatum := types.NewDatum(endInt)
 | |
| 		cmp, err = endDatum.CompareDatum(endPoint.value)
 | |
| 		if err != nil {
 | |
| 			r.err = errors.Trace(err)
 | |
| 			return tableRanges
 | |
| 		}
 | |
| 		if cmp > 0 || (cmp == 0 && endPoint.excl) {
 | |
| 			endInt--
 | |
| 		}
 | |
| 		if startInt > endInt {
 | |
| 			continue
 | |
| 		}
 | |
| 		tableRanges = append(tableRanges, TableRange{LowVal: startInt, HighVal: endInt})
 | |
| 	}
 | |
| 	return tableRanges
 | |
| }
 |