github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/constraint/constraint.go (about) 1 // Copyright 2018 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 constraint 12 13 import ( 14 "strings" 15 16 "github.com/cockroachdb/cockroach/pkg/sql/opt" 17 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 18 "github.com/cockroachdb/cockroach/pkg/sql/types" 19 "github.com/cockroachdb/errors" 20 ) 21 22 // Constraint specifies the possible set of values that one or more columns 23 // will have in the result set. If this is a single column constraint, then 24 // that column's value will always be part of one of the spans in this 25 // constraint. If this is a multi-column constraint, then the combination of 26 // column values will always be part of one of the spans. 27 // 28 // Constrained columns are specified as an ordered list, and span key values 29 // correspond to those columns by position. Constraints are inferred from 30 // scalar filter conditions, since these restrict the set of possible results. 31 // Constraints over different combinations of columns can be combined together 32 // in a constraint set, which is a conjunction of constraints. See the 33 // Set struct comment for more details. 34 // 35 // A few examples: 36 // - a constraint on @1 > 1: a single span /@1: (/1 - ] 37 // - a constraint on @1 = 1 AND @2 >= 1: a single span /@1/@2: [/1/1 - /1] 38 // - a constraint on @1 < 5 OR @1 > 10: multiple spans /@1: [ - /5) (10 - ] 39 type Constraint struct { 40 Columns Columns 41 42 // Spans contains the spans in this constraint. The spans are always ordered 43 // and non-overlapping. 44 Spans Spans 45 } 46 47 // Init initializes the constraint to the columns in the key context and with 48 // the given spans. 49 func (c *Constraint) Init(keyCtx *KeyContext, spans *Spans) { 50 for i := 1; i < spans.Count(); i++ { 51 if !spans.Get(i).StartsStrictlyAfter(keyCtx, spans.Get(i-1)) { 52 panic(errors.AssertionFailedf("spans must be ordered and non-overlapping")) 53 } 54 } 55 c.Columns = keyCtx.Columns 56 c.Spans = *spans 57 c.Spans.makeImmutable() 58 } 59 60 // InitSingleSpan initializes the constraint to the columns in the key context 61 // and with one span. 62 func (c *Constraint) InitSingleSpan(keyCtx *KeyContext, span *Span) { 63 c.Columns = keyCtx.Columns 64 c.Spans.InitSingleSpan(span) 65 } 66 67 // IsContradiction returns true if there are no spans in the constraint. 68 func (c *Constraint) IsContradiction() bool { 69 return c.Spans.Count() == 0 70 } 71 72 // IsUnconstrained returns true if the constraint contains an unconstrained 73 // span. 74 func (c *Constraint) IsUnconstrained() bool { 75 return c.Spans.Count() == 1 && c.Spans.Get(0).IsUnconstrained() 76 } 77 78 // UnionWith merges the spans of the given constraint into this constraint. The 79 // columns of both constraints must be the same. Constrained columns in the 80 // merged constraint can have values that are part of either of the input 81 // constraints. 82 func (c *Constraint) UnionWith(evalCtx *tree.EvalContext, other *Constraint) { 83 if !c.Columns.Equals(&other.Columns) { 84 panic(errors.AssertionFailedf("column mismatch")) 85 } 86 if c.IsUnconstrained() || other.IsContradiction() { 87 return 88 } 89 90 // Use variation on merge sort, because both sets of spans are ordered and 91 // non-overlapping. 92 93 left := &c.Spans 94 leftIndex := 0 95 right := &other.Spans 96 rightIndex := 0 97 keyCtx := MakeKeyContext(&c.Columns, evalCtx) 98 var result Spans 99 result.Alloc(left.Count() + right.Count()) 100 101 for leftIndex < left.Count() || rightIndex < right.Count() { 102 if rightIndex < right.Count() { 103 if leftIndex >= left.Count() || 104 left.Get(leftIndex).Compare(&keyCtx, right.Get(rightIndex)) > 0 { 105 // Swap the two sets, so that going forward the current left 106 // span starts before the current right span. 107 left, right = right, left 108 leftIndex, rightIndex = rightIndex, leftIndex 109 } 110 } 111 112 // Merge this span with any overlapping spans in left or right. Initially, 113 // it can only overlap with spans in right, but after the merge we can 114 // have new overlaps; hence why this is a loop and we check against both 115 // left and right. For example: 116 // left : [/1 - /10] [/20 - /30] [/40 - /50] 117 // right: [/5 - /25] [/30 - /40] 118 // span 119 // initial: [/1 - /10] 120 // merge with [/5 - /25]: [/1 - /25] 121 // merge with [/20 - /30]: [/1 - /30] 122 // merge with [/30 - /40]: [/1 - /40] 123 // merge with [/40 - /50]: [/1 - /50] 124 // 125 mergeSpan := *left.Get(leftIndex) 126 leftIndex++ 127 for { 128 // Note that Span.TryUnionWith returns false for a different reason 129 // than Constraint.tryUnionWith. Span.TryUnionWith returns false 130 // when the spans are not contiguous, and therefore the union cannot 131 // be represented as a valid Span. Constraint.tryUnionWith returns 132 // false when the merged spans are unconstrained (cover entire key 133 // range), and therefore the union cannot be represented as a valid 134 // Constraint. 135 var ok bool 136 if leftIndex < left.Count() { 137 if mergeSpan.TryUnionWith(&keyCtx, left.Get(leftIndex)) { 138 leftIndex++ 139 ok = true 140 } 141 } 142 if rightIndex < right.Count() { 143 if mergeSpan.TryUnionWith(&keyCtx, right.Get(rightIndex)) { 144 rightIndex++ 145 ok = true 146 } 147 } 148 149 // If neither union succeeded, then it means either: 150 // 1. The spans don't merge into a single contiguous span, and will 151 // need to be represented separately in this constraint. 152 // 2. There are no more spans to merge. 153 if !ok { 154 break 155 } 156 } 157 result.Append(&mergeSpan) 158 } 159 160 c.Spans = result 161 c.Spans.makeImmutable() 162 } 163 164 // IntersectWith intersects the spans of this constraint with those in the 165 // given constraint and updates this constraint with any overlapping spans. The 166 // columns of both constraints must be the same. If there are no overlapping 167 // spans, then the intersection is empty, and tryIntersectWith returns false. 168 // If a constraint set has even one empty constraint, then the entire set 169 // should be marked as empty and all constraints removed. 170 func (c *Constraint) IntersectWith(evalCtx *tree.EvalContext, other *Constraint) { 171 if !c.Columns.Equals(&other.Columns) { 172 panic(errors.AssertionFailedf("column mismatch")) 173 } 174 if c.IsContradiction() || other.IsUnconstrained() { 175 return 176 } 177 178 // Use variation on merge sort, because both sets of spans are ordered and 179 // non-overlapping. 180 181 left := &c.Spans 182 leftIndex := 0 183 right := &other.Spans 184 rightIndex := 0 185 keyCtx := MakeKeyContext(&c.Columns, evalCtx) 186 var result Spans 187 result.Alloc(left.Count()) 188 189 for leftIndex < left.Count() && rightIndex < right.Count() { 190 if left.Get(leftIndex).StartsAfter(&keyCtx, right.Get(rightIndex)) { 191 rightIndex++ 192 continue 193 } 194 195 mergeSpan := *left.Get(leftIndex) 196 if !mergeSpan.TryIntersectWith(&keyCtx, right.Get(rightIndex)) { 197 leftIndex++ 198 continue 199 } 200 result.Append(&mergeSpan) 201 202 // Skip past whichever span ends first, or skip past both if they have 203 // the same endpoint. 204 cmp := left.Get(leftIndex).CompareEnds(&keyCtx, right.Get(rightIndex)) 205 if cmp <= 0 { 206 leftIndex++ 207 } 208 if cmp >= 0 { 209 rightIndex++ 210 } 211 } 212 213 c.Spans = result 214 c.Spans.makeImmutable() 215 } 216 217 func (c Constraint) String() string { 218 var b strings.Builder 219 b.WriteString(c.Columns.String()) 220 b.WriteString(": ") 221 if c.IsUnconstrained() { 222 b.WriteString("unconstrained") 223 } else if c.IsContradiction() { 224 b.WriteString("contradiction") 225 } else { 226 b.WriteString(c.Spans.String()) 227 } 228 return b.String() 229 } 230 231 // ContainsSpan returns true if the constraint contains the given span (or a 232 // span that contains it). 233 func (c *Constraint) ContainsSpan(evalCtx *tree.EvalContext, sp *Span) bool { 234 keyCtx := MakeKeyContext(&c.Columns, evalCtx) 235 // Binary search to find an overlapping span. 236 for l, r := 0, c.Spans.Count()-1; l <= r; { 237 m := (l + r) / 2 238 cSpan := c.Spans.Get(m) 239 if sp.StartsAfter(&keyCtx, cSpan) { 240 l = m + 1 241 } else if cSpan.StartsAfter(&keyCtx, sp) { 242 r = m - 1 243 } else { 244 // The spans must overlap. Check if sp is fully contained. 245 return sp.CompareStarts(&keyCtx, cSpan) >= 0 && 246 sp.CompareEnds(&keyCtx, cSpan) <= 0 247 } 248 } 249 return false 250 } 251 252 // Combine refines the receiver constraint using constraints on a suffix of the 253 // same list of columns. For example: 254 // c: /a/b: [/1 - /2] [/4 - /4] 255 // other: /b: [/5 - /5] 256 // result: /a/b: [/1/5 - /2/5] [/4/5 - /4/5] 257 func (c *Constraint) Combine(evalCtx *tree.EvalContext, other *Constraint) { 258 if !other.Columns.IsStrictSuffixOf(&c.Columns) { 259 // Note: we don't want to let the c and other pointers escape by passing 260 // them directly to Sprintf. 261 panic(errors.AssertionFailedf("%s not a suffix of %s", other.String(), c.String())) 262 } 263 if c.IsUnconstrained() || c.IsContradiction() || other.IsUnconstrained() { 264 return 265 } 266 if other.IsContradiction() { 267 c.Spans = Spans{} 268 c.Spans.makeImmutable() 269 return 270 } 271 offset := c.Columns.Count() - other.Columns.Count() 272 273 var result Spans 274 // We only initialize result if we determine that we need to modify the list 275 // of spans. 276 var resultInitialized bool 277 keyCtx := KeyContext{Columns: c.Columns, EvalCtx: evalCtx} 278 279 for i := 0; i < c.Spans.Count(); i++ { 280 sp := *c.Spans.Get(i) 281 282 startLen, endLen := sp.start.Length(), sp.end.Length() 283 284 // Try to extend the start and end keys. 285 // TODO(radu): we could look at offsets in between 0 and startLen/endLen and 286 // potentially tighten up the spans (e.g. (a, b) > (1, 2) AND b > 10). 287 288 if startLen == endLen && startLen == offset && 289 sp.start.Compare(&keyCtx, sp.end, ExtendLow, ExtendLow) == 0 { 290 // Special case when the start and end keys are equal (i.e. an exact value 291 // on this column). This is the only case where we can break up a single 292 // span into multiple spans. Note that this implies inclusive boundaries. 293 // 294 // For example: 295 // @1 = 1 AND @2 IN (3, 4, 5) 296 // constraint[0]: 297 // [/1 - /1] 298 // constraint[1]: 299 // [/3 - /3] 300 // [/4 - /4] 301 // [/5 - /5] 302 // We break up the span to get: 303 // [/1/3 - /1/3] 304 // [/1/4 - /1/4] 305 // [/1/5 - /1/5] 306 307 if !resultInitialized { 308 resultInitialized = true 309 result.Alloc(c.Spans.Count() + other.Spans.Count()) 310 for j := 0; j < i; j++ { 311 result.Append(c.Spans.Get(j)) 312 } 313 } 314 for j := 0; j < other.Spans.Count(); j++ { 315 extSp := other.Spans.Get(j) 316 var newSp Span 317 newSp.Init( 318 sp.start.Concat(extSp.start), extSp.startBoundary, 319 sp.end.Concat(extSp.end), extSp.endBoundary, 320 ) 321 result.Append(&newSp) 322 } 323 continue 324 } 325 326 var modified bool 327 if startLen == offset && sp.startBoundary == IncludeBoundary { 328 // We can advance the starting boundary. Calculate constraints for the 329 // column that follows. If we have multiple constraints, we can only use 330 // the start of the first one to tighten the span. 331 // For example: 332 // @1 >= 2 AND @2 IN (1, 2, 3). 333 // constraints[0]: 334 // [/2 - ] 335 // constraints[1]: 336 // [/1 - /1] 337 // [/2 - /2] 338 // [/3 - /3] 339 // The best we can do is tighten the span to: 340 // [/2/1 - ] 341 extSp := other.Spans.Get(0) 342 if extSp.start.Length() > 0 { 343 sp.start = sp.start.Concat(extSp.start) 344 sp.startBoundary = extSp.startBoundary 345 modified = true 346 } 347 } 348 // End key case is symmetric with the one above. 349 if endLen == offset && sp.endBoundary == IncludeBoundary { 350 extSp := other.Spans.Get(other.Spans.Count() - 1) 351 if extSp.end.Length() > 0 { 352 sp.end = sp.end.Concat(extSp.end) 353 sp.endBoundary = extSp.endBoundary 354 modified = true 355 } 356 } 357 if modified { 358 // We only initialize result if we need to modify the list of spans. 359 if !resultInitialized { 360 resultInitialized = true 361 result.Alloc(c.Spans.Count()) 362 for j := 0; j < i; j++ { 363 result.Append(c.Spans.Get(j)) 364 } 365 } 366 // The span can become invalid (empty). For example: 367 // /1/2: [/1 - /1/2] 368 // /2: [/5 - /5] 369 // This results in an invalid span [/1/5 - /1/2] which we must discard. 370 if sp.start.Compare(&keyCtx, sp.end, sp.startExt(), sp.endExt()) < 0 { 371 result.Append(&sp) 372 } 373 } else { 374 if resultInitialized { 375 result.Append(&sp) 376 } 377 } 378 } 379 if resultInitialized { 380 c.Spans = result 381 c.Spans.makeImmutable() 382 } 383 } 384 385 // ConsolidateSpans merges spans that have consecutive boundaries. For example: 386 // [/1 - /2] [/3 - /4] becomes [/1 - /4]. 387 func (c *Constraint) ConsolidateSpans(evalCtx *tree.EvalContext) { 388 keyCtx := KeyContext{Columns: c.Columns, EvalCtx: evalCtx} 389 var result Spans 390 for i := 1; i < c.Spans.Count(); i++ { 391 last := c.Spans.Get(i - 1) 392 sp := c.Spans.Get(i) 393 if last.endBoundary == IncludeBoundary && sp.startBoundary == IncludeBoundary && 394 sp.start.IsNextKey(&keyCtx, last.end) { 395 // We only initialize `result` if we need to change something. 396 if result.Count() == 0 { 397 result.Alloc(c.Spans.Count() - 1) 398 for j := 0; j < i; j++ { 399 result.Append(c.Spans.Get(j)) 400 } 401 } 402 r := result.Get(result.Count() - 1) 403 r.end = sp.end 404 r.endBoundary = sp.endBoundary 405 } else { 406 if result.Count() != 0 { 407 result.Append(sp) 408 } 409 } 410 } 411 if result.Count() != 0 { 412 c.Spans = result 413 c.Spans.makeImmutable() 414 } 415 } 416 417 // ExactPrefix returns the length of the longest column prefix which are 418 // constrained to a single value. For example: 419 // /a/b/c: [/1/2/3 - /1/2/3] -> ExactPrefix = 3 420 // /a/b/c: [/1/2/3 - /1/2/3] [/1/2/5 - /1/2/8] -> ExactPrefix = 2 421 // /a/b/c: [/1/2/3 - /1/2/3] [/1/2/5 - /1/3/8] -> ExactPrefix = 1 422 // /a/b/c: [/1/2/3 - /1/2/3] [/1/3/3 - /1/3/3] -> ExactPrefix = 1 423 // /a/b/c: [/1/2/3 - /1/2/3] [/3 - /4] -> ExactPrefix = 0 424 func (c *Constraint) ExactPrefix(evalCtx *tree.EvalContext) int { 425 if c.IsContradiction() { 426 return 0 427 } 428 429 for col := 0; ; col++ { 430 // Check if all spans have the same value for this column. 431 var val tree.Datum 432 for i := 0; i < c.Spans.Count(); i++ { 433 sp := c.Spans.Get(i) 434 if sp.start.Length() <= col || sp.end.Length() <= col { 435 return col 436 } 437 startVal := sp.start.Value(col) 438 if startVal.Compare(evalCtx, sp.end.Value(col)) != 0 { 439 return col 440 } 441 if i == 0 { 442 val = startVal 443 } else if startVal.Compare(evalCtx, val) != 0 { 444 return col 445 } 446 } 447 } 448 } 449 450 // ConstrainedColumns returns the number of columns which are constrained by 451 // the Constraint. For example: 452 // /a/b/c: [/1/1 - /1] [/3 - /3] 453 // has 2 constrained columns. This may be less than the total number of columns 454 // in the constraint, especially if it represents an index constraint. 455 func (c *Constraint) ConstrainedColumns(evalCtx *tree.EvalContext) int { 456 count := 0 457 for i := 0; i < c.Spans.Count(); i++ { 458 sp := c.Spans.Get(i) 459 start := sp.StartKey() 460 end := sp.EndKey() 461 if start.Length() > count { 462 count = start.Length() 463 } 464 if end.Length() > count { 465 count = end.Length() 466 } 467 } 468 469 return count 470 } 471 472 // Prefix returns the length of the longest prefix of columns for which all the 473 // spans have the same start and end values. For example: 474 // /a/b/c: [/1/1/1 - /1/1/2] [/3/3/3 - /3/3/4] 475 // has prefix 2. 476 // 477 // Note that Prefix returns a value that is greater than or equal to the value 478 // returned by ExactPrefix. For example: 479 // /a/b/c: [/1/2/3 - /1/2/3] [/1/2/5 - /1/3/8] -> ExactPrefix = 1, Prefix = 1 480 // /a/b/c: [/1/2/3 - /1/2/3] [/1/3/3 - /1/3/3] -> ExactPrefix = 1, Prefix = 3 481 func (c *Constraint) Prefix(evalCtx *tree.EvalContext) int { 482 prefix := 0 483 for ; prefix < c.Columns.Count(); prefix++ { 484 for i := 0; i < c.Spans.Count(); i++ { 485 sp := c.Spans.Get(i) 486 start := sp.StartKey() 487 end := sp.EndKey() 488 if start.Length() <= prefix || end.Length() <= prefix || 489 start.Value(prefix).Compare(evalCtx, end.Value(prefix)) != 0 { 490 return prefix 491 } 492 } 493 } 494 495 return prefix 496 } 497 498 // ExtractConstCols returns a set of columns which are restricted to be 499 // constant by the constraint. 500 func (c *Constraint) ExtractConstCols(evalCtx *tree.EvalContext) opt.ColSet { 501 var res opt.ColSet 502 pre := c.ExactPrefix(evalCtx) 503 for i := 0; i < pre; i++ { 504 res.Add(c.Columns.Get(i).ID()) 505 } 506 return res 507 } 508 509 // ExtractNotNullCols returns a set of columns that cannot be NULL when the 510 // constraint holds. 511 func (c *Constraint) ExtractNotNullCols(evalCtx *tree.EvalContext) opt.ColSet { 512 if c.IsUnconstrained() || c.IsContradiction() { 513 return opt.ColSet{} 514 } 515 516 var res opt.ColSet 517 518 // If we have a span where the start and end key value diverge for a column, 519 // none of the columns that follow can be not-null. For example: 520 // /1/2/3: [/1/2/3 - /1/4/1] 521 // Because the span is not restricted to a single value on column 2, column 3 522 // can take any value, like /1/3/NULL. 523 // 524 // Find the longest prefix of columns for which all the spans have the same 525 // start and end values. For example: 526 // [/1/1/1 - /1/1/2] [/3/3/3 - /3/3/4] 527 // has prefix 2. Only these columns and the first following column can be 528 // known to be not-null. 529 prefix := c.Prefix(evalCtx) 530 for i := 0; i < prefix; i++ { 531 // hasNull identifies cases like [/1/NULL/1 - /1/NULL/2]. 532 hasNull := false 533 for j := 0; j < c.Spans.Count(); j++ { 534 start := c.Spans.Get(j).StartKey() 535 hasNull = hasNull || start.Value(i) == tree.DNull 536 } 537 if !hasNull { 538 res.Add(c.Columns.Get(i).ID()) 539 } 540 } 541 if prefix == c.Columns.Count() { 542 return res 543 } 544 545 // Now look at the first column that follows the prefix. 546 col := c.Columns.Get(prefix) 547 for i := 0; i < c.Spans.Count(); i++ { 548 span := c.Spans.Get(i) 549 var key Key 550 var boundary SpanBoundary 551 if !col.Descending() { 552 key, boundary = span.StartKey(), span.StartBoundary() 553 } else { 554 key, boundary = span.EndKey(), span.EndBoundary() 555 } 556 // If the span is unbounded on the NULL side, or if it is of the form 557 // [/NULL - /x], the column is nullable. 558 if key.Length() <= prefix || (key.Value(prefix) == tree.DNull && boundary == IncludeBoundary) { 559 return res 560 } 561 } 562 // All spans constrain col to be not-null. 563 res.Add(col.ID()) 564 return res 565 } 566 567 // CalculateMaxResults returns a non-zero integer indicating the maximum number 568 // of results that can be read from indexCols by using c.Spans. The indexCols 569 // are assumed to form at least a weak key. 570 // If 0 is returned, the maximum number of results could not be deduced. 571 // We can calculate the maximum number of results when both of the following 572 // are satisfied: 573 // 1. The index columns form a weak key (assumption), and the spans do not 574 // specify any nulls. 575 // 2. All spans cover all the columns of the index and have equal start and 576 // end keys up to but not necessarily including the last column. 577 // TODO(asubiotto): The only reason to extract this is that both the heuristic 578 // planner and optimizer need this logic, due to the heuristic planner planning 579 // mutations. Once the optimizer plans mutations, this method can go away. 580 func (c *Constraint) CalculateMaxResults( 581 evalCtx *tree.EvalContext, indexCols opt.ColSet, notNullCols opt.ColSet, 582 ) uint64 { 583 // Ensure that if we have nullable columns, we are only reading non-null 584 // values, given that a unique index allows an arbitrary number of duplicate 585 // entries if they have NULLs. 586 if !indexCols.SubsetOf(notNullCols.Union(c.ExtractNotNullCols(evalCtx))) { 587 return 0 588 } 589 590 numCols := c.Columns.Count() 591 592 // Check if the longest prefix of columns for which all the spans have the 593 // same start and end values covers all columns. 594 prefix := c.Prefix(evalCtx) 595 var distinctVals uint64 596 if prefix < numCols-1 { 597 return 0 598 } else if prefix == numCols-1 { 599 // If the prefix does not include the last column, calculate the number of 600 // distinct values possible in the span. This is only supported for int 601 // and date types. 602 for i := 0; i < c.Spans.Count(); i++ { 603 sp := c.Spans.Get(i) 604 start := sp.StartKey() 605 end := sp.EndKey() 606 607 // Ensure that the keys specify the last column. 608 if start.Length() != numCols || end.Length() != numCols { 609 return 0 610 } 611 612 // TODO(asubiotto): This logic is very similar to 613 // updateDistinctCountsFromConstraint. It would be nice to extract this 614 // logic somewhere. 615 colIdx := numCols - 1 616 startVal := start.Value(colIdx) 617 endVal := end.Value(colIdx) 618 var startIntVal, endIntVal int64 619 if startVal.ResolvedType().Family() == types.IntFamily && 620 endVal.ResolvedType().Family() == types.IntFamily { 621 startIntVal = int64(*startVal.(*tree.DInt)) 622 endIntVal = int64(*endVal.(*tree.DInt)) 623 } else if startVal.ResolvedType().Family() == types.DateFamily && 624 endVal.ResolvedType().Family() == types.DateFamily { 625 startDate := startVal.(*tree.DDate) 626 endDate := endVal.(*tree.DDate) 627 if !startDate.IsFinite() || !endDate.IsFinite() { 628 // One of the boundaries is not finite, so we can't determine the 629 // distinct count for this column. 630 return 0 631 } 632 startIntVal = int64(startDate.PGEpochDays()) 633 endIntVal = int64(endDate.PGEpochDays()) 634 } else { 635 return 0 636 } 637 638 if c.Columns.Get(colIdx).Ascending() { 639 distinctVals += uint64(endIntVal - startIntVal) 640 } else { 641 distinctVals += uint64(startIntVal - endIntVal) 642 } 643 644 // Add one since both start and end boundaries should be inclusive 645 // (due to Span.PreferInclusive). 646 distinctVals++ 647 } 648 } else { 649 distinctVals = uint64(c.Spans.Count()) 650 } 651 return distinctVals 652 }