github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/internal/rsg/rsg.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package rsg
    12  
    13  import (
    14  	"fmt"
    15  	"math"
    16  	"math/rand"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/internal/rsg/yacc"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    23  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    24  )
    25  
    26  // RSG is a random syntax generator.
    27  type RSG struct {
    28  	Rnd *rand.Rand
    29  
    30  	lock  syncutil.Mutex
    31  	seen  map[string]bool
    32  	prods map[string][]*yacc.ExpressionNode
    33  }
    34  
    35  // NewRSG creates a random syntax generator from the given random seed and
    36  // yacc file.
    37  func NewRSG(seed int64, y string, allowDuplicates bool) (*RSG, error) {
    38  	tree, err := yacc.Parse("sql", y)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	rsg := RSG{
    43  		Rnd:   rand.New(&lockedSource{src: rand.NewSource(seed).(rand.Source64)}),
    44  		prods: make(map[string][]*yacc.ExpressionNode),
    45  	}
    46  	if !allowDuplicates {
    47  		rsg.seen = make(map[string]bool)
    48  	}
    49  	for _, prod := range tree.Productions {
    50  		rsg.prods[prod.Name] = prod.Expressions
    51  	}
    52  	return &rsg, nil
    53  }
    54  
    55  // Generate generates a unique random syntax from the root node. At most depth
    56  // levels of token expansion are performed. An empty string is returned on
    57  // error or if depth is exceeded. Generate is safe to call from mulitipule
    58  // goroutines. If Generate is called more times than it can generate unique
    59  // output, it will block forever.
    60  func (r *RSG) Generate(root string, depth int) string {
    61  	for i := 0; i < 100000; i++ {
    62  		s := strings.Join(r.generate(root, depth), " ")
    63  		if r.seen != nil {
    64  			r.lock.Lock()
    65  			if !r.seen[s] {
    66  				r.seen[s] = true
    67  			} else {
    68  				s = ""
    69  			}
    70  			r.lock.Unlock()
    71  		}
    72  		if s != "" {
    73  			s = strings.Replace(s, "_LA", "", -1)
    74  			s = strings.Replace(s, " AS OF SYSTEM TIME \"string\"", "", -1)
    75  			return s
    76  		}
    77  	}
    78  	panic("couldn't find unique string")
    79  }
    80  
    81  func (r *RSG) generate(root string, depth int) []string {
    82  	// Initialize to an empty slice instead of nil because nil is the signal
    83  	// that the depth has been exceeded.
    84  	ret := make([]string, 0)
    85  	prods := r.prods[root]
    86  	if len(prods) == 0 {
    87  		return []string{root}
    88  	}
    89  	prod := prods[r.Intn(len(prods))]
    90  	for _, item := range prod.Items {
    91  		switch item.Typ {
    92  		case yacc.TypLiteral:
    93  			v := item.Value[1 : len(item.Value)-1]
    94  			ret = append(ret, v)
    95  		case yacc.TypToken:
    96  			var v []string
    97  			switch item.Value {
    98  			case "IDENT":
    99  				v = []string{"ident"}
   100  			case "c_expr":
   101  				v = r.generate(item.Value, 30)
   102  			case "SCONST":
   103  				v = []string{`'string'`}
   104  			case "ICONST":
   105  				v = []string{fmt.Sprint(r.Intn(1000) - 500)}
   106  			case "FCONST":
   107  				v = []string{fmt.Sprint(r.Float64())}
   108  			case "BCONST":
   109  				v = []string{`b'bytes'`}
   110  			case "BITCONST":
   111  				v = []string{`B'10010'`}
   112  			case "substr_from":
   113  				v = []string{"FROM", `'string'`}
   114  			case "substr_for":
   115  				v = []string{"FOR", `'string'`}
   116  			case "overlay_placing":
   117  				v = []string{"PLACING", `'string'`}
   118  			default:
   119  				if depth == 0 {
   120  					return nil
   121  				}
   122  				v = r.generate(item.Value, depth-1)
   123  			}
   124  			if v == nil {
   125  				return nil
   126  			}
   127  			ret = append(ret, v...)
   128  		default:
   129  			panic("unknown item type")
   130  		}
   131  	}
   132  	return ret
   133  }
   134  
   135  // Intn returns a random int.
   136  func (r *RSG) Intn(n int) int {
   137  	return r.Rnd.Intn(n)
   138  }
   139  
   140  // Int63 returns a random int64.
   141  func (r *RSG) Int63() int64 {
   142  	return r.Rnd.Int63()
   143  }
   144  
   145  // Float64 returns a random float. It is sometimes +/-Inf, NaN, and attempts to
   146  // be distributed among very small, large, and normal scale numbers.
   147  func (r *RSG) Float64() float64 {
   148  	v := r.Rnd.Float64()*2 - 1
   149  	switch r.Rnd.Intn(10) {
   150  	case 0:
   151  		v = 0
   152  	case 1:
   153  		v = math.Inf(1)
   154  	case 2:
   155  		v = math.Inf(-1)
   156  	case 3:
   157  		v = math.NaN()
   158  	case 4, 5:
   159  		i := r.Rnd.Intn(50)
   160  		v *= math.Pow10(i)
   161  	case 6, 7:
   162  		i := r.Rnd.Intn(50)
   163  		v *= math.Pow10(-i)
   164  	}
   165  	return v
   166  }
   167  
   168  // GenerateRandomArg generates a random, valid, SQL function argument of
   169  // the specified type.
   170  func (r *RSG) GenerateRandomArg(typ *types.T) string {
   171  	switch r.Intn(20) {
   172  	case 0:
   173  		return "NULL"
   174  	case 1:
   175  		return fmt.Sprintf("NULL::%s", typ)
   176  	case 2:
   177  		return fmt.Sprintf("(SELECT NULL)::%s", typ)
   178  	}
   179  
   180  	r.lock.Lock()
   181  	datum := sqlbase.RandDatumWithNullChance(r.Rnd, typ, 0)
   182  	r.lock.Unlock()
   183  
   184  	return tree.Serialize(datum)
   185  }
   186  
   187  // lockedSource is a thread safe math/rand.Source. See math/rand/rand.go.
   188  type lockedSource struct {
   189  	lk  syncutil.Mutex
   190  	src rand.Source64
   191  }
   192  
   193  func (r *lockedSource) Int63() (n int64) {
   194  	r.lk.Lock()
   195  	n = r.src.Int63()
   196  	r.lk.Unlock()
   197  	return
   198  }
   199  
   200  func (r *lockedSource) Uint64() (n uint64) {
   201  	r.lk.Lock()
   202  	n = r.src.Uint64()
   203  	r.lk.Unlock()
   204  	return
   205  }
   206  
   207  func (r *lockedSource) Seed(seed int64) {
   208  	r.lk.Lock()
   209  	r.src.Seed(seed)
   210  	r.lk.Unlock()
   211  }