From c8c5d84835523ea3a5cee237ee096438776b2f39 Mon Sep 17 00:00:00 2001 From: Wang Yanqing Date: Tue, 28 Aug 2018 18:08:39 +0800 Subject: [PATCH] Add support for Postgres Range types. Closes #27791 --- pkg/sql/coltypes/aliases.go | 11 +++++++ pkg/sql/coltypes/ranges.go | 46 ++++++++++++++++++++++++++++ pkg/sql/parser/sql.y | 27 +++++++++++++++++ pkg/sql/pg_catalog.go | 3 +- pkg/sql/sem/tree/datum.go | 55 ++++++++++++++++++++++++++++++++++ pkg/sql/sem/tree/eval.go | 5 ++++ pkg/sql/sem/tree/expr.go | 1 + pkg/sql/sem/tree/type_check.go | 4 +++ pkg/sql/sem/tree/walk.go | 3 ++ pkg/sql/sem/types/oid.go | 15 ++++++++++ pkg/sql/sem/types/types.go | 42 ++++++++++++++++++++++++++ pkg/sql/sqlbase/datum_alloc.go | 13 ++++++++ 12 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 pkg/sql/coltypes/ranges.go diff --git a/pkg/sql/coltypes/aliases.go b/pkg/sql/coltypes/aliases.go index 2f44fa4cf407..bfd3880d724c 100644 --- a/pkg/sql/coltypes/aliases.go +++ b/pkg/sql/coltypes/aliases.go @@ -94,6 +94,17 @@ var ( // JSON is an immutable T instance. JSON = &TJSON{} + // INT4RANGE is an immutable T instance. + Int4Range = &TRange{Int4} + // INT8RANGE is an immutable T instance. + Int8Range = &TRange{Int8} + // NUMRANGE is an immutable T instance. + NumRange = &TRange{Decimal} + // INT4RANGE is an immutable T instance. + TSRange = &TRange{Timestamp} + // INT4RANGE is an immutable T instance. + TSTZRange = &TRange{TimestampWithTZ} + // Oid is an immutable T instance. Oid = &TOid{Name: "OID"} // RegClass is an immutable T instance. diff --git a/pkg/sql/coltypes/ranges.go b/pkg/sql/coltypes/ranges.go new file mode 100644 index 000000000000..249d66def587 --- /dev/null +++ b/pkg/sql/coltypes/ranges.go @@ -0,0 +1,46 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +package coltypes + +import ( + "bytes" + "github.com/cockroachdb/cockroach/pkg/sql/lex" +) + +// TRange represents a XXXRANGE column type. +type TRange struct { + ParamType T +} + +// TypeName implements the ColTypeFormatter interface. +func (node *TRange) TypeName() string { + return node.ParamType.TypeName() + "RANGE" +} + +// Format implements the ColTypeFormatter interface. +func (node *TRange) Format(buf *bytes.Buffer, f lex.EncodeFlags) { + buf.WriteString(node.TypeName()) +} + +func canBeInRangeColType(t T) bool { + // TODO: Any column type with b-tree operator can be customized with range col type, + // TODO: But for now we just impl the builtin ones. + switch t.(type) { + case *TInt, *TDecimal, *TTimestamp, *TTimestampTZ: + return true + default: + return false + } +} diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 381908ea3dd3..5eee622ce247 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -6297,6 +6297,28 @@ interval_second: } | SECOND '(' ICONST ')' { return unimplemented(sqllex, "interval_second") } +range_types: + INT4RANGE + { + $$.val = coltypes.Int4Range + } +| INT8RANGE + { + $$.val = coltypes.Int8Range + } +| NUMRANGE + { + $$.val = coltypes.NumRange + } +| TSRANGE + { + $$.val = coltypes.TSRange + } +| TSTZRANGE + { + $$.val = coltypes.TSTZRange + } + // General expressions. This is the heart of the expression syntax. // // We have two expression types: a_expr is the unrestricted kind, and b_expr is @@ -8397,12 +8419,15 @@ col_name_keyword: | IFERROR | IFNULL | INT +| INT4RANGE +| INT8RANGE | INTEGER | INTERVAL | ISERROR | LEAST | NULLIF | NUMERIC +| NUMRANGE | OUT | OVERLAY | POSITION @@ -8414,6 +8439,8 @@ col_name_keyword: | TIME | TIMETZ | TIMESTAMP +| TSRANGE +| TSTZRANGE | TREAT | TRIM | VALUES diff --git a/pkg/sql/pg_catalog.go b/pkg/sql/pg_catalog.go index e56c9a8a8f17..499116b3f57a 100644 --- a/pkg/sql/pg_catalog.go +++ b/pkg/sql/pg_catalog.go @@ -1775,7 +1775,6 @@ var ( _ = typCategoryComposite _ = typCategoryEnum _ = typCategoryGeometric - _ = typCategoryRange _ = typCategoryBitString _ = typCategoryUnknown @@ -2076,6 +2075,8 @@ func typCategory(typ types.T) tree.Datum { return typCategoryPseudo } return typCategoryArray + } else if typ.FamilyEqual(types.FamRange) { + return typCategoryRange } return datumToTypeCategory[reflect.TypeOf(types.UnwrapType(typ))] } diff --git a/pkg/sql/sem/tree/datum.go b/pkg/sql/sem/tree/datum.go index d88983a5df87..0502c0dafee0 100644 --- a/pkg/sql/sem/tree/datum.go +++ b/pkg/sql/sem/tree/datum.go @@ -3005,6 +3005,61 @@ func (d *DArray) Append(v Datum) error { return d.Validate() } +type DRange struct { + ParamTyp types.T + + Lower Datum + Upper Datum + + BlanketType int +} + +func (d *DRange) AmbiguousFormat() bool { + return false +} + +func (d *DRange) Compare(ctx *EvalContext, other Datum) int { + return 1 +} + +func (d *DRange) Prev(ctx *EvalContext) (Datum, bool) { + return nil, false +} + +func (d *DRange) IsMin(ctx *EvalContext) bool { + return false +} + +func (d *DRange) Next(ctx *EvalContext) (Datum, bool) { + return nil, false +} + +func (d *DRange) IsMax(ctx *EvalContext) bool { + return false +} + +func (d *DRange) Max(ctx *EvalContext) (Datum, bool) { + return nil, false +} + +func (d *DRange) Min(ctx *EvalContext) (Datum, bool) { + return nil, false +} + +func (d *DRange) Size() uintptr { + return unsafe.Sizeof(*d) +} + +// ResolvedType implements the TypedExpr interface. +func (d *DRange) ResolvedType() types.T { + return types.TRange{Typ: d.ParamTyp} +} + +// Format implements the NodeFormatter interface +func (d *DRange) Format(ctx *FmtCtx) { + +} + // DOid is the Postgres OID datum. It can represent either an OID type or any // of the reg* types, such as regproc or regclass. type DOid struct { diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go index bdcbde80722e..c182edcf3d17 100644 --- a/pkg/sql/sem/tree/eval.go +++ b/pkg/sql/sem/tree/eval.go @@ -3782,6 +3782,11 @@ func (t *DOid) Eval(_ *EvalContext) (Datum, error) { return t, nil } +// Eval implements the TypedExpr interface. +func (t *DRange) Eval(_ *EvalContext) (Datum, error) { + return t, nil +} + // Eval implements the TypedExpr interface. func (t *DOidWrapper) Eval(_ *EvalContext) (Datum, error) { return t, nil diff --git a/pkg/sql/sem/tree/expr.go b/pkg/sql/sem/tree/expr.go index 306d3767bf9c..4a79768fb71f 100644 --- a/pkg/sql/sem/tree/expr.go +++ b/pkg/sql/sem/tree/expr.go @@ -1654,6 +1654,7 @@ func (node *DTimestamp) String() string { return AsString(node) } func (node *DTimestampTZ) String() string { return AsString(node) } func (node *DTuple) String() string { return AsString(node) } func (node *DArray) String() string { return AsString(node) } +func (node *DRange) String() string { return AsString(node) } func (node *DOid) String() string { return AsString(node) } func (node *DOidWrapper) String() string { return AsString(node) } func (node *Exprs) String() string { return AsString(node) } diff --git a/pkg/sql/sem/tree/type_check.go b/pkg/sql/sem/tree/type_check.go index 8357ee379c08..3d9c20751f64 100644 --- a/pkg/sql/sem/tree/type_check.go +++ b/pkg/sql/sem/tree/type_check.go @@ -1344,6 +1344,10 @@ func (d *DTuple) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { retur // identity function for Datum. func (d *DArray) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { return d, nil } +// TypeCheck implements the Expr interface. It is implemented as an idempotent +// identity function for Datum. +func (d *DRange) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { return d, nil } + // TypeCheck implements the Expr interface. It is implemented as an idempotent // identity function for Datum. func (d *DOid) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { return d, nil } diff --git a/pkg/sql/sem/tree/walk.go b/pkg/sql/sem/tree/walk.go index d01438f1bedc..29dff6cbdd90 100644 --- a/pkg/sql/sem/tree/walk.go +++ b/pkg/sql/sem/tree/walk.go @@ -661,6 +661,9 @@ func (expr *DTuple) Walk(_ Visitor) Expr { return expr } // Walk implements the Expr interface. func (expr *DArray) Walk(_ Visitor) Expr { return expr } +// Walk implements the Expr interface. +func (expr *DRange) Walk(_ Visitor) Expr { return expr } + // Walk implements the Expr interface. func (expr *DOid) Walk(_ Visitor) Expr { return expr } diff --git a/pkg/sql/sem/types/oid.go b/pkg/sql/sem/types/oid.go index 87b7784b1a40..a80a4d8f4eb5 100644 --- a/pkg/sql/sem/types/oid.go +++ b/pkg/sql/sem/types/oid.go @@ -144,6 +144,21 @@ var oidToArrayOid = map[oid.Oid]oid.Oid{ oid.T_timestamptz: oid.T__timestamptz, oid.T_varchar: oid.T__varchar, oid.T_uuid: oid.T__uuid, + oid.T_int4range: oid.T__int4range, + oid.T_int8range: oid.T__int8range, + oid.T_tsrange: oid.T__tsrange, + oid.T_tstzrange: oid.T__tstzrange, + oid.T_daterange: oid.T__daterange, + oid.T_numrange: oid.T__numrange, +} + +var oidToRangeOid = map[oid.Oid]oid.Oid{ + oid.T_int4: oid.T_int4range, + oid.T_int8: oid.T_int8range, + oid.T_timestamp: oid.T_tsrange, + oid.T_timestamptz: oid.T_tstzrange, + oid.T_date: oid.T_daterange, + oid.T_numeric: oid.T_numrange, } // TOid represents an alias to the Int type with a different Postgres OID. diff --git a/pkg/sql/sem/types/types.go b/pkg/sql/sem/types/types.go index 892150e4e499..2852437b2e8b 100644 --- a/pkg/sql/sem/types/types.go +++ b/pkg/sql/sem/types/types.go @@ -109,6 +109,8 @@ var ( FamTuple T = TTuple{} // FamArray is the type family of a DArray. CANNOT be compared with ==. FamArray T = TArray{} + // FamRange is the type family of a DRange. CANNOT be compared with ==. + FamRange T = TRange{} // FamPlaceholder is the type family of a placeholder. CANNOT be compared // with ==. FamPlaceholder T = TPlaceholder{} @@ -466,6 +468,46 @@ func (a TArray) IsAmbiguous() bool { return a.Typ == nil || a.Typ.IsAmbiguous() } +type TRange struct { + Typ T +} + +const noRangeType = 0 + +func (a TRange) String() string { + return "" +} + +func (a TRange) Equivalent(other T) bool { + if other == Any { + return true + } + if u, ok := UnwrapType(other).(TRange); ok { + return a.Typ.Equivalent(u.Typ) + } + return false +} + +func (TRange) FamilyEqual(other T) bool { + _, ok := UnwrapType(other).(TRange) + return ok +} + +func (a TRange) Oid() oid.Oid { + if o, ok := oidToRangeOid[a.Typ.Oid()]; ok { + return o + } + return noRangeType +} + +func (a TRange) SQLName() string { + return a.String() +} + +func (a TRange) IsAmbiguous() bool { + return a.Typ == nil || a.Typ.IsAmbiguous() +} + type tAny struct{} func (tAny) String() string { return "anyelement" } diff --git a/pkg/sql/sqlbase/datum_alloc.go b/pkg/sql/sqlbase/datum_alloc.go index 8322d27dd8ac..f642e08e494d 100644 --- a/pkg/sql/sqlbase/datum_alloc.go +++ b/pkg/sql/sqlbase/datum_alloc.go @@ -35,6 +35,7 @@ type DatumAlloc struct { djsonAlloc []tree.DJSON dtupleAlloc []tree.DTuple doidAlloc []tree.DOid + drangeAlloc []tree.DRange scratch []byte env tree.CollationEnvironment } @@ -241,3 +242,15 @@ func (a *DatumAlloc) NewDOid(v tree.DOid) tree.Datum { *buf = (*buf)[1:] return r } + +// NewDRange allocates a DRange. +func (a *DatumAlloc) NewDRange(v tree.DRange) tree.Datum { + buf := &a.drangeAlloc + if len(*buf) == 0 { + *buf = make([]tree.DRange, datumAllocSize) + } + r := &(*buf)[0] + *r = v + *buf = (*buf)[1:] + return r +}