332 lines
9.6 KiB
Go
Executable File
332 lines
9.6 KiB
Go
Executable File
// Copyright 2025 Dolthub, 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 pgcatalog
|
|
|
|
import (
|
|
"io"
|
|
"iter"
|
|
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
"github.com/google/btree"
|
|
)
|
|
|
|
// inMemIndexScanIter is a sql.RowIter that uses an in-memory btree index to satisfy index lookups
|
|
// on pg_catalog tables.
|
|
type inMemIndexScanIter[T any] struct {
|
|
lookup sql.IndexLookup
|
|
rangeConverter RangeConverter[T]
|
|
btreeAccess BTreeStorageAccess[T]
|
|
rowConverter rowConverter[T]
|
|
rangeIdx int
|
|
next func() (T, bool)
|
|
stop func()
|
|
}
|
|
|
|
var _ sql.RowIter = (*inMemIndexScanIter[any])(nil)
|
|
|
|
// RangeConverter knows how to convert a Range to bounds for a btree scan. The two values returned are the
|
|
// greater-than-or-equal lower bound, and the less-than upper bound for this index.
|
|
type RangeConverter[T any] interface {
|
|
getIndexScanRange(rng sql.Range, index sql.Index) (T, bool, T, bool)
|
|
}
|
|
|
|
// BTreeStorageAccess knows how to get a btree index by name. This interface needs two methods because
|
|
// unique and non-unique indexes have different types as stored in the btree package.
|
|
type BTreeStorageAccess[T any] interface {
|
|
getIndex(name string) *inMemIndexStorage[T]
|
|
}
|
|
|
|
// rowConverter converts a value of type T to a sql.Row.
|
|
type rowConverter[T any] func(T) sql.Row
|
|
|
|
// Next implements the sql.RowIter interface.
|
|
func (l *inMemIndexScanIter[T]) Next(ctx *sql.Context) (sql.Row, error) {
|
|
nextClass, err := l.nextItem()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return l.rowConverter(*nextClass), nil
|
|
}
|
|
|
|
// Close implements the sql.RowIter interface.
|
|
func (l *inMemIndexScanIter[T]) Close(ctx *sql.Context) error {
|
|
if l.stop != nil {
|
|
l.stop()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// nextItem returns the next item from the index lookup, or io.EOF if there are no more items.
|
|
// Needs to return a pointer to T so that we can return nil for EOF.
|
|
func (l *inMemIndexScanIter[T]) nextItem() (*T, error) {
|
|
if l.rangeIdx >= l.lookup.Ranges.Len() {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
if l.next != nil {
|
|
next, ok := l.next()
|
|
if !ok {
|
|
l.stop()
|
|
l.next = nil
|
|
l.stop = nil
|
|
l.rangeIdx++
|
|
return l.nextItem()
|
|
}
|
|
return &next, nil
|
|
}
|
|
|
|
inMemIndex := l.lookup.Index.(pgCatalogInMemIndex)
|
|
rng := l.lookup.Ranges.ToRanges()[l.rangeIdx]
|
|
gte, hasLowerBound, lt, hasUpperBound := l.rangeConverter.getIndexScanRange(rng, l.lookup.Index)
|
|
idx := l.btreeAccess.getIndex(inMemIndex.name)
|
|
if hasLowerBound && hasUpperBound {
|
|
l.next, l.stop = idx.IterRange(gte, lt)
|
|
} else if hasLowerBound {
|
|
l.next, l.stop = idx.IterGreaterThanEqual(gte)
|
|
} else if hasUpperBound {
|
|
l.next, l.stop = idx.IterLessThan(lt)
|
|
} else {
|
|
// We don't support nil lookups for this kind of index, there are never nillable elements
|
|
return nil, io.EOF
|
|
}
|
|
|
|
return l.nextItem()
|
|
}
|
|
|
|
// pgCatalogInMemIndex is an in-memory implementation of sql.Index for pg_catalog tables.
|
|
type pgCatalogInMemIndex struct {
|
|
name string
|
|
tblName string
|
|
dbName string
|
|
uniq bool
|
|
columnExprs []sql.ColumnExpressionType
|
|
}
|
|
|
|
var _ sql.Index = (*pgCatalogInMemIndex)(nil)
|
|
|
|
// ID implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) ID() string {
|
|
return p.name
|
|
}
|
|
|
|
// Database implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) Database() string {
|
|
return p.dbName
|
|
}
|
|
|
|
// Table implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) Table() string {
|
|
return p.tblName
|
|
}
|
|
|
|
// Expressions implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) Expressions() []string {
|
|
exprs := make([]string, len(p.columnExprs))
|
|
for i, expr := range p.columnExprs {
|
|
exprs[i] = expr.Expression
|
|
}
|
|
return exprs
|
|
}
|
|
|
|
// IsUnique implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) IsUnique() bool {
|
|
return p.uniq
|
|
}
|
|
|
|
// IsSpatial implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) IsSpatial() bool {
|
|
return false
|
|
}
|
|
|
|
// IsFullText implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) IsFullText() bool {
|
|
return false
|
|
}
|
|
|
|
// IsVector implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) IsVector() bool {
|
|
return false
|
|
}
|
|
|
|
// Comment implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) Comment() string {
|
|
return ""
|
|
}
|
|
|
|
// IndexType implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) IndexType() string {
|
|
return "BTREE"
|
|
}
|
|
|
|
// IsGenerated implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) IsGenerated() bool {
|
|
return false
|
|
}
|
|
|
|
// ColumnExpressionTypes implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) ColumnExpressionTypes(ctx *sql.Context) []sql.ColumnExpressionType {
|
|
return p.columnExprs
|
|
}
|
|
|
|
// CanSupport implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) CanSupport(context *sql.Context, r ...sql.Range) bool {
|
|
return true
|
|
}
|
|
|
|
// CanSupportOrderBy implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) CanSupportOrderBy(expr sql.Expression) bool {
|
|
return true
|
|
}
|
|
|
|
// PrefixLengths implements the interface sql.Index.
|
|
func (p pgCatalogInMemIndex) PrefixLengths() []uint16 {
|
|
return make([]uint16, len(p.columnExprs))
|
|
}
|
|
|
|
var _ sql.Index = (*pgCatalogInMemIndex)(nil)
|
|
|
|
// inMemIndexPartition is a sql.Partition that represents the single partition for an in memory index lookup.
|
|
type inMemIndexPartition struct {
|
|
idxName string
|
|
lookup sql.IndexLookup
|
|
}
|
|
|
|
var _ sql.Partition = (*inMemIndexPartition)(nil)
|
|
|
|
// Key implements the interface sql.Partition.
|
|
func (p inMemIndexPartition) Key() []byte {
|
|
return []byte(p.idxName)
|
|
}
|
|
|
|
// inMemIndexPartIter is a sql.PartitionIter that returns a single partition for an in memory index lookup.
|
|
type inMemIndexPartIter struct {
|
|
used bool
|
|
part inMemIndexPartition
|
|
}
|
|
|
|
var _ sql.PartitionIter = (*inMemIndexPartIter)(nil)
|
|
|
|
// Close implements the interface sql.PartitionIter.
|
|
func (p inMemIndexPartIter) Close(context *sql.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// Next implements the interface sql.PartitionIter.
|
|
func (p *inMemIndexPartIter) Next(context *sql.Context) (sql.Partition, error) {
|
|
if p.used {
|
|
return nil, io.EOF
|
|
}
|
|
p.used = true
|
|
return p.part, nil
|
|
}
|
|
|
|
// inMemIndexStorage is an in-memory storage for an index using a btree, abstracting away the differences between
|
|
// unique and non-unique indexes.
|
|
type inMemIndexStorage[T any] struct {
|
|
uniqTree *btree.BTreeG[T]
|
|
nonUniqTree *btree.BTreeG[[]T]
|
|
}
|
|
|
|
// NewUniqueInMemIndexStorage creates a new in-memory index storage for a unique index.
|
|
func NewUniqueInMemIndexStorage[T any](lessFunc func(a, b T) bool) *inMemIndexStorage[T] {
|
|
return &inMemIndexStorage[T]{
|
|
uniqTree: btree.NewG[T](2, lessFunc),
|
|
}
|
|
}
|
|
|
|
// NewNonUniqueInMemIndexStorage creates a new in-memory index storage for a non-unique index.
|
|
func NewNonUniqueInMemIndexStorage[T any](lessFunc func(a, b []T) bool) *inMemIndexStorage[T] {
|
|
return &inMemIndexStorage[T]{
|
|
nonUniqTree: btree.NewG[[]T](2, lessFunc),
|
|
}
|
|
}
|
|
|
|
// Add adds a value to the in-memory index storage.
|
|
func (s *inMemIndexStorage[T]) Add(val T) {
|
|
if s.uniqTree != nil {
|
|
s.uniqTree.ReplaceOrInsert(val)
|
|
} else {
|
|
existing, replaced := s.nonUniqTree.ReplaceOrInsert([]T{val})
|
|
if replaced {
|
|
existing = append(existing, val)
|
|
s.nonUniqTree.ReplaceOrInsert(existing)
|
|
}
|
|
}
|
|
}
|
|
|
|
// IterRange implements an in-order iteration over the index values in the range [gte, lt). All values in the
|
|
// index in the range are sent to the channel
|
|
func (s *inMemIndexStorage[T]) IterRange(gte, lt T) (next func() (T, bool), stop func()) {
|
|
if s.uniqTree != nil {
|
|
return iter.Pull(func(yield func(T) bool) {
|
|
s.uniqTree.AscendRange(gte, lt, yield)
|
|
})
|
|
} else {
|
|
next, stop := iter.Pull(func(yield func([]T) bool) {
|
|
s.nonUniqTree.AscendRange([]T{gte}, []T{lt}, yield)
|
|
})
|
|
return s.unnestIter(next, stop)
|
|
}
|
|
}
|
|
|
|
// IterGreaterThanEqual implements an in-order iteration over the index values greater than or equal to the given value.
|
|
// All values in the index greater than or equal to the given value are sent to the channel.
|
|
func (s *inMemIndexStorage[T]) IterGreaterThanEqual(gte T) (next func() (T, bool), stop func()) {
|
|
if s.uniqTree != nil {
|
|
return iter.Pull(func(yield func(T) bool) {
|
|
s.uniqTree.AscendGreaterOrEqual(gte, yield)
|
|
})
|
|
} else {
|
|
next, stop := iter.Pull(func(yield func([]T) bool) {
|
|
s.nonUniqTree.AscendGreaterOrEqual([]T{gte}, yield)
|
|
})
|
|
return s.unnestIter(next, stop)
|
|
}
|
|
}
|
|
|
|
// IterLessThan implements an in-order iteration over the index values less than the given value.
|
|
// All values in the index less than or equal to the given value are sent to the channel.
|
|
func (s *inMemIndexStorage[T]) IterLessThan(lt T) (next func() (T, bool), stop func()) {
|
|
if s.uniqTree != nil {
|
|
return iter.Pull(func(yield func(T) bool) {
|
|
s.uniqTree.AscendLessThan(lt, yield)
|
|
})
|
|
} else {
|
|
next, stop := iter.Pull(func(yield func([]T) bool) {
|
|
s.nonUniqTree.AscendLessThan([]T{lt}, yield)
|
|
})
|
|
return s.unnestIter(next, stop)
|
|
}
|
|
}
|
|
|
|
// unnestIter takes an iterator that returns slices of T, and returns an iterator that returns individual T values.
|
|
func (s *inMemIndexStorage[T]) unnestIter(sNext func() ([]T, bool), sStop func()) (next func() (T, bool), stop func()) {
|
|
return iter.Pull(func(yield func(T) bool) {
|
|
defer sStop()
|
|
for {
|
|
items, ok := sNext()
|
|
if !ok {
|
|
return
|
|
}
|
|
for _, item := range items {
|
|
if !yield(item) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|