github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/testutils/sqlutils/table_gen.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 sqlutils
    12  
    13  import (
    14  	"bytes"
    15  	gosql "database/sql"
    16  	"fmt"
    17  	"io"
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    22  )
    23  
    24  const rowsPerInsert = 100
    25  
    26  // TestDB is the name of the database created for test tables.
    27  const TestDB = "test"
    28  
    29  // GenRowFn is a function that takes a (1-based) row index and returns a row of
    30  // Datums that will be converted to strings to form part of an INSERT statement.
    31  type GenRowFn func(row int) []tree.Datum
    32  
    33  // genValues writes a string of generated values "(a,b,c),(d,e,f)...".
    34  func genValues(w io.Writer, firstRow, lastRow int, fn GenRowFn, shouldPrint bool) {
    35  	for rowIdx := firstRow; rowIdx <= lastRow; rowIdx++ {
    36  		if rowIdx > firstRow {
    37  			fmt.Fprint(w, ",")
    38  		}
    39  		row := fn(rowIdx)
    40  		if shouldPrint {
    41  			for _, v := range row {
    42  				fmt.Printf("%s\t\t", v)
    43  			}
    44  			fmt.Printf("\n")
    45  		}
    46  		fmt.Fprintf(w, "(%s", tree.Serialize(row[0]))
    47  		for _, v := range row[1:] {
    48  			fmt.Fprintf(w, ",%s", tree.Serialize(v))
    49  		}
    50  		fmt.Fprint(w, ")")
    51  	}
    52  }
    53  
    54  // CreateTable creates a table in the "test" database with the given number of
    55  // rows and using the given row generation function.
    56  func CreateTable(
    57  	tb testing.TB, sqlDB *gosql.DB, tableName, schema string, numRows int, fn GenRowFn,
    58  ) {
    59  	CreateTableInterleaved(tb, sqlDB, tableName, schema, "" /*interleaveSchema*/, numRows, fn)
    60  }
    61  
    62  // CreateTableDebug is identical to debug, but allows for the added option of
    63  // printing the table and its contents upon creation.
    64  func CreateTableDebug(
    65  	tb testing.TB,
    66  	sqlDB *gosql.DB,
    67  	tableName, schema string,
    68  	numRows int,
    69  	fn GenRowFn,
    70  	shouldPrint bool,
    71  ) {
    72  	CreateTableInterleavedDebug(tb, sqlDB, tableName, schema, "" /*interleaveSchema*/, numRows, fn, shouldPrint)
    73  }
    74  
    75  // CreateTableInterleaved is identical to CreateTable with the added option
    76  // of specifying an interleave schema for interleaving the table.
    77  func CreateTableInterleaved(
    78  	tb testing.TB,
    79  	sqlDB *gosql.DB,
    80  	tableName, schema, interleaveSchema string,
    81  	numRows int,
    82  	fn GenRowFn,
    83  ) {
    84  	CreateTableInterleavedDebug(tb, sqlDB, tableName, schema, interleaveSchema, numRows, fn, false /* print */)
    85  }
    86  
    87  // CreateTableInterleavedDebug is identical to CreateTableInterleaved with the
    88  // option of printing the table being created.
    89  func CreateTableInterleavedDebug(
    90  	tb testing.TB,
    91  	sqlDB *gosql.DB,
    92  	tableName, schema, interleaveSchema string,
    93  	numRows int,
    94  	fn GenRowFn,
    95  	shouldPrint bool,
    96  ) {
    97  	if interleaveSchema != "" {
    98  		interleaveSchema = fmt.Sprintf(`INTERLEAVE IN PARENT %s.%s`, TestDB, interleaveSchema)
    99  	}
   100  
   101  	r := MakeSQLRunner(sqlDB)
   102  	stmt := fmt.Sprintf(`CREATE DATABASE IF NOT EXISTS %s;`, TestDB)
   103  	stmt += fmt.Sprintf(`CREATE TABLE %s.%s (%s) %s;`, TestDB, tableName, schema, interleaveSchema)
   104  	r.Exec(tb, stmt)
   105  	if shouldPrint {
   106  		fmt.Printf("Creating table: %s\n%s\n", tableName, schema)
   107  	}
   108  	for i := 1; i <= numRows; {
   109  		var buf bytes.Buffer
   110  		fmt.Fprintf(&buf, `INSERT INTO %s.%s VALUES `, TestDB, tableName)
   111  		batchEnd := i + rowsPerInsert
   112  		if batchEnd > numRows {
   113  			batchEnd = numRows
   114  		}
   115  		genValues(&buf, i, batchEnd, fn, shouldPrint)
   116  
   117  		r.Exec(tb, buf.String())
   118  		i = batchEnd + 1
   119  	}
   120  }
   121  
   122  // CreateTestInterleavedHierarchy generates the following interleaved hierarchy
   123  // for testing:
   124  //   <table>		  <primary index/interleave prefix>   <nrows>
   125  //   parent1		  (pid1)			      100
   126  //     child1		  (pid1, cid1, cid2)		      250
   127  //       grandchild1	  (pid1, cid1, cid2, gcid1)	      1000
   128  //     child2		  (pid1, cid3, cid4)		      50
   129  //   parent2		  (pid1)			      20
   130  func CreateTestInterleavedHierarchy(t *testing.T, sqlDB *gosql.DB) {
   131  	vMod := 42
   132  	CreateTable(t, sqlDB, "parent1",
   133  		"pid1 INT PRIMARY KEY, v INT",
   134  		100,
   135  		ToRowFn(RowIdxFn, RowModuloFn(vMod)),
   136  	)
   137  
   138  	CreateTableInterleaved(t, sqlDB, "child1",
   139  		"pid1 INT, cid1 INT, cid2 INT, v INT, PRIMARY KEY (pid1, cid1, cid2)",
   140  		"parent1 (pid1)",
   141  		250,
   142  		ToRowFn(
   143  			RowModuloShiftedFn(100),
   144  			RowIdxFn,
   145  			RowIdxFn,
   146  			RowModuloFn(vMod),
   147  		),
   148  	)
   149  
   150  	CreateTableInterleaved(t, sqlDB, "grandchild1",
   151  		"pid1 INT, cid1 INT, cid2 INT, gcid1 INT, v INT, PRIMARY KEY (pid1, cid1, cid2, gcid1)",
   152  		"child1 (pid1, cid1, cid2)",
   153  		1000,
   154  		ToRowFn(
   155  			RowModuloShiftedFn(250, 100),
   156  			RowModuloShiftedFn(250),
   157  			RowModuloShiftedFn(250),
   158  			RowIdxFn,
   159  			RowModuloFn(vMod),
   160  		),
   161  	)
   162  
   163  	CreateTableInterleaved(t, sqlDB, "child2",
   164  		"pid1 INT, cid3 INT, cid4 INT, v INT, PRIMARY KEY (pid1, cid3, cid4)",
   165  		"parent1 (pid1)",
   166  		50,
   167  		ToRowFn(
   168  			RowModuloShiftedFn(100),
   169  			RowIdxFn,
   170  			RowIdxFn,
   171  			RowModuloFn(vMod),
   172  		),
   173  	)
   174  
   175  	CreateTable(t, sqlDB, "parent2",
   176  		"pid1 INT PRIMARY KEY, v INT",
   177  		20,
   178  		ToRowFn(RowIdxFn, RowModuloFn(vMod)),
   179  	)
   180  }
   181  
   182  // GenValueFn is a function that takes a (1-based) row index and returns a Datum
   183  // which will be converted to a string to form part of an INSERT statement.
   184  type GenValueFn func(row int) tree.Datum
   185  
   186  // RowIdxFn is a GenValueFn that returns the row number as a DInt
   187  func RowIdxFn(row int) tree.Datum {
   188  	return tree.NewDInt(tree.DInt(row))
   189  }
   190  
   191  // RowModuloFn creates a GenValueFn that returns the row number modulo a given
   192  // value as a DInt
   193  func RowModuloFn(modulo int) GenValueFn {
   194  	return func(row int) tree.Datum {
   195  		return tree.NewDInt(tree.DInt(row % modulo))
   196  	}
   197  }
   198  
   199  // RowModuloShiftedFn creates a GenValueFn that uses the following recursive
   200  // function definition F(row, modulo), where modulo is []int
   201  //    F(row, [])      = row
   202  //    F(row, modulo)  = F((row - 1) % modulo[0] + 1, modulo[1:])
   203  // and returns the result as a DInt.
   204  func RowModuloShiftedFn(modulo ...int) GenValueFn {
   205  	return func(row int) tree.Datum {
   206  		return tree.NewDInt(tree.DInt(moduloShiftedRecursive(row, modulo)))
   207  	}
   208  }
   209  
   210  func moduloShiftedRecursive(row int, modulo []int) int {
   211  	if len(modulo) == 0 {
   212  		return row
   213  	}
   214  	return moduloShiftedRecursive(((row-1)%modulo[0])+1, modulo[1:])
   215  }
   216  
   217  // IntToEnglish returns an English (pilot style) string for the given integer,
   218  // for example:
   219  //   IntToEnglish(135) = "one-three-five"
   220  func IntToEnglish(val int) string {
   221  	if val < 0 {
   222  		panic(val)
   223  	}
   224  	d := []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}
   225  
   226  	var digits []string
   227  	digits = append(digits, d[val%10])
   228  	for val > 9 {
   229  		val /= 10
   230  		digits = append(digits, d[val%10])
   231  	}
   232  	for i, j := 0, len(digits)-1; i < j; i, j = i+1, j-1 {
   233  		digits[i], digits[j] = digits[j], digits[i]
   234  	}
   235  	return strings.Join(digits, "-")
   236  }
   237  
   238  // RowEnglishFn is a GenValueFn which returns an English representation of the
   239  // row number, as a DString
   240  func RowEnglishFn(row int) tree.Datum {
   241  	return tree.NewDString(IntToEnglish(row))
   242  }
   243  
   244  // ToRowFn creates a GenRowFn that returns rows of values generated by the given
   245  // GenValueFns (one per column).
   246  func ToRowFn(fn ...GenValueFn) GenRowFn {
   247  	return func(row int) []tree.Datum {
   248  		res := make([]tree.Datum, 0, len(fn))
   249  		for _, f := range fn {
   250  			res = append(res, f(row))
   251  		}
   252  		return res
   253  	}
   254  }