github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/select_into.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package interlock
    15  
    16  import (
    17  	"bufio"
    18  	"bytes"
    19  	"context"
    20  	"math"
    21  	"os"
    22  	"strconv"
    23  
    24  	"github.com/whtcorpsinc/errors"
    25  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    26  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    27  	"github.com/whtcorpsinc/milevadb/types"
    28  	"github.com/whtcorpsinc/milevadb/soliton/chunk"
    29  )
    30  
    31  // SelectIntoInterDirc represents a SelectInto interlock.
    32  type SelectIntoInterDirc struct {
    33  	baseInterlockingDirectorate
    34  	intoOpt *ast.SelectIntoOption
    35  
    36  	lineBuf   []byte
    37  	realBuf   []byte
    38  	fieldBuf  []byte
    39  	escapeBuf []byte
    40  	enclosed  bool
    41  	writer    *bufio.Writer
    42  	dstFile   *os.File
    43  	chk       *chunk.Chunk
    44  	started   bool
    45  }
    46  
    47  // Open implements the InterlockingDirectorate Open interface.
    48  func (s *SelectIntoInterDirc) Open(ctx context.Context) error {
    49  	// only 'select ... into outfile' is supported now
    50  	if s.intoOpt.Tp != ast.SelectIntoOutfile {
    51  		return errors.New("unsupported SelectInto type")
    52  	}
    53  
    54  	f, err := os.OpenFile(s.intoOpt.FileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
    55  	if err != nil {
    56  		return errors.Trace(err)
    57  	}
    58  	s.started = true
    59  	s.dstFile = f
    60  	s.writer = bufio.NewWriter(s.dstFile)
    61  	s.chk = newFirstChunk(s.children[0])
    62  	s.lineBuf = make([]byte, 0, 1024)
    63  	s.fieldBuf = make([]byte, 0, 64)
    64  	s.escapeBuf = make([]byte, 0, 64)
    65  	return s.baseInterlockingDirectorate.Open(ctx)
    66  }
    67  
    68  // Next implements the InterlockingDirectorate Next interface.
    69  func (s *SelectIntoInterDirc) Next(ctx context.Context, req *chunk.Chunk) error {
    70  	for {
    71  		if err := Next(ctx, s.children[0], s.chk); err != nil {
    72  			return err
    73  		}
    74  		if s.chk.NumEvents() == 0 {
    75  			break
    76  		}
    77  		if err := s.dumpToOutfile(); err != nil {
    78  			return err
    79  		}
    80  	}
    81  	return nil
    82  }
    83  
    84  func (s *SelectIntoInterDirc) considerEncloseOpt(et types.EvalType) bool {
    85  	return et == types.ETString || et == types.ETDuration ||
    86  		et == types.ETTimestamp || et == types.ETDatetime ||
    87  		et == types.ETJson
    88  }
    89  
    90  func (s *SelectIntoInterDirc) escapeField(f []byte) []byte {
    91  	if s.intoOpt.FieldsInfo.Escaped == 0 {
    92  		return f
    93  	}
    94  	s.escapeBuf = s.escapeBuf[:0]
    95  	for _, b := range f {
    96  		escape := false
    97  		switch {
    98  		case b == 0:
    99  			// we always escape 0
   100  			escape = true
   101  			b = '0'
   102  		case b == s.intoOpt.FieldsInfo.Escaped || b == s.intoOpt.FieldsInfo.Enclosed:
   103  			escape = true
   104  		case !s.enclosed && len(s.intoOpt.FieldsInfo.Terminated) > 0 && b == s.intoOpt.FieldsInfo.Terminated[0]:
   105  			// if field is enclosed, we only escape line terminator, otherwise both field and line terminator will be escaped
   106  			escape = true
   107  		case len(s.intoOpt.LinesInfo.Terminated) > 0 && b == s.intoOpt.LinesInfo.Terminated[0]:
   108  			// we always escape line terminator
   109  			escape = true
   110  		}
   111  		if escape {
   112  			s.escapeBuf = append(s.escapeBuf, s.intoOpt.FieldsInfo.Escaped)
   113  		}
   114  		s.escapeBuf = append(s.escapeBuf, b)
   115  	}
   116  	return s.escapeBuf
   117  }
   118  
   119  func (s *SelectIntoInterDirc) dumpToOutfile() error {
   120  	lineTerm := "\n"
   121  	if s.intoOpt.LinesInfo.Terminated != "" {
   122  		lineTerm = s.intoOpt.LinesInfo.Terminated
   123  	}
   124  	fieldTerm := "\t"
   125  	if s.intoOpt.FieldsInfo.Terminated != "" {
   126  		fieldTerm = s.intoOpt.FieldsInfo.Terminated
   127  	}
   128  	encloseFlag := false
   129  	var encloseByte byte
   130  	encloseOpt := false
   131  	if s.intoOpt.FieldsInfo.Enclosed != byte(0) {
   132  		encloseByte = s.intoOpt.FieldsInfo.Enclosed
   133  		encloseFlag = true
   134  		encloseOpt = s.intoOpt.FieldsInfo.OptEnclosed
   135  	}
   136  	nullTerm := []byte("\\N")
   137  	if s.intoOpt.FieldsInfo.Escaped != byte(0) {
   138  		nullTerm[0] = s.intoOpt.FieldsInfo.Escaped
   139  	} else {
   140  		nullTerm = []byte("NULL")
   141  	}
   142  
   143  	defcaus := s.children[0].Schema().DeferredCausets
   144  	for i := 0; i < s.chk.NumEvents(); i++ {
   145  		event := s.chk.GetEvent(i)
   146  		s.lineBuf = s.lineBuf[:0]
   147  		for j, defCaus := range defcaus {
   148  			if j != 0 {
   149  				s.lineBuf = append(s.lineBuf, fieldTerm...)
   150  			}
   151  			if event.IsNull(j) {
   152  				s.lineBuf = append(s.lineBuf, nullTerm...)
   153  				continue
   154  			}
   155  			et := defCaus.GetType().EvalType()
   156  			if (encloseFlag && !encloseOpt) ||
   157  				(encloseFlag && encloseOpt && s.considerEncloseOpt(et)) {
   158  				s.lineBuf = append(s.lineBuf, encloseByte)
   159  				s.enclosed = true
   160  			} else {
   161  				s.enclosed = false
   162  			}
   163  			s.fieldBuf = s.fieldBuf[:0]
   164  			switch defCaus.GetType().Tp {
   165  			case allegrosql.TypeTiny, allegrosql.TypeShort, allegrosql.TypeInt24, allegrosql.TypeLong:
   166  				s.fieldBuf = strconv.AppendInt(s.fieldBuf, event.GetInt64(j), 10)
   167  			case allegrosql.TypeLonglong:
   168  				if allegrosql.HasUnsignedFlag(defCaus.GetType().Flag) {
   169  					s.fieldBuf = strconv.AppendUint(s.fieldBuf, event.GetUint64(j), 10)
   170  				} else {
   171  					s.fieldBuf = strconv.AppendInt(s.fieldBuf, event.GetInt64(j), 10)
   172  				}
   173  			case allegrosql.TypeFloat, allegrosql.TypeDouble:
   174  				s.realBuf, s.fieldBuf = DumpRealOutfile(s.realBuf, s.fieldBuf, event.GetFloat64(j), defCaus.RetType)
   175  			case allegrosql.TypeNewDecimal:
   176  				s.fieldBuf = append(s.fieldBuf, event.GetMyDecimal(j).String()...)
   177  			case allegrosql.TypeString, allegrosql.TypeVarString, allegrosql.TypeVarchar,
   178  				allegrosql.TypeTinyBlob, allegrosql.TypeMediumBlob, allegrosql.TypeLongBlob, allegrosql.TypeBlob:
   179  				s.fieldBuf = append(s.fieldBuf, event.GetBytes(j)...)
   180  			case allegrosql.TypeBit:
   181  				// bit value won't be escaped anyway (verified on MyALLEGROSQL, test case added)
   182  				s.lineBuf = append(s.lineBuf, event.GetBytes(j)...)
   183  			case allegrosql.TypeDate, allegrosql.TypeDatetime, allegrosql.TypeTimestamp:
   184  				s.fieldBuf = append(s.fieldBuf, event.GetTime(j).String()...)
   185  			case allegrosql.TypeDuration:
   186  				s.fieldBuf = append(s.fieldBuf, event.GetDuration(j, defCaus.GetType().Decimal).String()...)
   187  			case allegrosql.TypeEnum:
   188  				s.fieldBuf = append(s.fieldBuf, event.GetEnum(j).String()...)
   189  			case allegrosql.TypeSet:
   190  				s.fieldBuf = append(s.fieldBuf, event.GetSet(j).String()...)
   191  			case allegrosql.TypeJSON:
   192  				s.fieldBuf = append(s.fieldBuf, event.GetJSON(j).String()...)
   193  			}
   194  			s.lineBuf = append(s.lineBuf, s.escapeField(s.fieldBuf)...)
   195  			if (encloseFlag && !encloseOpt) ||
   196  				(encloseFlag && encloseOpt && s.considerEncloseOpt(et)) {
   197  				s.lineBuf = append(s.lineBuf, encloseByte)
   198  			}
   199  		}
   200  		s.lineBuf = append(s.lineBuf, lineTerm...)
   201  		if _, err := s.writer.Write(s.lineBuf); err != nil {
   202  			return errors.Trace(err)
   203  		}
   204  	}
   205  	return nil
   206  }
   207  
   208  // Close implements the InterlockingDirectorate Close interface.
   209  func (s *SelectIntoInterDirc) Close() error {
   210  	if !s.started {
   211  		return nil
   212  	}
   213  	err1 := s.writer.Flush()
   214  	err2 := s.dstFile.Close()
   215  	err3 := s.baseInterlockingDirectorate.Close()
   216  	if err1 != nil {
   217  		return errors.Trace(err1)
   218  	} else if err2 != nil {
   219  		return errors.Trace(err2)
   220  	}
   221  	return err3
   222  }
   223  
   224  const (
   225  	expFormatBig   = 1e15
   226  	expFormatSmall = 1e-15
   227  )
   228  
   229  // DumpRealOutfile dumps a real number to lineBuf.
   230  func DumpRealOutfile(realBuf, lineBuf []byte, v float64, tp *types.FieldType) ([]byte, []byte) {
   231  	prec := types.UnspecifiedLength
   232  	if tp.Decimal > 0 && tp.Decimal != allegrosql.NotFixedDec {
   233  		prec = tp.Decimal
   234  	}
   235  	absV := math.Abs(v)
   236  	if prec == types.UnspecifiedLength && (absV >= expFormatBig || (absV != 0 && absV < expFormatSmall)) {
   237  		realBuf = strconv.AppendFloat(realBuf[:0], v, 'e', prec, 64)
   238  		if idx := bytes.IndexByte(realBuf, '+'); idx != -1 {
   239  			lineBuf = append(lineBuf, realBuf[:idx]...)
   240  			lineBuf = append(lineBuf, realBuf[idx+1:]...)
   241  		} else {
   242  			lineBuf = append(lineBuf, realBuf...)
   243  		}
   244  	} else {
   245  		lineBuf = strconv.AppendFloat(lineBuf, v, 'f', prec, 64)
   246  	}
   247  	return realBuf, lineBuf
   248  }