Files
dolthub--doltgresql/testing/generation/utils/statement_generator.go
T
2026-07-13 12:32:25 +08:00

767 lines
23 KiB
Go

// Copyright 2023-2024 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 utils
import (
"fmt"
"math"
"math/big"
"sort"
"strings"
)
// StatementGenerator represents a statement, and is able to produce all valid variations of the statement.
type StatementGenerator interface {
// AddChildren adds the given children to the generator. Not all generators accept all children, so this may error.
AddChildren(child ...StatementGenerator) error
// Consume returns true when the generator is able to produce a unique mutation, and false if it is not. Only one
// generator should mutate per call, meaning a parent generator should only mutate when its children return false.
// If the top-level generator returns false, then all permutations have been created.
Consume() bool
// SetConsumeIterations is equivalent to calling Copy then Consume the given number of times, without allocating a
// new StatementGenerator. This allows you to generate a specific statement efficiently, rather than calling Consume
// the given number of times. If the count is <= 0, then the statement will be in its original state (the same state
// as a StatementGenerator copy).
SetConsumeIterations(count *big.Int)
// SetConsumeIterationsFast is the same as SetConsumeIterations, except far more efficient due to using uint64,
// however it only works for iteration counts <= MAX_SIZE(uint64).
SetConsumeIterationsFast(count uint64)
// String returns a string based on the current permutation.
String() string
// Copy returns a copy of the given generator (along with all of its children) in its original setting. This means
// that the copy is in the same state that the target would be in if it had never called Consume.
Copy() StatementGenerator
// Reset sets the StatementGenerator back to its original state, which would be as though Consume was never called.
// This is equivalent to calling SetConsumeIterations(0), albeit slightly more efficient.
Reset()
// SourceString returns a string that may be used to recreate the StatementGenerator in a Go source file.
SourceString() string
// Permutations returns the number of unique permutations that the generator can return.
Permutations() *big.Int
// PermutationsUint64 returns the number of unique permutations that the generator can return. Returns true if the
// number fits within an uint64, false if it's larger than an uint64.
PermutationsUint64() (uint64, bool)
}
// TextGen is a generator that returns a simple string.
type TextGen string
var _ StatementGenerator = (*TextGen)(nil)
// Text creates a new StatementGenerator representing a simple string.
func Text(str string) *TextGen {
gen := TextGen(str)
return &gen
}
// AddChildren implements the interface StatementGenerator.
func (t *TextGen) AddChildren(children ...StatementGenerator) error {
return fmt.Errorf("text cannot have children")
}
// Consume implements the interface StatementGenerator.
func (t *TextGen) Consume() bool {
return false
}
// SetConsumeIterations implements the interface StatementGenerator.
func (t *TextGen) SetConsumeIterations(count *big.Int) {}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (t *TextGen) SetConsumeIterationsFast(count uint64) {}
// Copy implements the interface StatementGenerator.
func (t *TextGen) Copy() StatementGenerator {
if t == nil {
return nil
}
return Text(string(*t))
}
// String implements the interface StatementGenerator.
func (t *TextGen) String() string {
return string(*t)
}
// Reset implements the interface StatementGenerator.
func (t *TextGen) Reset() {}
// SourceString implements the interface StatementGenerator.
func (t *TextGen) SourceString() string {
return fmt.Sprintf(`Text("%s")`, string(*t))
}
// Permutations implements the interface StatementGenerator.
func (t *TextGen) Permutations() *big.Int {
return BigIntOne
}
// PermutationsUint64 implements the interface StatementGenerator.
func (t *TextGen) PermutationsUint64() (uint64, bool) {
return 1, true
}
// OrGen is a generator that contains multiple child generators, and will print only one at a time. Consuming will
// cycle to the next child.
type OrGen struct {
children []StatementGenerator
index int
localInt *big.Int
}
var _ StatementGenerator = (*OrGen)(nil)
// Or creates a new StatementGenerator representing an OrGen.
func Or(children ...StatementGenerator) *OrGen {
return &OrGen{
children: copyGenerators(children),
index: 0,
localInt: new(big.Int),
}
}
// AddChildren implements the interface StatementGenerator.
func (o *OrGen) AddChildren(children ...StatementGenerator) error {
o.children = append(o.children, copyGenerators(children)...)
return nil
}
// Consume implements the interface StatementGenerator.
func (o *OrGen) Consume() bool {
if len(o.children) == 0 {
return false
}
if o.children[o.index].Consume() {
return true
}
o.index++
if o.index >= len(o.children) {
o.index = 0
return false
}
return true
}
// SetConsumeIterations implements the interface StatementGenerator.
func (o *OrGen) SetConsumeIterations(count *big.Int) {
// If we're given zero, then we'll just call Reset
if count.Cmp(BigIntZero) <= 0 {
o.Reset()
return
}
count = o.localInt.Mod(count, o.Permutations())
for i, child := range o.children {
// The index is equal to whichever child we stop on
o.index = i
childPermutations := child.Permutations()
if childPermutations.Cmp(count) > 0 {
// The child has more permutations than the count, so we'll stop here
if count.Cmp(BigIntMaxUint64) <= 0 {
child.SetConsumeIterationsFast(count.Uint64())
} else {
child.SetConsumeIterations(count)
}
break
} else {
// The child's permutations are <= the count, so we'll reset it and subtract it from the total.
// Subtraction here is the opposite of the addition we do to determine the permutation count.
// Important to note that the permutations equaling the count means that the index increments to the next
// item, but since the count will be zero, it matches the original state of that item.
child.Reset()
count.Sub(count, childPermutations)
}
}
// We still need to reset any children that we never looped over
for i := o.index + 1; i < len(o.children); i++ {
o.children[i].Reset()
}
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (o *OrGen) SetConsumeIterationsFast(count uint64) {
// This is a copy of SetConsumeIterations, except rewritten to use uint64
if count <= 0 {
o.Reset()
return
}
permutations, _ := o.PermutationsUint64()
count = count % permutations
for i, child := range o.children {
o.index = i
childPermutations, _ := child.PermutationsUint64()
if childPermutations > count {
child.SetConsumeIterationsFast(count)
break
} else {
child.Reset()
count -= childPermutations
}
}
for i := o.index + 1; i < len(o.children); i++ {
o.children[i].Reset()
}
}
// Copy implements the interface StatementGenerator.
func (o *OrGen) Copy() StatementGenerator {
if o == nil {
return nil
}
return Or(o.children...)
}
// String implements the interface StatementGenerator.
func (o *OrGen) String() string {
return o.children[o.index].String()
}
// Reset implements the interface StatementGenerator.
func (o *OrGen) Reset() {
o.index = 0
for _, child := range o.children {
child.Reset()
}
}
// SourceString implements the interface StatementGenerator.
func (o *OrGen) SourceString() string {
return fmt.Sprintf(`Or(%s)`, sourceGenerators(o.children))
}
// Permutations implements the interface StatementGenerator.
func (o *OrGen) Permutations() *big.Int {
sum := big.NewInt(0)
for _, child := range o.children {
sum.Add(sum, child.Permutations())
}
return sum
}
// PermutationsUint64 implements the interface StatementGenerator.
func (o *OrGen) PermutationsUint64() (uint64, bool) {
sum := uint64(0)
for _, child := range o.children {
childCount, ok := child.PermutationsUint64()
if !ok || sum > (math.MaxUint64-childCount) {
return math.MaxUint64, false
}
sum += childCount
}
return sum, true
}
// VariableGen represents a variable in the synopsis. Its values are user-configurable if they cannot be deduced from
// the synopsis.
type VariableGen struct {
name string
options StatementGenerator
}
var _ StatementGenerator = (*VariableGen)(nil)
// Variable creates a new StatementGenerator representing a VariableGen.
func Variable(name string, child StatementGenerator) *VariableGen {
if child != nil {
return &VariableGen{
name: name,
options: child.Copy(),
}
} else {
return &VariableGen{
name: name,
options: nil,
}
}
}
// AddChildren implements the interface StatementGenerator.
func (v *VariableGen) AddChildren(children ...StatementGenerator) error {
children = removeNilGenerators(children)
if len(children) == 0 {
return nil
}
if len(children) > 1 {
return fmt.Errorf("attempting to give variable `%s` too many children", v.name)
}
if v.options != nil {
return fmt.Errorf("variable `%s` has already been assigned", v.name)
}
v.options = children[0].Copy()
return nil
}
// Consume implements the interface StatementGenerator.
func (v *VariableGen) Consume() bool {
if v.options != nil {
return v.options.Consume()
}
return false
}
// SetConsumeIterations implements the interface StatementGenerator.
func (v *VariableGen) SetConsumeIterations(count *big.Int) {
if v.options != nil {
v.options.SetConsumeIterations(count)
}
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (v *VariableGen) SetConsumeIterationsFast(count uint64) {
if v.options != nil {
v.options.SetConsumeIterationsFast(count)
}
}
// Copy implements the interface StatementGenerator.
func (v *VariableGen) Copy() StatementGenerator {
if v == nil {
return nil
}
return Variable(v.name, v.options)
}
// String implements the interface StatementGenerator.
func (v *VariableGen) String() string {
if v.options != nil {
return v.options.String()
} else {
return v.name
}
}
// Reset implements the interface StatementGenerator.
func (v *VariableGen) Reset() {
if v.options != nil {
v.options.Reset()
}
}
// SourceString implements the interface StatementGenerator.
func (v *VariableGen) SourceString() string {
if v.options != nil {
return fmt.Sprintf(`Variable("%s", %s)`, v.name, v.options.SourceString())
} else {
return fmt.Sprintf(`Variable("%s", nil)`, v.name)
}
}
// Permutations implements the interface StatementGenerator.
func (v *VariableGen) Permutations() *big.Int {
if v.options != nil {
return v.options.Permutations()
} else {
return BigIntOne
}
}
// PermutationsUint64 implements the interface StatementGenerator.
func (v *VariableGen) PermutationsUint64() (uint64, bool) {
if v.options != nil {
return v.options.PermutationsUint64()
} else {
return 1, true
}
}
// CollectionGen is a generator that contains multiple child generators, and will print all of its children.
type CollectionGen struct {
children []StatementGenerator
localInt *big.Int
}
var _ StatementGenerator = (*CollectionGen)(nil)
// Collection creates a new StatementGenerator representing a CollectionGen.
func Collection(children ...StatementGenerator) *CollectionGen {
return &CollectionGen{
children: copyGenerators(children),
localInt: new(big.Int),
}
}
// AddChildren implements the interface StatementGenerator.
func (c *CollectionGen) AddChildren(children ...StatementGenerator) error {
c.children = append(c.children, copyGenerators(children)...)
return nil
}
// Consume implements the interface StatementGenerator.
func (c *CollectionGen) Consume() bool {
for i := range c.children {
if c.children[i].Consume() {
return true
}
}
return false
}
// SetConsumeIterations implements the interface StatementGenerator.
func (c *CollectionGen) SetConsumeIterations(count *big.Int) {
// We handle this one as though it's a non-uniform numbering system (binary and decimal are uniform systems).
// In a traditional number system like binary, you can find each bit's value using the following:
//
// bit = number % 2; number = number / 2;
//
// Collections behave similarly to that system, where we increment the second generator after fully incrementing the
// first generator. Then we have to iterate over the first generator again before we can increment the second
// generator again. Do this until the second generator has exhausted its permutations, and then the third generator
// can increment.
//
// Going back to our binary example, we can achieve that same counting effect by replacing 2 with the permutation
// count. This lets us have our non-uniform numbering system, and allows us to efficiently find the exact number for
// each generator.
count = c.localInt.Mod(count, c.Permutations())
index := 0
for i, child := range c.children {
// The index is equal to whichever child we stop on
index = i
childPermutations := child.Permutations()
// We give the child the modulo of the count versus its permutation count, which will determine how many
// iterations it's supposed to simulate from the total.
childIterations := new(big.Int).Mod(count, childPermutations)
if childIterations.Cmp(BigIntMaxUint64) <= 0 {
child.SetConsumeIterationsFast(childIterations.Uint64())
} else {
child.SetConsumeIterations(childIterations)
}
// We divide the count by this child's permutation count to move to the next "base".
count.Div(count, childPermutations)
// If we're at zero now, then this child used up the remaining count, so we'll stop here
if count.Cmp(BigIntZero) <= 0 {
break
}
}
// We still need to reset any children that we never looped over
for index += 1; index < len(c.children); index++ {
c.children[index].Reset()
}
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (c *CollectionGen) SetConsumeIterationsFast(count uint64) {
// This is a copy of SetConsumeIterations, except rewritten to use uint64
permutations, _ := c.PermutationsUint64()
count = count % permutations
index := 0
for i, child := range c.children {
index = i
childPermutations, _ := child.PermutationsUint64()
child.SetConsumeIterationsFast(count % childPermutations)
count /= childPermutations
if count <= 0 {
break
}
}
for index += 1; index < len(c.children); index++ {
c.children[index].Reset()
}
}
// Copy implements the interface StatementGenerator.
func (c *CollectionGen) Copy() StatementGenerator {
if c == nil {
return nil
}
return Collection(c.children...)
}
// String implements the interface StatementGenerator.
func (c *CollectionGen) String() string {
var childrenStrings []string
for i := range c.children {
childString := c.children[i].String()
if len(childString) > 0 {
childrenStrings = append(childrenStrings, childString)
}
}
return strings.Join(childrenStrings, " ")
}
// Reset implements the interface StatementGenerator.
func (c *CollectionGen) Reset() {
for _, child := range c.children {
child.Reset()
}
}
// SourceString implements the interface StatementGenerator.
func (c *CollectionGen) SourceString() string {
return fmt.Sprintf(`Collection(%s)`, sourceGenerators(c.children))
}
// Permutations implements the interface StatementGenerator.
func (c *CollectionGen) Permutations() *big.Int {
total := big.NewInt(1)
for _, child := range c.children {
childPermutations := child.Permutations()
if childPermutations.Cmp(BigIntZero) != 0 {
total.Mul(total, childPermutations)
}
}
return total
}
// PermutationsUint64 implements the interface StatementGenerator.
func (c *CollectionGen) PermutationsUint64() (uint64, bool) {
total := uint64(1)
for _, child := range c.children {
childPermutations, ok := child.PermutationsUint64()
if !ok {
return math.MaxUint64, false
}
if childPermutations == 0 {
continue
}
if total > math.MaxUint64/childPermutations {
return math.MaxUint64, false
}
total *= childPermutations
}
return total, true
}
// OptionalGen is a generator that will toggle between displaying its children and not displaying its children.
type OptionalGen struct {
children *CollectionGen
display bool
localInt *big.Int
}
var _ StatementGenerator = (*OptionalGen)(nil)
// Optional creates a new StatementGenerator representing an OptionalGen.
func Optional(children ...StatementGenerator) *OptionalGen {
return &OptionalGen{
children: Collection(children...),
display: false,
localInt: new(big.Int),
}
}
// AddChildren implements the interface StatementGenerator.
func (o *OptionalGen) AddChildren(children ...StatementGenerator) error {
return o.children.AddChildren(children...)
}
// Consume implements the interface StatementGenerator.
func (o *OptionalGen) Consume() bool {
if !o.display {
o.display = true
return true
} else if o.children.Consume() {
return true
} else {
o.display = false
return false
}
}
// SetConsumeIterations implements the interface StatementGenerator.
func (o *OptionalGen) SetConsumeIterations(count *big.Int) {
// If we're given zero, then we'll just call Reset
if count.Cmp(BigIntZero) <= 0 {
o.Reset()
return
}
// The count is >= 1, so display will be true
o.display = true
count = o.localInt.Mod(count, o.Permutations())
// Setting display to true uses a single Consume, so we subtract it before passing the count to the child
count.Sub(count, BigIntOne)
// We'll pass the rest of the remaining count to the child, which will be >= 0
o.children.SetConsumeIterations(count)
}
// SetConsumeIterationsFast implements the interface StatementGenerator.
func (o *OptionalGen) SetConsumeIterationsFast(count uint64) {
// This is a copy of SetConsumeIterations, except rewritten to use uint64
if count <= 0 {
o.Reset()
return
}
o.display = true
permutations, _ := o.PermutationsUint64()
count = count % permutations
count -= 1
o.children.SetConsumeIterationsFast(count)
}
// Copy implements the interface StatementGenerator.
func (o *OptionalGen) Copy() StatementGenerator {
if o == nil {
return nil
}
return Optional(o.children.children...)
}
// String implements the interface StatementGenerator.
func (o *OptionalGen) String() string {
if o.display {
return o.children.String()
} else {
return ""
}
}
// Reset implements the interface StatementGenerator.
func (o *OptionalGen) Reset() {
o.display = false
o.children.Reset()
}
// SourceString implements the interface StatementGenerator.
func (o *OptionalGen) SourceString() string {
return fmt.Sprintf(`Optional(%s)`, sourceGenerators(o.children.children))
}
// Permutations implements the interface StatementGenerator.
func (o *OptionalGen) Permutations() *big.Int {
return new(big.Int).Add(BigIntOne, o.children.Permutations())
}
// PermutationsUint64 implements the interface StatementGenerator.
func (o *OptionalGen) PermutationsUint64() (uint64, bool) {
childCount, ok := o.children.PermutationsUint64()
if !ok || childCount == math.MaxUint64 {
return math.MaxUint64, false
}
return 1 + childCount, true
}
// ApplyVariableDefinition applies the given map of variable definitions to the statement generator. This modifies the
// statement generator, rather than returning a copy.
func ApplyVariableDefinition(gen StatementGenerator, definitions map[string]StatementGenerator) error {
if len(definitions) == 0 {
return nil
}
switch gen := gen.(type) {
case *CollectionGen:
for _, child := range gen.children {
if err := ApplyVariableDefinition(child, definitions); err != nil {
return err
}
}
case *OptionalGen:
if err := ApplyVariableDefinition(gen.children, definitions); err != nil {
return err
}
case *OrGen:
for _, child := range gen.children {
if err := ApplyVariableDefinition(child, definitions); err != nil {
return err
}
}
case *TextGen:
// Nothing to do here
case *VariableGen:
if gen.options == nil {
if definition, ok := definitions[gen.name]; ok {
if err := gen.AddChildren(definition); err != nil {
return err
}
if err := ApplyVariableDefinition(gen.options, definitions); err != nil {
return err
}
}
} else {
if err := ApplyVariableDefinition(gen.options, definitions); err != nil {
return err
}
}
case nil:
return nil
default:
return fmt.Errorf("unknown generator encountered: %T", gen)
}
return nil
}
// UnsetVariables returns the name of all variables that do not have a definition. Sorted in ascending order.
func UnsetVariables(gen StatementGenerator) ([]string, error) {
varNames := make(map[string]struct{})
switch gen := gen.(type) {
case *CollectionGen:
for _, child := range gen.children {
children, err := UnsetVariables(child)
if err != nil {
return nil, err
}
for _, childName := range children {
varNames[childName] = struct{}{}
}
}
case *OptionalGen:
return UnsetVariables(gen.children)
case *OrGen:
return UnsetVariables(Collection(gen.children...))
case *TextGen:
// Nothing to do here
case *VariableGen:
if gen.options == nil {
return []string{gen.name}, nil
} else {
return UnsetVariables(gen.options)
}
default:
return nil, fmt.Errorf("unknown generator encountered: %T", gen)
}
var varNamesSlice []string
for varName := range varNames {
varNamesSlice = append(varNamesSlice, varName)
}
sort.Strings(varNamesSlice)
return varNamesSlice, nil
}
// copyGenerators returns a full copy of the given slice of generators. Each generator will be in its original state.
func copyGenerators(gens []StatementGenerator) []StatementGenerator {
gens = removeNilGenerators(gens)
if len(gens) == 0 {
return nil
}
newGens := make([]StatementGenerator, len(gens))
for i, gen := range gens {
newGens[i] = gen.Copy()
}
return newGens
}
// sourceGenerators returns a comma-separated SourceString from the given generator slice.
func sourceGenerators(gens []StatementGenerator) string {
gens = removeNilGenerators(gens)
if len(gens) == 0 {
return ""
}
sourceStrs := make([]string, len(gens))
for i, gen := range gens {
sourceStrs[i] = gen.SourceString()
}
return strings.Join(sourceStrs, ", ")
}
// removeNilGenerators returns a new slice of generators with all nils removed.
func removeNilGenerators(gens []StatementGenerator) []StatementGenerator {
newGens := make([]StatementGenerator, 0, len(gens))
for i := range gens {
if gens[i] != nil {
newGens = append(newGens, gens[i])
}
}
if len(newGens) == 0 {
return nil
}
return newGens
}