github.com/matrixorigin/matrixone@v0.7.0/pkg/sql/plan/function/builtin/binary/str_to_date.go (about) 1 // Copyright 2021 Matrix Origin 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package binary 16 17 import ( 18 "context" 19 "strings" 20 "unicode" 21 22 "github.com/matrixorigin/matrixone/pkg/common/moerr" 23 "github.com/matrixorigin/matrixone/pkg/container/nulls" 24 "github.com/matrixorigin/matrixone/pkg/container/types" 25 "github.com/matrixorigin/matrixone/pkg/container/vector" 26 "github.com/matrixorigin/matrixone/pkg/vm/process" 27 ) 28 29 const ( 30 // MaxFsp is the maximum digit of fractional seconds part. 31 MaxFsp = 6 32 ) 33 34 // Convert the string to date type value according to the format string 35 func StrToDate(vectors []*vector.Vector, proc *process.Process) (*vector.Vector, error) { 36 dateVector := vectors[0] 37 formatVector := vectors[1] 38 39 resultType := types.T_date.ToType() 40 if !formatVector.IsScalar() { 41 return nil, moerr.NewInvalidArg(proc.Ctx, "to_date format", "not constant") 42 } 43 if dateVector.IsScalarNull() || formatVector.IsScalarNull() { 44 return proc.AllocScalarNullVector(resultType), nil 45 } 46 // get the format string. 47 formatMask := formatVector.GetString(0) 48 49 if dateVector.IsScalar() { 50 datestr := dateVector.GetString(0) 51 ctx := make(map[string]int) 52 time := NewGeneralTime() 53 success := strToDate(proc.Ctx, time, datestr, formatMask, ctx) 54 if !success { 55 // should be null 56 return proc.AllocScalarNullVector(resultType), nil 57 } else { 58 if types.ValidDate(int32(time.year), time.month, time.day) { 59 resCol := types.DateFromCalendar(int32(time.year), time.month, time.day) 60 return vector.NewConstFixed[types.Date](resultType, 1, resCol, proc.Mp()), nil 61 } else { 62 // should be null 63 return proc.AllocScalarNullVector(resultType), nil 64 } 65 } 66 } else { 67 datestrs := vector.MustStrCols(dateVector) 68 rNsp := nulls.NewWithSize(len(datestrs)) 69 resCol, err := CalcStrToDate(proc.Ctx, datestrs, formatMask, dateVector.Nsp, rNsp) 70 if err != nil { 71 return nil, err 72 } 73 resultVector := vector.NewWithFixed[types.Date](resultType, resCol, rNsp, proc.Mp()) 74 nulls.Set(resultVector.Nsp, dateVector.Nsp) 75 return resultVector, nil 76 } 77 } 78 79 // Convert the string to datetime type value according to the format string 80 func StrToDateTime(vectors []*vector.Vector, proc *process.Process) (*vector.Vector, error) { 81 dateVector := vectors[0] 82 formatVector := vectors[1] 83 84 resultType := types.T_datetime.ToType() 85 if !formatVector.IsScalar() { 86 return nil, moerr.NewInvalidArg(proc.Ctx, "to_date format", "not constant") 87 } 88 if dateVector.IsScalarNull() || formatVector.IsScalarNull() { 89 return proc.AllocScalarNullVector(resultType), nil 90 } 91 // get the format string. 92 formatMask := formatVector.GetString(0) 93 94 if dateVector.IsScalar() { 95 datetimestr := dateVector.GetString(0) 96 ctx := make(map[string]int) 97 time := NewGeneralTime() 98 success := strToDate(proc.Ctx, time, datetimestr, formatMask, ctx) 99 if !success { 100 // should be null 101 return proc.AllocScalarNullVector(resultType), nil 102 } else { 103 if types.ValidDatetime(int32(time.year), time.month, time.day) && types.ValidTimeInDay(time.hour, time.minute, time.second) { 104 resCol := types.DatetimeFromClock(int32(time.year), time.month, time.day, time.hour, time.minute, time.second, time.microsecond) 105 return vector.NewConstFixed[types.Datetime](resultType, 1, resCol, proc.Mp()), nil 106 } else { 107 // should be null 108 return proc.AllocScalarNullVector(resultType), nil 109 } 110 } 111 } else { 112 datetimestrs := vector.MustStrCols(dateVector) 113 rNsp := nulls.NewWithSize(len(datetimestrs)) 114 resCol, err := CalcStrToDatetime(proc.Ctx, datetimestrs, formatMask, dateVector.Nsp, rNsp) 115 if err != nil { 116 return nil, err 117 } 118 resultVector := vector.NewWithFixed[types.Datetime](resultType, resCol, nulls.NewWithSize(len(resCol)), proc.Mp()) 119 nulls.Set(resultVector.Nsp, dateVector.Nsp) 120 return resultVector, nil 121 } 122 } 123 124 // // Convert the string to time type value according to the format string,such as '09:30:17' 125 func StrToTime(vectors []*vector.Vector, proc *process.Process) (*vector.Vector, error) { 126 dateVector := vectors[0] 127 formatVector := vectors[1] 128 129 resultType := types.T_time.ToType() 130 if !formatVector.IsScalar() { 131 return nil, moerr.NewInvalidArg(proc.Ctx, "to_date format", "not constant") 132 } 133 if dateVector.IsScalarNull() || formatVector.IsScalarNull() { 134 return proc.AllocScalarNullVector(resultType), nil 135 } 136 // get the format string. 137 formatMask := formatVector.GetString(0) 138 139 if dateVector.IsScalar() { 140 timestr := dateVector.GetString(0) 141 142 ctx := make(map[string]int) 143 time := NewGeneralTime() 144 success := strToDate(proc.Ctx, time, timestr, formatMask, ctx) 145 if !success { 146 // should be null 147 return proc.AllocScalarNullVector(resultType), nil 148 } else { 149 if types.ValidTime(uint64(time.hour), uint64(time.minute), uint64(time.second)) { 150 resCol := types.TimeFromClock(false, uint64(time.hour), time.minute, time.second, time.microsecond) 151 return vector.NewConstFixed[types.Time](resultType, 1, resCol, proc.Mp()), nil 152 } else { 153 // should be null 154 return proc.AllocScalarNullVector(resultType), nil 155 } 156 } 157 } else { 158 timestrs := vector.MustStrCols(dateVector) 159 rNsp := nulls.NewWithSize(len(timestrs)) 160 resCol, err := CalcStrToTime(proc.Ctx, timestrs, formatMask, dateVector.Nsp, rNsp) 161 if err != nil { 162 return nil, err 163 } 164 resultVector := vector.NewWithFixed[types.Time](resultType, resCol, nulls.NewWithSize(len(resCol)), proc.Mp()) 165 nulls.Set(resultVector.Nsp, dateVector.Nsp) 166 return resultVector, nil 167 } 168 } 169 170 func CalcStrToDatetime(ctx context.Context, timestrs []string, format string, ns *nulls.Nulls, rNsp *nulls.Nulls) ([]types.Datetime, error) { 171 res := make([]types.Datetime, len(timestrs)) 172 time := NewGeneralTime() 173 for idx, timestr := range timestrs { 174 if nulls.Contains(ns, uint64(idx)) { 175 continue 176 } 177 success := CoreStrToDate(ctx, time, timestr, format) 178 if !success { 179 // should be null 180 nulls.Add(rNsp, uint64(idx)) 181 } else { 182 if types.ValidDatetime(int32(time.year), time.month, time.day) && types.ValidTimeInDay(time.hour, time.minute, time.second) { 183 res[idx] = types.DatetimeFromClock(int32(time.year), time.month, time.day, time.hour, time.minute, time.second, time.microsecond) 184 } else { 185 // should be null 186 nulls.Add(rNsp, uint64(idx)) 187 } 188 } 189 time.ResetTime() 190 } 191 return res, nil 192 } 193 194 func CalcStrToDate(ctx context.Context, timestrs []string, format string, ns *nulls.Nulls, rNsp *nulls.Nulls) ([]types.Date, error) { 195 res := make([]types.Date, len(timestrs)) 196 time := NewGeneralTime() 197 for idx, timestr := range timestrs { 198 if nulls.Contains(ns, uint64(idx)) { 199 continue 200 } 201 success := CoreStrToDate(ctx, time, timestr, format) 202 if !success { 203 // should be null 204 nulls.Add(rNsp, uint64(idx)) 205 } else { 206 if types.ValidDate(int32(time.year), time.month, time.day) { 207 res[idx] = types.DateFromCalendar(int32(time.year), time.month, time.day) 208 } else { 209 // should be null 210 nulls.Add(rNsp, uint64(idx)) 211 } 212 } 213 time.ResetTime() 214 } 215 return res, nil 216 } 217 218 func CalcStrToTime(ctx context.Context, timestrs []string, format string, ns *nulls.Nulls, rNsp *nulls.Nulls) ([]types.Time, error) { 219 res := make([]types.Time, len(timestrs)) 220 time := NewGeneralTime() 221 for idx, timestr := range timestrs { 222 if nulls.Contains(ns, uint64(idx)) { 223 continue 224 } 225 success := CoreStrToDate(ctx, time, timestr, format) 226 if !success { 227 // should be null 228 nulls.Add(rNsp, uint64(idx)) 229 } else { 230 if types.ValidTime(uint64(time.hour), uint64(time.minute), uint64(time.second)) { 231 res[idx] = types.TimeFromClock(false, uint64(time.hour), time.minute, time.second, time.microsecond) 232 } else { 233 // should be null 234 nulls.Add(rNsp, uint64(idx)) 235 } 236 } 237 time.ResetTime() 238 } 239 return res, nil 240 } 241 242 func CoreStrToDate(cctx context.Context, t *GeneralTime, date string, format string) bool { 243 ctx := make(map[string]int) 244 success := strToDate(cctx, t, date, format, ctx) 245 if !success { 246 return false 247 } 248 if err := checkMysqlTime(cctx, t, ctx); err != nil { 249 return false 250 } 251 return true 252 } 253 254 // strToDate converts date string according to format, 255 // the value will be stored in argument ctx. the second return value is true when success 256 func strToDate(cctx context.Context, t *GeneralTime, date string, format string, ctx map[string]int) (success bool) { 257 date = trimWhiteSpace(date) 258 format = trimWhiteSpace(format) 259 260 token, formatRemain, succ := nextFormatToken(format) 261 if !succ { 262 return false 263 } 264 265 if token == "" { 266 if len(date) != 0 { 267 // Extra characters at the end of date are ignored 268 return true 269 } 270 // Normal case. Both token and date are empty now. 271 return true 272 } 273 274 if len(date) == 0 { 275 ctx[token] = 0 276 return true 277 } 278 279 dateRemain, succ := matchDateWithToken(t, date, token, ctx) 280 if !succ { 281 return false 282 } 283 284 return strToDate(cctx, t, dateRemain, formatRemain, ctx) 285 } 286 287 // checkMysqlTime fixes the Time use the values in the context. 288 func checkMysqlTime(cctx context.Context, t *GeneralTime, ctx map[string]int) error { 289 if valueAMorPm, ok := ctx["%p"]; ok { 290 if _, ok := ctx["%H"]; ok { 291 return moerr.NewInternalError(cctx, "Truncated incorrect %-.64s value: '%-.128s'", "time", t) 292 } 293 if t.getHour() == 0 { 294 return moerr.NewInternalError(cctx, "Truncated incorrect %-.64s value: '%-.128s'", "time", t) 295 } 296 if t.getHour() == 12 { 297 // 12 is a special hour. 298 switch valueAMorPm { 299 case timeOfAM: 300 t.setHour(0) 301 case timeOfPM: 302 t.setHour(12) 303 } 304 return nil 305 } 306 if valueAMorPm == timeOfPM { 307 t.setHour(t.getHour() + 12) 308 } 309 } else { 310 if _, ok := ctx["%h"]; ok && t.getHour() == 12 { 311 t.setHour(0) 312 } 313 } 314 return nil 315 } 316 317 // Judge the return value type of the str_to_date function according to the value of the fromat parameter 318 func JudgmentToDateReturnType(format string) (tp types.T, fsp int) { 319 isTime, isDate := GetTimeFormatType(format) 320 if isTime && !isDate { 321 tp = types.T_time 322 } else if !isTime && isDate { 323 tp = types.T_date 324 } else { 325 tp = types.T_datetime 326 } 327 if strings.Contains(format, "%f") { 328 fsp = MaxFsp 329 } 330 return tp, MaxFsp 331 } 332 333 // GetTimeFormatType checks the type(Time, Date or Datetime) of a format string. 334 func GetTimeFormatType(format string) (isTime, isDate bool) { 335 format = trimWhiteSpace(format) 336 var token string 337 var succ bool 338 for { 339 token, format, succ = nextFormatToken(format) 340 if len(token) == 0 { 341 break 342 } 343 if !succ { 344 isTime, isDate = false, false 345 break 346 } 347 if len(token) >= 2 && token[0] == '%' { 348 switch token[1] { 349 case 'h', 'H', 'i', 'I', 's', 'S', 'k', 'l', 'f', 'r', 'T': 350 isTime = true 351 case 'y', 'Y', 'm', 'M', 'c', 'b', 'D', 'd', 'e': 352 isDate = true 353 } 354 } 355 if isTime && isDate { 356 break 357 } 358 } 359 return 360 } 361 362 // trim spaces in strings 363 func trimWhiteSpace(input string) string { 364 for i, c := range input { 365 if !unicode.IsSpace(c) { 366 return input[i:] 367 } 368 } 369 return "" 370 } 371 372 // nextFormatToken takes next one format control token from the string. 373 // such as: format "%d %H %m" will get token "%d" and the remain is " %H %m". 374 func nextFormatToken(format string) (token string, remain string, success bool) { 375 if len(format) == 0 { 376 return "", "", true 377 } 378 379 // Just one character. 380 if len(format) == 1 { 381 if format[0] == '%' { 382 return "", "", false 383 } 384 return format, "", true 385 } 386 387 // More than one character. 388 if format[0] == '%' { 389 return format[:2], format[2:], true 390 } 391 392 return format[:1], format[1:], true 393 }