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 }