github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/evaluator/builtin_time.go (about) 1 // Copyright 2013 The ql Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSES/QL-LICENSE file. 4 5 // Copyright 2015 PingCAP, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package evaluator 19 20 import ( 21 "fmt" 22 "regexp" 23 "strings" 24 "time" 25 26 "github.com/insionng/yougam/libraries/juju/errors" 27 "github.com/insionng/yougam/libraries/pingcap/tidb/ast" 28 "github.com/insionng/yougam/libraries/pingcap/tidb/context" 29 "github.com/insionng/yougam/libraries/pingcap/tidb/mysql" 30 "github.com/insionng/yougam/libraries/pingcap/tidb/util/types" 31 ) 32 33 func convertToTime(arg types.Datum, tp byte) (d types.Datum, err error) { 34 f := types.NewFieldType(tp) 35 f.Decimal = mysql.MaxFsp 36 37 d, err = arg.ConvertTo(f) 38 if err != nil { 39 d.SetNull() 40 return d, errors.Trace(err) 41 } 42 43 if d.Kind() == types.KindNull { 44 return d, nil 45 } 46 47 if d.Kind() != types.KindMysqlTime { 48 err = errors.Errorf("need time type, but got %T", d.GetValue()) 49 d.SetNull() 50 return d, err 51 } 52 return d, nil 53 } 54 55 func convertToDuration(arg types.Datum, fsp int) (d types.Datum, err error) { 56 f := types.NewFieldType(mysql.TypeDuration) 57 f.Decimal = fsp 58 59 d, err = arg.ConvertTo(f) 60 if err != nil { 61 d.SetNull() 62 return d, errors.Trace(err) 63 } 64 65 if d.Kind() == types.KindNull { 66 d.SetNull() 67 return d, nil 68 } 69 70 if d.Kind() != types.KindMysqlDuration { 71 err = errors.Errorf("need duration type, but got %T", d.GetValue()) 72 d.SetNull() 73 return d, err 74 } 75 return d, nil 76 } 77 78 func builtinDate(args []types.Datum, _ context.Context) (types.Datum, error) { 79 return convertToTime(args[0], mysql.TypeDate) 80 } 81 82 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_day 83 // day is a synonym for DayOfMonth 84 func builtinDay(args []types.Datum, ctx context.Context) (types.Datum, error) { 85 return builtinDayOfMonth(args, ctx) 86 } 87 88 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour 89 func builtinHour(args []types.Datum, _ context.Context) (types.Datum, error) { 90 d, err := convertToDuration(args[0], mysql.MaxFsp) 91 if err != nil || d.Kind() == types.KindNull { 92 d.SetNull() 93 return d, errors.Trace(err) 94 } 95 96 // No need to check type here. 97 h := int64(d.GetMysqlDuration().Hour()) 98 d.SetInt64(h) 99 return d, nil 100 } 101 102 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_minute 103 func builtinMinute(args []types.Datum, _ context.Context) (types.Datum, error) { 104 d, err := convertToDuration(args[0], mysql.MaxFsp) 105 if err != nil || d.Kind() == types.KindNull { 106 d.SetNull() 107 return d, errors.Trace(err) 108 } 109 110 // No need to check type here. 111 m := int64(d.GetMysqlDuration().Minute()) 112 d.SetInt64(m) 113 return d, nil 114 } 115 116 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_second 117 func builtinSecond(args []types.Datum, _ context.Context) (types.Datum, error) { 118 d, err := convertToDuration(args[0], mysql.MaxFsp) 119 if err != nil || d.Kind() == types.KindNull { 120 d.SetNull() 121 return d, errors.Trace(err) 122 } 123 124 // No need to check type here. 125 s := int64(d.GetMysqlDuration().Second()) 126 d.SetInt64(s) 127 return d, nil 128 } 129 130 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_microsecond 131 func builtinMicroSecond(args []types.Datum, _ context.Context) (types.Datum, error) { 132 d, err := convertToDuration(args[0], mysql.MaxFsp) 133 if err != nil || d.Kind() == types.KindNull { 134 d.SetNull() 135 return d, errors.Trace(err) 136 } 137 138 // No need to check type here. 139 m := int64(d.GetMysqlDuration().MicroSecond()) 140 d.SetInt64(m) 141 return d, nil 142 } 143 144 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_month 145 func builtinMonth(args []types.Datum, _ context.Context) (types.Datum, error) { 146 d, err := convertToTime(args[0], mysql.TypeDate) 147 if err != nil || d.Kind() == types.KindNull { 148 d.SetNull() 149 return d, errors.Trace(err) 150 } 151 152 // No need to check type here. 153 t := d.GetMysqlTime() 154 i := int64(0) 155 if t.IsZero() { 156 d.SetInt64(i) 157 return d, nil 158 } 159 i = int64(t.Month()) 160 d.SetInt64(i) 161 return d, nil 162 } 163 164 func builtinNow(args []types.Datum, _ context.Context) (d types.Datum, err error) { 165 // TODO: if NOW is used in stored function or trigger, NOW will return the beginning time 166 // of the execution. 167 fsp := 0 168 if len(args) == 1 && args[0].Kind() != types.KindNull { 169 if fsp, err = checkFsp(args[0]); err != nil { 170 d.SetNull() 171 return d, errors.Trace(err) 172 } 173 } 174 175 t := mysql.Time{ 176 Time: time.Now(), 177 Type: mysql.TypeDatetime, 178 // set unspecified for later round 179 Fsp: mysql.UnspecifiedFsp, 180 } 181 182 tr, err := t.RoundFrac(int(fsp)) 183 if err != nil { 184 d.SetNull() 185 return d, errors.Trace(err) 186 } 187 d.SetMysqlTime(tr) 188 return d, nil 189 } 190 191 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayname 192 func builtinDayName(args []types.Datum, ctx context.Context) (types.Datum, error) { 193 d, err := builtinWeekDay(args, ctx) 194 if err != nil || d.Kind() == types.KindNull { 195 d.SetNull() 196 return d, errors.Trace(err) 197 } 198 weekday := d.GetInt64() 199 if (weekday < 0) || (weekday >= int64(len(mysql.WeekdayNames))) { 200 d.SetNull() 201 return d, errors.Errorf("no name for invalid weekday: %d.", weekday) 202 } 203 d.SetString(mysql.WeekdayNames[weekday]) 204 return d, nil 205 } 206 207 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayofmonth 208 func builtinDayOfMonth(args []types.Datum, _ context.Context) (d types.Datum, err error) { 209 // TODO: some invalid format like 2000-00-00 will return 0 too. 210 d, err = convertToTime(args[0], mysql.TypeDate) 211 if err != nil || d.Kind() == types.KindNull { 212 d.SetNull() 213 return d, errors.Trace(err) 214 } 215 216 // No need to check type here. 217 t := d.GetMysqlTime() 218 if t.IsZero() { 219 d.SetInt64(int64(0)) 220 return d, nil 221 } 222 223 d.SetInt64(int64(t.Day())) 224 return d, nil 225 } 226 227 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayofweek 228 func builtinDayOfWeek(args []types.Datum, _ context.Context) (d types.Datum, err error) { 229 d, err = convertToTime(args[0], mysql.TypeDate) 230 if err != nil || d.Kind() == types.KindNull { 231 d.SetNull() 232 return d, errors.Trace(err) 233 } 234 235 // No need to check type here. 236 t := d.GetMysqlTime() 237 if t.IsZero() { 238 d.SetNull() 239 // TODO: log warning or return error? 240 return d, nil 241 } 242 243 // 1 is Sunday, 2 is Monday, .... 7 is Saturday 244 d.SetInt64(int64(t.Weekday()) + 1) 245 return d, nil 246 } 247 248 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayofyear 249 func builtinDayOfYear(args []types.Datum, _ context.Context) (types.Datum, error) { 250 d, err := convertToTime(args[0], mysql.TypeDate) 251 if err != nil || d.Kind() == types.KindNull { 252 d.SetNull() 253 return d, errors.Trace(err) 254 } 255 256 t := d.GetMysqlTime() 257 if t.IsZero() { 258 // TODO: log warning or return error? 259 d.SetNull() 260 return d, nil 261 } 262 263 yd := int64(t.YearDay()) 264 d.SetInt64(yd) 265 return d, nil 266 } 267 268 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_week 269 func builtinWeek(args []types.Datum, _ context.Context) (types.Datum, error) { 270 d, err := convertToTime(args[0], mysql.TypeDate) 271 if err != nil || d.Kind() == types.KindNull { 272 d.SetNull() 273 return d, errors.Trace(err) 274 } 275 276 // No need to check type here. 277 t := d.GetMysqlTime() 278 if t.IsZero() { 279 // TODO: log warning or return error? 280 d.SetNull() 281 return d, nil 282 } 283 284 // TODO: support multi mode for week 285 _, week := t.ISOWeek() 286 wi := int64(week) 287 d.SetInt64(wi) 288 return d, nil 289 } 290 291 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_weekday 292 func builtinWeekDay(args []types.Datum, _ context.Context) (types.Datum, error) { 293 d, err := convertToTime(args[0], mysql.TypeDate) 294 if err != nil || d.Kind() == types.KindNull { 295 d.SetNull() 296 return d, errors.Trace(err) 297 } 298 299 // No need to check type here. 300 t := d.GetMysqlTime() 301 if t.IsZero() { 302 // TODO: log warning or return error? 303 d.SetNull() 304 return d, nil 305 } 306 307 // Monday is 0, ... Sunday = 6 in MySQL 308 // but in go, Sunday is 0, ... Saturday is 6 309 // w will do a conversion. 310 w := (int64(t.Weekday()) + 6) % 7 311 d.SetInt64(w) 312 return d, nil 313 } 314 315 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_weekofyear 316 func builtinWeekOfYear(args []types.Datum, ctx context.Context) (types.Datum, error) { 317 // WeekOfYear is equivalent to to Week(date, 3) 318 d := types.Datum{} 319 d.SetInt64(3) 320 return builtinWeek([]types.Datum{args[0], d}, ctx) 321 } 322 323 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_year 324 func builtinYear(args []types.Datum, _ context.Context) (types.Datum, error) { 325 d, err := convertToTime(args[0], mysql.TypeDate) 326 if err != nil || d.Kind() == types.KindNull { 327 return d, errors.Trace(err) 328 } 329 330 // No need to check type here. 331 t := d.GetMysqlTime() 332 if t.IsZero() { 333 d.SetInt64(0) 334 return d, nil 335 } 336 337 d.SetInt64(int64(t.Year())) 338 return d, nil 339 } 340 341 // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_yearweek 342 func builtinYearWeek(args []types.Datum, _ context.Context) (types.Datum, error) { 343 d, err := convertToTime(args[0], mysql.TypeDate) 344 if err != nil || d.Kind() == types.KindNull { 345 d.SetNull() 346 return d, errors.Trace(err) 347 } 348 349 // No need to check type here. 350 t := d.GetMysqlTime() 351 if t.IsZero() { 352 d.SetNull() 353 // TODO: log warning or return error? 354 return d, nil 355 } 356 357 // TODO: support multi mode for week 358 year, week := t.ISOWeek() 359 d.SetInt64(int64(year*100 + week)) 360 return d, nil 361 } 362 363 func builtinSysDate(args []types.Datum, ctx context.Context) (types.Datum, error) { 364 // SYSDATE is not the same as NOW if NOW is used in a stored function or trigger. 365 // But here we can just think they are the same because we don't support stored function 366 // and trigger now. 367 return builtinNow(args, ctx) 368 } 369 370 // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_curdate 371 func builtinCurrentDate(args []types.Datum, _ context.Context) (d types.Datum, err error) { 372 year, month, day := time.Now().Date() 373 t := mysql.Time{ 374 Time: time.Date(year, month, day, 0, 0, 0, 0, time.Local), 375 Type: mysql.TypeDate, Fsp: 0} 376 d.SetMysqlTime(t) 377 return d, nil 378 } 379 380 // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_curtime 381 func builtinCurrentTime(args []types.Datum, _ context.Context) (d types.Datum, err error) { 382 fsp := 0 383 if len(args) == 1 && args[0].Kind() != types.KindNull { 384 if fsp, err = checkFsp(args[0]); err != nil { 385 d.SetNull() 386 return d, errors.Trace(err) 387 } 388 } 389 d.SetString(time.Now().Format("15:04:05.000000")) 390 return convertToDuration(d, fsp) 391 } 392 393 // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_utc-date 394 func builtinUTCDate(args []types.Datum, _ context.Context) (d types.Datum, err error) { 395 year, month, day := time.Now().UTC().Date() 396 t := mysql.Time{ 397 Time: time.Date(year, month, day, 0, 0, 0, 0, time.UTC), 398 Type: mysql.TypeDate, Fsp: mysql.UnspecifiedFsp} 399 d.SetMysqlTime(t) 400 return d, nil 401 } 402 403 // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_extract 404 func builtinExtract(args []types.Datum, _ context.Context) (d types.Datum, err error) { 405 unit := args[0].GetString() 406 vd := args[1] 407 408 if vd.Kind() == types.KindNull { 409 d.SetNull() 410 return d, nil 411 } 412 413 f := types.NewFieldType(mysql.TypeDatetime) 414 f.Decimal = mysql.MaxFsp 415 val, err := vd.ConvertTo(f) 416 if err != nil { 417 d.SetNull() 418 return d, errors.Trace(err) 419 } 420 if val.Kind() == types.KindNull { 421 d.SetNull() 422 return d, nil 423 } 424 425 if val.Kind() != types.KindMysqlTime { 426 err = errors.Errorf("need time type, but got %T", val) 427 d.SetNull() 428 return d, err 429 } 430 t := val.GetMysqlTime() 431 n, err1 := mysql.ExtractTimeNum(unit, t) 432 if err1 != nil { 433 d.SetNull() 434 return d, errors.Trace(err1) 435 } 436 d.SetInt64(n) 437 return d, nil 438 } 439 440 func checkFsp(arg types.Datum) (int, error) { 441 fsp, err := arg.ToInt64() 442 if err != nil { 443 return 0, errors.Trace(err) 444 } 445 if int(fsp) > mysql.MaxFsp { 446 return 0, errors.Errorf("Too big precision %d specified. Maximum is 6.", fsp) 447 } else if fsp < 0 { 448 return 0, errors.Errorf("Invalid negative %d specified, must in [0, 6].", fsp) 449 } 450 return int(fsp), nil 451 } 452 453 func builtinDateArith(args []types.Datum, ctx context.Context) (d types.Datum, err error) { 454 // Op is used for distinguishing date_add and date_sub. 455 // args[0] -> Op 456 // args[1] -> Date 457 // args[2] -> DateArithInterval 458 // health check for date and interval 459 if args[1].Kind() == types.KindNull { 460 d.SetNull() 461 return d, nil 462 } 463 nodeDate := args[1] 464 nodeInterval := args[2].GetInterface().(ast.DateArithInterval) 465 nodeIntervalIntervalDatum := nodeInterval.Interval.GetDatum() 466 if nodeIntervalIntervalDatum.Kind() == types.KindNull { 467 d.SetNull() 468 return d, nil 469 } 470 // parse date 471 fieldType := mysql.TypeDate 472 var resultField *types.FieldType 473 switch nodeDate.Kind() { 474 case types.KindMysqlTime: 475 x := nodeDate.GetMysqlTime() 476 if (x.Type == mysql.TypeDatetime) || (x.Type == mysql.TypeTimestamp) { 477 fieldType = mysql.TypeDatetime 478 } 479 case types.KindString: 480 x := nodeDate.GetString() 481 if !mysql.IsDateFormat(x) { 482 fieldType = mysql.TypeDatetime 483 } 484 case types.KindInt64: 485 x := nodeDate.GetInt64() 486 if t, err1 := mysql.ParseTimeFromInt64(x); err1 == nil { 487 if (t.Type == mysql.TypeDatetime) || (t.Type == mysql.TypeTimestamp) { 488 fieldType = mysql.TypeDatetime 489 } 490 } 491 } 492 if mysql.IsClockUnit(nodeInterval.Unit) { 493 fieldType = mysql.TypeDatetime 494 } 495 resultField = types.NewFieldType(fieldType) 496 resultField.Decimal = mysql.MaxFsp 497 value, err := nodeDate.ConvertTo(resultField) 498 if err != nil { 499 d.SetNull() 500 return d, ErrInvalidOperation.Gen("DateArith invalid args, need date but get %T", nodeDate) 501 } 502 if value.Kind() == types.KindNull { 503 d.SetNull() 504 return d, ErrInvalidOperation.Gen("DateArith invalid args, need date but get %v", value.GetValue()) 505 } 506 if value.Kind() != types.KindMysqlTime { 507 d.SetNull() 508 return d, ErrInvalidOperation.Gen("DateArith need time type, but got %T", value.GetValue()) 509 } 510 result := value.GetMysqlTime() 511 // parse interval 512 var interval string 513 if strings.ToLower(nodeInterval.Unit) == "day" { 514 day, err1 := parseDayInterval(*nodeIntervalIntervalDatum) 515 if err1 != nil { 516 d.SetNull() 517 return d, ErrInvalidOperation.Gen("DateArith invalid day interval, need int but got %T", nodeIntervalIntervalDatum.GetString()) 518 } 519 interval = fmt.Sprintf("%d", day) 520 } else { 521 if nodeIntervalIntervalDatum.Kind() == types.KindString { 522 interval = fmt.Sprintf("%v", nodeIntervalIntervalDatum.GetString()) 523 } else { 524 ii, err1 := nodeIntervalIntervalDatum.ToInt64() 525 if err1 != nil { 526 d.SetNull() 527 return d, errors.Trace(err1) 528 } 529 interval = fmt.Sprintf("%v", ii) 530 } 531 } 532 year, month, day, duration, err := mysql.ExtractTimeValue(nodeInterval.Unit, interval) 533 if err != nil { 534 d.SetNull() 535 return d, errors.Trace(err) 536 } 537 op := args[0].GetInterface().(ast.DateArithType) 538 if op == ast.DateSub { 539 year, month, day, duration = -year, -month, -day, -duration 540 } 541 result.Time = result.Time.Add(duration) 542 result.Time = result.Time.AddDate(int(year), int(month), int(day)) 543 if result.Time.Nanosecond() == 0 { 544 result.Fsp = 0 545 } 546 d.SetMysqlTime(result) 547 return d, nil 548 } 549 550 var reg = regexp.MustCompile(`[\d]+`) 551 552 func parseDayInterval(value types.Datum) (int64, error) { 553 switch value.Kind() { 554 case types.KindString: 555 vs := value.GetString() 556 s := strings.ToLower(vs) 557 if s == "false" { 558 return 0, nil 559 } else if s == "true" { 560 return 1, nil 561 } 562 value.SetString(reg.FindString(vs)) 563 } 564 return value.ToInt64() 565 }