github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/duration/parse.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package duration
    12  
    13  import (
    14  	"math"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode"
    20  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgerror"
    21  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/types"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  type intervalLexer struct {
    26  	str    string
    27  	offset int
    28  	err    error
    29  }
    30  
    31  // consumeNum consumes the next decimal number.
    32  // 1st return value is the integer part.
    33  // 2nd return value is whether a decimal part was encountered.
    34  // 3rd return value is the decimal part as a float.
    35  // If the number is negative, both the 1st and 3rd return
    36  // value are negative.
    37  // The decimal value is returned separately from the integer value so
    38  // as to support large integer values which would not fit in a float.
    39  func (l *intervalLexer) consumeNum() (int64, bool, float64) {
    40  	if l.err != nil {
    41  		return 0, false, 0
    42  	}
    43  
    44  	offset := l.offset
    45  
    46  	neg := false
    47  	if l.offset < len(l.str) && l.str[l.offset] == '-' {
    48  		// Remember a leading negative sign. We can't use "intPart < 0"
    49  		// below, because when the input syntax is "-0.xxxx" intPart is 0.
    50  		neg = true
    51  	}
    52  
    53  	// Integer part before the decimal separator.
    54  	intPart := l.consumeInt()
    55  
    56  	var decPart float64
    57  	hasDecimal := false
    58  	if l.offset < len(l.str) && l.str[l.offset] == '.' {
    59  		hasDecimal = true
    60  		start := l.offset
    61  
    62  		// Advance offset to prepare a valid argument to ParseFloat().
    63  		l.offset++
    64  		for ; l.offset < len(l.str) && l.str[l.offset] >= '0' && l.str[l.offset] <= '9'; l.offset++ {
    65  		}
    66  		// Try to convert.
    67  		value, err := strconv.ParseFloat(l.str[start:l.offset], 64)
    68  		if err != nil {
    69  			l.err = pgerror.Wrap(
    70  				err, pgcode.InvalidDatetimeFormat, "interval")
    71  			return 0, false, 0
    72  		}
    73  		decPart = value
    74  	}
    75  
    76  	// Ensure we have something.
    77  	if offset == l.offset {
    78  		l.err = pgerror.Newf(
    79  			pgcode.InvalidDatetimeFormat, "interval: missing number at position %d: %q", offset, l.str)
    80  		return 0, false, 0
    81  	}
    82  
    83  	if neg {
    84  		decPart = -decPart
    85  	}
    86  	return intPart, hasDecimal, decPart
    87  }
    88  
    89  // Consumes the next integer.
    90  func (l *intervalLexer) consumeInt() int64 {
    91  	if l.err != nil {
    92  		return 0
    93  	}
    94  
    95  	start := l.offset
    96  
    97  	// Advance offset to prepare a valid argument to ParseInt().
    98  	if l.offset < len(l.str) && (l.str[l.offset] == '-' || l.str[l.offset] == '+') {
    99  		l.offset++
   100  	}
   101  	for ; l.offset < len(l.str) && l.str[l.offset] >= '0' && l.str[l.offset] <= '9'; l.offset++ {
   102  	}
   103  	// Check if we have something like ".X".
   104  	if start == l.offset && len(l.str) > (l.offset+1) && l.str[l.offset] == '.' {
   105  		return 0
   106  	}
   107  
   108  	x, err := strconv.ParseInt(l.str[start:l.offset], 10, 64)
   109  	if err != nil {
   110  		l.err = pgerror.Wrap(
   111  			err, pgcode.InvalidDatetimeFormat, "interval")
   112  		return 0
   113  	}
   114  	if start == l.offset {
   115  		l.err = pgerror.Newf(
   116  			pgcode.InvalidDatetimeFormat, "interval: missing number at position %d: %q", start, l.str)
   117  		return 0
   118  	}
   119  	return x
   120  }
   121  
   122  // Consumes the next unit.
   123  func (l *intervalLexer) consumeUnit(skipCharacter byte) string {
   124  	if l.err != nil {
   125  		return ""
   126  	}
   127  
   128  	offset := l.offset
   129  	for ; l.offset < len(l.str); l.offset++ {
   130  		if (l.str[l.offset] >= '0' && l.str[l.offset] <= '9') ||
   131  			l.str[l.offset] == skipCharacter ||
   132  			l.str[l.offset] == '-' {
   133  			break
   134  		}
   135  	}
   136  
   137  	if offset == l.offset {
   138  		l.err = pgerror.Newf(
   139  			pgcode.InvalidDatetimeFormat, "interval: missing unit at position %d: %q", offset, l.str)
   140  		return ""
   141  	}
   142  	return l.str[offset:l.offset]
   143  }
   144  
   145  // Consumes any number of spaces.
   146  func (l *intervalLexer) consumeSpaces() {
   147  	if l.err != nil {
   148  		return
   149  	}
   150  	for ; l.offset < len(l.str) && l.str[l.offset] == ' '; l.offset++ {
   151  	}
   152  }
   153  
   154  // ISO Units.
   155  var isoDateUnitMap = map[string]Duration{
   156  	"D": MakeDuration(0, 1, 0),
   157  	"W": MakeDuration(0, 7, 0),
   158  	"M": MakeDuration(0, 0, 1),
   159  	"Y": MakeDuration(0, 0, 12),
   160  }
   161  
   162  var isoTimeUnitMap = map[string]Duration{
   163  	"S": MakeDuration(time.Second.Nanoseconds(), 0, 0),
   164  	"M": MakeDuration(time.Minute.Nanoseconds(), 0, 0),
   165  	"H": MakeDuration(time.Hour.Nanoseconds(), 0, 0),
   166  }
   167  
   168  const errInvalidSQLDuration = "invalid input syntax for type interval %s"
   169  
   170  type parsedIndex uint8
   171  
   172  const (
   173  	nothingParsed parsedIndex = iota
   174  	hmsParsed
   175  	dayParsed
   176  	yearMonthParsed
   177  )
   178  
   179  func newInvalidSQLDurationError(s string) error {
   180  	return pgerror.Newf(pgcode.InvalidDatetimeFormat, errInvalidSQLDuration, s)
   181  }
   182  
   183  // Parses a SQL standard interval string.
   184  // See the following links for examples:
   185  //   - http://www.postgresql.org/docs/9.1/static/datatype-datetime.html#DATATYPE-INTERVAL-INPUT-EXAMPLES
   186  //   - http://www.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.esqlc.doc/ids_esqlc_0190.htm
   187  func sqlStdToDuration(s string, itm types.IntervalTypeMetadata) (Duration, error) {
   188  	var d Duration
   189  	parts := strings.Fields(s)
   190  	if len(parts) > 3 || len(parts) == 0 {
   191  		return d, newInvalidSQLDurationError(s)
   192  	}
   193  	// Index of which part(s) have been parsed for detecting bad order such as `HH:MM:SS Year-Month`.
   194  	parsedIdx := nothingParsed
   195  	// Both 'Day' and 'Second' can be float, but 'Day Second'::interval is invalid.
   196  	floatParsed := false
   197  	// Parsing backward makes it easy to distinguish 'Day' and 'Second' when encountering a single value.
   198  	//   `1-2 5 9:` and `1-2 5`
   199  	//        |              |
   200  	// day ---+              |
   201  	// second ---------------+
   202  	for i := len(parts) - 1; i >= 0; i-- {
   203  		// Parses leading sign
   204  		part := parts[i]
   205  
   206  		consumeNeg := func(str string) (newStr string, mult int64, ok bool) {
   207  			neg := false
   208  			// Consumes [-+]
   209  			if str != "" {
   210  				c := str[0]
   211  				if c == '-' || c == '+' {
   212  					neg = c == '-'
   213  					str = str[1:]
   214  				}
   215  			}
   216  			if len(str) == 0 {
   217  				return str, 0, false
   218  			}
   219  			if str[0] == '-' || str[0] == '+' {
   220  				return str, 0, false
   221  			}
   222  
   223  			mult = 1
   224  			if neg {
   225  				mult = -1
   226  			}
   227  			return str, mult, true
   228  		}
   229  
   230  		var mult int64
   231  		var ok bool
   232  		if part, mult, ok = consumeNeg(part); !ok {
   233  			return d, newInvalidSQLDurationError(s)
   234  		}
   235  
   236  		if strings.ContainsRune(part, ':') {
   237  			// Try to parse as HH:MM:SS
   238  			if parsedIdx != nothingParsed {
   239  				return d, newInvalidSQLDurationError(s)
   240  			}
   241  			parsedIdx = hmsParsed
   242  			// Colon-separated intervals in Postgres are odd. They have day, hour,
   243  			// minute, or second parts depending on number of fields and if the field
   244  			// is an int or float.
   245  			//
   246  			// Instead of supporting unit changing based on int or float, use the
   247  			// following rules:
   248  			// - If there is a float at the front, it represents D:(<apply below rules).
   249  			// - Two fields is H:M or M:S.fff (unless using MINUTE TO SECOND, then M:S).
   250  			// - Three fields is H:M:S(.fff)?.
   251  			hms := strings.Split(part, ":")
   252  
   253  			// If the first element is blank or is a float, it represents a day.
   254  			// Take it to days, and simplify logic below to a H:M:S scenario.
   255  			firstComponentIsFloat := strings.Contains(hms[0], ".")
   256  			if firstComponentIsFloat || hms[0] == "" {
   257  				// Negatives are not permitted in this format.
   258  				// Also, there must be more units in front.
   259  				if mult != 1 || len(hms) == 1 {
   260  					return d, newInvalidSQLDurationError(s)
   261  				}
   262  				if firstComponentIsFloat {
   263  					days, err := strconv.ParseFloat(hms[0], 64)
   264  					if err != nil {
   265  						return d, newInvalidSQLDurationError(s)
   266  					}
   267  					d = d.Add(MakeDuration(0, 1, 0).MulFloat(days))
   268  				}
   269  
   270  				hms = hms[1:]
   271  				if hms[0], mult, ok = consumeNeg(hms[0]); !ok {
   272  					return d, newInvalidSQLDurationError(s)
   273  				}
   274  			}
   275  
   276  			// Postgres fills in the blanks of all H:M:S as if they were zero.
   277  			for i := 0; i < len(hms); i++ {
   278  				if hms[i] == "" {
   279  					hms[i] = "0"
   280  				}
   281  			}
   282  
   283  			var hours, mins int64
   284  			var secs float64
   285  
   286  			switch len(hms) {
   287  			case 2:
   288  				// If we find a decimal, it must be the m:s.ffffff format
   289  				var err error
   290  				if strings.Contains(hms[1], ".") || itm.DurationField.IsMinuteToSecond() {
   291  					if mins, err = strconv.ParseInt(hms[0], 10, 64); err != nil {
   292  						return d, newInvalidSQLDurationError(s)
   293  					}
   294  					if secs, err = strconv.ParseFloat(hms[1], 64); err != nil {
   295  						return d, newInvalidSQLDurationError(s)
   296  					}
   297  				} else {
   298  					if hours, err = strconv.ParseInt(hms[0], 10, 64); err != nil {
   299  						return d, newInvalidSQLDurationError(s)
   300  					}
   301  					if mins, err = strconv.ParseInt(hms[1], 10, 64); err != nil {
   302  						return d, newInvalidSQLDurationError(s)
   303  					}
   304  				}
   305  			case 3:
   306  				var err error
   307  				if hours, err = strconv.ParseInt(hms[0], 10, 64); err != nil {
   308  					return d, newInvalidSQLDurationError(s)
   309  				}
   310  				if mins, err = strconv.ParseInt(hms[1], 10, 64); err != nil {
   311  					return d, newInvalidSQLDurationError(s)
   312  				}
   313  				if secs, err = strconv.ParseFloat(hms[2], 64); err != nil {
   314  					return d, newInvalidSQLDurationError(s)
   315  				}
   316  			default:
   317  				return d, newInvalidSQLDurationError(s)
   318  			}
   319  
   320  			// None of these units can be negative, as we explicitly strip the negative
   321  			// unit from the very beginning.
   322  			if hours < 0 || mins < 0 || secs < 0 {
   323  				return d, newInvalidSQLDurationError(s)
   324  			}
   325  
   326  			d = d.Add(MakeDuration(time.Hour.Nanoseconds(), 0, 0).Mul(mult * hours))
   327  			d = d.Add(MakeDuration(time.Minute.Nanoseconds(), 0, 0).Mul(mult * mins))
   328  			d = d.Add(MakeDuration(time.Second.Nanoseconds(), 0, 0).MulFloat(float64(mult) * secs))
   329  		} else if strings.ContainsRune(part, '-') {
   330  			// Try to parse as Year-Month.
   331  			if parsedIdx >= yearMonthParsed {
   332  				return d, newInvalidSQLDurationError(s)
   333  			}
   334  			parsedIdx = yearMonthParsed
   335  
   336  			yms := strings.Split(part, "-")
   337  			if len(yms) != 2 {
   338  				return d, newInvalidSQLDurationError(s)
   339  			}
   340  			year, errYear := strconv.Atoi(yms[0])
   341  			var month int
   342  			var errMonth error
   343  			if yms[1] != "" {
   344  				// postgres technically supports decimals here, but it seems to be buggy
   345  				// due to the way it is parsed on their side.
   346  				// e.g. `select interval '0-2.1'` is different to select interval `'0-2.1 01:00'` --
   347  				// it seems the ".1" represents either a day or a constant, which we cannot
   348  				// replicate because we use spaces for divisors, but also seems like something
   349  				// we shouldn't sink too much time into looking at supporting.
   350  				month, errMonth = strconv.Atoi(yms[1])
   351  			}
   352  			if errYear == nil && errMonth == nil {
   353  				delta := MakeDuration(0, 0, 1).Mul(int64(year)*12 + int64(month))
   354  				if mult < 0 {
   355  					d = d.Sub(delta)
   356  				} else {
   357  					d = d.Add(delta)
   358  				}
   359  			} else {
   360  				return d, newInvalidSQLDurationError(s)
   361  			}
   362  		} else if value, err := strconv.ParseFloat(part, 64); err == nil {
   363  			// We cannot specify '<Day> <Second>'::interval as two floats,
   364  			// but we can in the DAY TO HOUR format, where it is '<Day> <Hour>'.
   365  			if floatParsed && !itm.DurationField.IsDayToHour() {
   366  				return d, newInvalidSQLDurationError(s)
   367  			}
   368  			floatParsed = true
   369  			if parsedIdx == nothingParsed {
   370  				// It must be <DurationType> part because nothing has been parsed.
   371  				switch itm.DurationField.DurationType {
   372  				case types.IntervalDurationType_YEAR:
   373  					d = d.Add(MakeDuration(0, 0, 12).MulFloat(value * float64(mult)))
   374  				case types.IntervalDurationType_MONTH:
   375  					d = d.Add(MakeDuration(0, 0, 1).MulFloat(value * float64(mult)))
   376  				case types.IntervalDurationType_DAY:
   377  					d = d.Add(MakeDuration(0, 1, 0).MulFloat(value * float64(mult)))
   378  				case types.IntervalDurationType_HOUR:
   379  					d = d.Add(MakeDuration(time.Hour.Nanoseconds(), 0, 0).MulFloat(value * float64(mult)))
   380  				case types.IntervalDurationType_MINUTE:
   381  					d = d.Add(MakeDuration(time.Minute.Nanoseconds(), 0, 0).MulFloat(value * float64(mult)))
   382  				case types.IntervalDurationType_SECOND, types.IntervalDurationType_UNSET:
   383  					d = d.Add(MakeDuration(time.Second.Nanoseconds(), 0, 0).MulFloat(value * float64(mult)))
   384  				case types.IntervalDurationType_MILLISECOND:
   385  					d = d.Add(MakeDuration(time.Millisecond.Nanoseconds(), 0, 0).MulFloat(value * float64(mult)))
   386  				default:
   387  					return d, errors.AssertionFailedf("unhandled DurationField constant %#v", itm.DurationField)
   388  				}
   389  				parsedIdx = hmsParsed
   390  			} else if parsedIdx == hmsParsed {
   391  				// Day part.
   392  				delta := MakeDuration(0, 1, 0).MulFloat(value)
   393  				if mult < 0 {
   394  					d = d.Sub(delta)
   395  				} else {
   396  					d = d.Add(delta)
   397  				}
   398  				parsedIdx = dayParsed
   399  			} else {
   400  				return d, newInvalidSQLDurationError(s)
   401  			}
   402  		} else {
   403  			return d, newInvalidSQLDurationError(s)
   404  		}
   405  	}
   406  	return d, nil
   407  }
   408  
   409  // Parses an ISO8601 (with designators) string.
   410  // See the following links for examples:
   411  //   - http://www.postgresql.org/docs/9.1/static/datatype-datetime.html#DATATYPE-INTERVAL-INPUT-EXAMPLES
   412  //   - https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
   413  //   - https://en.wikipedia.org/wiki/ISO_8601#Durations
   414  func iso8601ToDuration(s string) (Duration, error) {
   415  	var d Duration
   416  	if len(s) == 0 || s[0] != 'P' {
   417  		return d, newInvalidSQLDurationError(s)
   418  	}
   419  
   420  	// Advance to offset 1, since we don't care about the leading P.
   421  	l := intervalLexer{str: s, offset: 1, err: nil}
   422  	unitMap := isoDateUnitMap
   423  
   424  	for l.offset < len(s) {
   425  		// Check if we're in the time part yet.
   426  		if s[l.offset] == 'T' {
   427  			unitMap = isoTimeUnitMap
   428  			l.offset++
   429  		}
   430  
   431  		v, hasDecimal, vp := l.consumeNum()
   432  		u := l.consumeUnit('T')
   433  		if l.err != nil {
   434  			return d, l.err
   435  		}
   436  
   437  		if unit, ok := unitMap[u]; ok {
   438  			d = d.Add(unit.Mul(v))
   439  			if hasDecimal {
   440  				var err error
   441  				d, err = addFrac(d, unit, vp)
   442  				if err != nil {
   443  					return d, err
   444  				}
   445  			}
   446  		} else {
   447  			return d, pgerror.Newf(
   448  				pgcode.InvalidDatetimeFormat,
   449  				"interval: unknown unit %q in ISO-8601 duration %q", u, s)
   450  		}
   451  	}
   452  
   453  	return d, nil
   454  }
   455  
   456  // unitMap defines for each unit name what is the time duration for
   457  // that unit.
   458  var unitMap = func(
   459  	units map[string]Duration,
   460  	aliases map[string][]string,
   461  ) map[string]Duration {
   462  	for a, alist := range aliases {
   463  		// Pluralize.
   464  		units[a+"s"] = units[a]
   465  		for _, alias := range alist {
   466  			// Populate the remaining aliases.
   467  			units[alias] = units[a]
   468  		}
   469  	}
   470  	return units
   471  }(map[string]Duration{
   472  	// Use DecodeDuration here because ns is the only unit for which we do not
   473  	// want to round nanoseconds since it is only used for multiplication.
   474  	"microsecond": MakeDuration(time.Microsecond.Nanoseconds(), 0, 0),
   475  	"millisecond": MakeDuration(time.Millisecond.Nanoseconds(), 0, 0),
   476  	"second":      MakeDuration(time.Second.Nanoseconds(), 0, 0),
   477  	"minute":      MakeDuration(time.Minute.Nanoseconds(), 0, 0),
   478  	"hour":        MakeDuration(time.Hour.Nanoseconds(), 0, 0),
   479  	"day":         MakeDuration(0, 1, 0),
   480  	"week":        MakeDuration(0, 7, 0),
   481  	"month":       MakeDuration(0, 0, 1),
   482  	"year":        MakeDuration(0, 0, 12),
   483  }, map[string][]string{
   484  	// Include PostgreSQL's unit keywords for compatibility; see
   485  	// https://github.com/postgres/postgres/blob/a01d0fa1d889cc2003e1941e8b98707c4d701ba9/src/backend/utils/adt/datetime.c#L175-L240
   486  	//
   487  	// µ = U+00B5 = micro symbol
   488  	// μ = U+03BC = Greek letter mu
   489  	"microsecond": {"us", "µs", "μs", "usec", "usecs", "usecond", "useconds"},
   490  	"millisecond": {"ms", "msec", "msecs", "msecond", "mseconds"},
   491  	"second":      {"s", "sec", "secs"},
   492  	"minute":      {"m", "min", "mins"},
   493  	"hour":        {"h", "hr", "hrs"},
   494  	"day":         {"d"},
   495  	"week":        {"w"},
   496  	"month":       {"mon", "mons"},
   497  	"year":        {"y", "yr", "yrs"},
   498  })
   499  
   500  // parseDuration parses a duration in the "traditional" Postgres
   501  // format (e.g. '1 day 2 hours', '1 day 03:02:04', etc.) or golang
   502  // format (e.g. '1d2h', '1d3h2m4s', etc.)
   503  func parseDuration(
   504  	style IntervalStyle, s string, itm types.IntervalTypeMetadata,
   505  ) (Duration, error) {
   506  	var d Duration
   507  	l := intervalLexer{str: s, offset: 0, err: nil}
   508  	l.consumeSpaces()
   509  
   510  	if l.offset == len(l.str) {
   511  		return d, pgerror.Newf(
   512  			pgcode.InvalidDatetimeFormat, "interval: invalid input syntax: %q", l.str)
   513  	}
   514  
   515  	// If we have strictly one negative at the beginning belonging to a
   516  	// in SQL Standard parsing, treat everything as negative.
   517  	isSQLStandardNegative :=
   518  		style == IntervalStyle_SQL_STANDARD &&
   519  			(l.offset+1) < len(l.str) && l.str[l.offset] == '-' &&
   520  			!strings.ContainsAny(l.str[l.offset+1:], "+-")
   521  	if isSQLStandardNegative {
   522  		l.offset++
   523  	}
   524  
   525  	for l.offset != len(l.str) {
   526  		// To support -00:XX:XX we record the sign here since -0 doesn't exist
   527  		// as an int64.
   528  		sign := l.str[l.offset] == '-'
   529  		// Parse the next number.
   530  		v, hasDecimal, vp := l.consumeNum()
   531  		l.consumeSpaces()
   532  
   533  		if l.offset < len(l.str) && l.str[l.offset] == ':' && !hasDecimal {
   534  			// Special case: HH:MM[:SS.ffff] or MM:SS.ffff
   535  			delta, err := l.parseShortDuration(v, sign, itm)
   536  			if err != nil {
   537  				return d, err
   538  			}
   539  			d = d.Add(delta)
   540  			continue
   541  		}
   542  
   543  		// Parse the unit.
   544  		u := l.consumeUnit(' ')
   545  		l.consumeSpaces()
   546  		if unit, ok := unitMap[strings.ToLower(u)]; ok {
   547  			// A regular number followed by a unit, such as "9 day".
   548  			d = d.Add(unit.Mul(v))
   549  			if hasDecimal {
   550  				var err error
   551  				d, err = addFrac(d, unit, vp)
   552  				if err != nil {
   553  					return d, err
   554  				}
   555  			}
   556  			continue
   557  		}
   558  
   559  		if l.err != nil {
   560  			return d, l.err
   561  		}
   562  		if u != "" {
   563  			return d, pgerror.Newf(
   564  				pgcode.InvalidDatetimeFormat, "interval: unknown unit %q in duration %q", u, s)
   565  		}
   566  		return d, pgerror.Newf(
   567  			pgcode.InvalidDatetimeFormat, "interval: missing unit at position %d: %q", l.offset, s)
   568  	}
   569  	if isSQLStandardNegative {
   570  		return MakeDuration(
   571  			-d.Nanos(),
   572  			-d.Days,
   573  			-d.Months,
   574  		), l.err
   575  	}
   576  	return d, l.err
   577  }
   578  
   579  func (l *intervalLexer) parseShortDuration(
   580  	h int64, hasSign bool, itm types.IntervalTypeMetadata,
   581  ) (Duration, error) {
   582  	sign := int64(1)
   583  	if hasSign {
   584  		sign = -1
   585  	}
   586  	// postgresToDuration() has rewound the cursor to just after the
   587  	// first number, so that we can check here there are no unwanted
   588  	// spaces.
   589  	if l.str[l.offset] != ':' {
   590  		return Duration{}, pgerror.Newf(
   591  			pgcode.InvalidDatetimeFormat, "interval: invalid format %s", l.str[l.offset:])
   592  	}
   593  	l.offset++
   594  	// Parse the second number.
   595  	m, hasDecimal, mp := l.consumeNum()
   596  
   597  	if m < 0 {
   598  		return Duration{}, pgerror.Newf(
   599  			pgcode.InvalidDatetimeFormat, "interval: invalid format: %s", l.str)
   600  	}
   601  	// We have three possible formats:
   602  	// - MM:SS.ffffff
   603  	// - HH:MM (or MM:SS for MINUTE TO SECOND)
   604  	// - HH:MM:SS[.ffffff]
   605  	//
   606  	// The top format has the "h" field parsed above actually
   607  	// represent minutes. Get this out of the way first.
   608  	if hasDecimal {
   609  		l.consumeSpaces()
   610  		return MakeDuration(
   611  			h*time.Minute.Nanoseconds()+
   612  				sign*(m*time.Second.Nanoseconds()+
   613  					floatToNanos(mp)),
   614  			0,
   615  			0,
   616  		), nil
   617  	}
   618  
   619  	// Remaining formats.
   620  	var s int64
   621  	var sp float64
   622  	hasSecondsComponent := false
   623  	if l.offset != len(l.str) && l.str[l.offset] == ':' {
   624  		hasSecondsComponent = true
   625  		// The last :NN part.
   626  		l.offset++
   627  		s, _, sp = l.consumeNum()
   628  		if s < 0 {
   629  			return Duration{}, pgerror.Newf(
   630  				pgcode.InvalidDatetimeFormat, "interval: invalid format: %s", l.str)
   631  		}
   632  	}
   633  
   634  	l.consumeSpaces()
   635  
   636  	if !hasSecondsComponent && itm.DurationField.IsMinuteToSecond() {
   637  		return MakeDuration(
   638  			h*time.Minute.Nanoseconds()+sign*(m*time.Second.Nanoseconds()),
   639  			0,
   640  			0,
   641  		), nil
   642  	}
   643  	return MakeDuration(
   644  		h*time.Hour.Nanoseconds()+
   645  			sign*(m*time.Minute.Nanoseconds()+
   646  				int64(mp*float64(time.Minute.Nanoseconds()))+
   647  				s*time.Second.Nanoseconds()+
   648  				floatToNanos(sp)),
   649  		0,
   650  		0,
   651  	), nil
   652  }
   653  
   654  // addFrac increases the duration given as first argument by the unit
   655  // given as second argument multiplied by the factor in the third
   656  // argument. For computing fractions there are 30 days to a month and
   657  // 24 hours to a day.
   658  func addFrac(d Duration, unit Duration, f float64) (Duration, error) {
   659  	if unit.Months > 0 {
   660  		f = f * float64(unit.Months)
   661  		d.Months += int64(f)
   662  		switch unit.Months {
   663  		case 1:
   664  			f = math.Mod(f, 1) * 30
   665  			d.Days += int64(f)
   666  			f = math.Mod(f, 1) * 24
   667  			d.SetNanos(d.Nanos() + int64(float64(time.Hour.Nanoseconds())*f))
   668  		case 12:
   669  			// Nothing to do: Postgres limits the precision of fractional years to
   670  			// months. Do not continue to add precision to the interval.
   671  			// See issue #55226 for more details on this.
   672  		default:
   673  			return Duration{}, errors.AssertionFailedf("unhandled unit type %v", unit)
   674  		}
   675  	} else if unit.Days > 0 {
   676  		f = f * float64(unit.Days)
   677  		d.Days += int64(f)
   678  		f = math.Mod(f, 1) * 24
   679  		d.SetNanos(d.Nanos() + int64(float64(time.Hour.Nanoseconds())*f))
   680  	} else {
   681  		d.SetNanos(d.Nanos() + int64(float64(unit.Nanos())*f))
   682  	}
   683  	return d, nil
   684  }
   685  
   686  // floatToNanos converts a fractional number representing nanoseconds to the
   687  // number of integer nanoseconds. For example: ".354874219" to "354874219"
   688  // or ".123" to "123000000". This function takes care to round correctly
   689  // when a naive conversion would incorrectly truncate due to floating point
   690  // inaccuracies. This function should match the semantics of rint() from
   691  // Postgres. See:
   692  // https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/adt/timestamp.c;h=449164ae7e5b00f6580771017888d4922685a73c;hb=HEAD#l1511
   693  // https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/port/rint.c;h=d59d9ab774307b7db2f7cb2347815a30da563fc5;hb=HEAD
   694  func floatToNanos(f float64) int64 {
   695  	return int64(math.Round(f * float64(time.Second.Nanoseconds())))
   696  }