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  }