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 }