github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/importccl/testutils_test.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package importccl 10 11 import ( 12 "context" 13 "fmt" 14 "io" 15 "math" 16 "strconv" 17 "testing" 18 "time" 19 20 "github.com/cockroachdb/cockroach/pkg/keys" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 23 "github.com/cockroachdb/cockroach/pkg/sql" 24 "github.com/cockroachdb/cockroach/pkg/sql/parser" 25 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 26 "github.com/cockroachdb/cockroach/pkg/sql/sessiondata" 27 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 28 "github.com/cockroachdb/cockroach/pkg/storage/cloud" 29 "github.com/cockroachdb/cockroach/pkg/util/hlc" 30 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 31 "github.com/cockroachdb/errors" 32 ) 33 34 func descForTable( 35 t *testing.T, create string, parent, id sqlbase.ID, fks fkHandler, 36 ) *sqlbase.TableDescriptor { 37 t.Helper() 38 parsed, err := parser.Parse(create) 39 if err != nil { 40 t.Fatalf("could not parse %q: %v", create, err) 41 } 42 nanos := testEvalCtx.StmtTimestamp.UnixNano() 43 44 settings := testEvalCtx.Settings 45 46 var stmt *tree.CreateTable 47 48 if len(parsed) == 2 { 49 stmt = parsed[1].AST.(*tree.CreateTable) 50 name := parsed[0].AST.(*tree.CreateSequence).Name.String() 51 52 ts := hlc.Timestamp{WallTime: nanos} 53 priv := sqlbase.NewDefaultPrivilegeDescriptor() 54 desc, err := sql.MakeSequenceTableDesc( 55 name, 56 tree.SequenceOptions{}, 57 parent, 58 keys.PublicSchemaID, 59 id-1, 60 ts, 61 priv, 62 false, /* temporary */ 63 nil, /* params */ 64 ) 65 if err != nil { 66 t.Fatal(err) 67 } 68 fks.resolver[name] = &desc 69 } else { 70 stmt = parsed[0].AST.(*tree.CreateTable) 71 } 72 table, err := MakeSimpleTableDescriptor(context.Background(), settings, stmt, parent, id, fks, nanos) 73 if err != nil { 74 t.Fatalf("could not interpret %q: %v", create, err) 75 } 76 if err := fixDescriptorFKState(table.TableDesc()); err != nil { 77 t.Fatal(err) 78 } 79 return table.TableDesc() 80 } 81 82 var testEvalCtx = &tree.EvalContext{ 83 SessionData: &sessiondata.SessionData{ 84 DataConversion: sessiondata.DataConversionConfig{Location: time.UTC}, 85 }, 86 StmtTimestamp: timeutil.Unix(100000000, 0), 87 Settings: cluster.MakeTestingClusterSettings(), 88 Codec: keys.SystemSQLCodec, 89 } 90 91 // Value generator represents a value of some data at specified row/col. 92 type csvRow = int 93 type csvCol = int 94 95 type valueGenerator interface { 96 getValue(r csvRow, c csvCol) string 97 } 98 type intGenerator struct{} // Returns row value 99 type strGenerator struct{} // Returns generated string 100 101 var _ valueGenerator = &intGenerator{} 102 var _ valueGenerator = &strGenerator{} 103 104 func (g *intGenerator) getValue(r csvRow, _ csvCol) string { 105 return strconv.Itoa(r) 106 } 107 108 func (g *strGenerator) getValue(r csvRow, c csvCol) string { 109 return fmt.Sprintf("data<r=%d;c=%d>", r, c) 110 } 111 112 // A breakpoint handler runs some breakpoint specific logic. A handler 113 // returns a boolean indicating if this breakpoint should remain active. 114 // An error result will cause csv generation to stop and the csv server 115 // to return that error. 116 type csvBpHandler = func() (bool, error) 117 type csvBreakpoint struct { 118 row csvRow 119 enabled bool 120 handler csvBpHandler 121 } 122 123 // csvGenerator generates csv data. 124 type csvGenerator struct { 125 startRow int 126 numRows int 127 columns []valueGenerator 128 breakpoints map[int]*csvBreakpoint 129 130 data []string 131 size int 132 rowPos int // csvRow number we're emitting 133 rowOffset int // Offset within the current row 134 } 135 136 const maxBreakpointPos = math.MaxInt32 137 138 var _ io.ReadCloser = &csvGenerator{} 139 140 func (csv *csvGenerator) Open() (io.ReadCloser, error) { 141 csv.rowPos = 0 142 csv.rowOffset = 0 143 csv.maybeInitData() 144 return csv, nil 145 } 146 147 // Note: we read one row at a time because reading as much as the 148 // buffer space allows might cause problems for breakpoints (e.g. 149 // a breakpoint may block until job progress is updated, but that 150 // update would never happen because we're still reading the data). 151 func (csv *csvGenerator) Read(p []byte) (n int, err error) { 152 n = 0 153 if n < cap(p) && csv.rowPos < len(csv.data) { 154 if csv.rowOffset == 0 { 155 if err = csv.handleBreakpoint(csv.rowPos); err != nil { 156 return 157 } 158 } 159 rowBytes := copy(p[n:], csv.data[csv.rowPos][csv.rowOffset:]) 160 csv.rowOffset += rowBytes 161 n += rowBytes 162 163 if rowBytes == len(csv.data[csv.rowPos]) { 164 csv.rowOffset = 0 165 csv.rowPos++ 166 } 167 } 168 169 if n == 0 { 170 _ = csv.Close() 171 err = io.EOF 172 } 173 174 return 175 } 176 177 func (csv *csvGenerator) maybeInitData() { 178 if csv.data != nil { 179 return 180 } 181 182 csv.data = make([]string, csv.numRows) 183 csv.size = 0 184 for i := 0; i < csv.numRows; i++ { 185 csv.data[i] = "" 186 for colIdx, gen := range csv.columns { 187 if len(csv.data[i]) > 0 { 188 csv.data[i] += "," 189 } 190 csv.data[i] += gen.getValue(i+csv.startRow, colIdx) 191 } 192 csv.data[i] += "\n" 193 csv.size += len(csv.data[i]) 194 } 195 } 196 197 func (csv *csvGenerator) handleBreakpoint(idx int) (err error) { 198 if bp, ok := csv.breakpoints[idx]; ok && bp.enabled { 199 bp.enabled, err = bp.handler() 200 } 201 return 202 } 203 204 func (csv *csvGenerator) Close() error { 205 // Execute breakpoint at the end of the stream. 206 _ = csv.handleBreakpoint(maxBreakpointPos) 207 csv.data = nil 208 return nil 209 } 210 211 // Add a breakpoint to the generator. 212 func (csv *csvGenerator) addBreakpoint(r csvRow, handler csvBpHandler) *csvBreakpoint { 213 csv.breakpoints[r] = &csvBreakpoint{ 214 row: r, 215 enabled: true, 216 handler: handler, 217 } 218 return csv.breakpoints[r] 219 } 220 221 // Creates a new instance of csvGenerator, generating 'numRows', starting form 222 // the specified 'startRow'. The 'columns' list specifies data generators for 223 // each column. The optional 'breakpoints' specifies the list of csv breakpoints 224 // which allow caller to execute some code while generating csv data. 225 func newCsvGenerator(startRow int, numRows int, columns ...valueGenerator) *csvGenerator { 226 return &csvGenerator{ 227 startRow: startRow, 228 numRows: numRows, 229 columns: columns, 230 breakpoints: make(map[int]*csvBreakpoint), 231 } 232 } 233 234 // generatorExternalStorage is an external storage implementation 235 // that returns its data from the underlying generator. 236 type generatorExternalStorage struct { 237 conf roachpb.ExternalStorage 238 gen *csvGenerator 239 } 240 241 var _ cloud.ExternalStorage = &generatorExternalStorage{} 242 243 func (es *generatorExternalStorage) Conf() roachpb.ExternalStorage { 244 return es.conf 245 } 246 247 func (es *generatorExternalStorage) ReadFile( 248 ctx context.Context, basename string, 249 ) (io.ReadCloser, error) { 250 return es.gen.Open() 251 } 252 253 func (es *generatorExternalStorage) Close() error { 254 return nil 255 } 256 257 func (es *generatorExternalStorage) Size(ctx context.Context, basename string) (int64, error) { 258 es.gen.maybeInitData() 259 return int64(es.gen.size), nil 260 } 261 262 func (es *generatorExternalStorage) WriteFile( 263 ctx context.Context, basename string, content io.ReadSeeker, 264 ) error { 265 return errors.New("unsupported") 266 } 267 268 func (es *generatorExternalStorage) ListFiles(ctx context.Context, _ string) ([]string, error) { 269 return nil, errors.New("unsupported") 270 } 271 272 func (es *generatorExternalStorage) Delete(ctx context.Context, basename string) error { 273 return errors.New("unsupported") 274 } 275 276 // generatedStorage is a factory (cloud.ExternalStorageFactory) 277 // that returns the underlying csvGenerators The file name of the 278 // generated file doesn't matter: the first time we attempt to get a 279 // generator for a file, we return the next generator on the list. 280 type generatedStorage struct { 281 generators []*csvGenerator 282 nextID int 283 nameIDMap map[string]int 284 } 285 286 func newGeneratedStorage(gens ...*csvGenerator) *generatedStorage { 287 return &generatedStorage{ 288 generators: gens, 289 nextID: 0, 290 nameIDMap: make(map[string]int), 291 } 292 } 293 294 // Returns the list of file names (URIs) suitable for this factory. 295 func (ses *generatedStorage) getGeneratorURIs() []interface{} { 296 // Names do not matter; all that matters is the number of generators. 297 res := make([]interface{}, len(ses.generators)) 298 for i := range ses.generators { 299 res[i] = fmt.Sprintf("http://host.does.not.matter/filename_is_meaningless%d", i) 300 } 301 return res 302 }