Skip to content

Commit 342a4f2

Browse files
authored
Merge pull request #2158 from dolthub/zachmu/bittypes
Added bit and bit varying types
2 parents b55bd4e + 6a597c7 commit 342a4f2

22 files changed

Lines changed: 1016 additions & 118 deletions

server/ast/expr.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,12 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) {
476476
case *tree.DArray:
477477
return nil, errors.Errorf("the statement is not yet supported")
478478
case *tree.DBitArray:
479-
return nil, errors.Errorf("the statement is not yet supported")
479+
// We convert bitarray to string representation for engine representation purposes so that we don't have to
480+
// represent another fundamental golang type. This means our representation in memory is more verbose.
481+
bitStr := tree.AsStringWithFlags(node, tree.FmtPgwireText)
482+
return vitess.InjectedExpr{
483+
Expression: pgexprs.NewUnsafeLiteral(bitStr, pgtypes.Bit),
484+
}, nil
480485
case *tree.DBool:
481486
return vitess.InjectedExpr{
482487
Expression: pgexprs.NewRawLiteralBool(bool(*node)),

server/ast/resolvable_type_reference.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,38 @@ func nodeResolvableTypeReference(ctx *Context, typ tree.ResolvableTypeReference)
158158
resolvedType = pgtypes.VarChar
159159
} else {
160160
resolvedType, err = pgtypes.NewVarCharType(int32(width))
161-
}
162-
if err != nil {
163-
return nil, nil, err
161+
if err != nil {
162+
return nil, nil, err
163+
}
164164
}
165165
case oid.T_xid:
166166
resolvedType = pgtypes.Xid
167+
case oid.T_bit:
168+
width := uint32(columnType.Width())
169+
if width > pgtypes.StringMaxLength {
170+
return nil, nil, errors.Errorf("length for type bit cannot exceed %d", pgtypes.StringMaxLength)
171+
} else if width == 0 {
172+
// TODO: need to differentiate between definitions 'bit' (valid) and 'bit(0)' (invalid)
173+
resolvedType = pgtypes.Bit
174+
} else {
175+
resolvedType, err = pgtypes.NewBitType(int32(width))
176+
if err != nil {
177+
return nil, nil, err
178+
}
179+
}
180+
case oid.T_varbit:
181+
width := uint32(columnType.Width())
182+
if width > pgtypes.StringMaxLength {
183+
return nil, nil, errors.Errorf("length for type varbit cannot exceed %d", pgtypes.StringMaxLength)
184+
} else if width == 0 {
185+
// TODO: need to differentiate between definitions 'varbit' (valid) and 'varbit(0)' (invalid)
186+
resolvedType = pgtypes.VarBit
187+
} else {
188+
resolvedType, err = pgtypes.NewVarBitType(int32(width))
189+
if err != nil {
190+
return nil, nil, err
191+
}
192+
}
167193
default:
168194
return nil, nil, errors.Errorf("unknown type with oid: %d", uint32(columnType.Oid()))
169195
}

server/cast/bit.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2026 Dolthub, Inc.
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 cast
16+
17+
import (
18+
"github.com/cockroachdb/errors"
19+
"github.com/dolthub/go-mysql-server/sql"
20+
21+
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
22+
23+
"github.com/dolthub/doltgresql/server/functions/framework"
24+
pgtypes "github.com/dolthub/doltgresql/server/types"
25+
)
26+
27+
// initBit handles all casts that are built-in. This comprises only the "From" types.
28+
func initBit() {
29+
bitExplicit()
30+
bitImplicit()
31+
}
32+
33+
// bitExplicit registers all explicit casts. This comprises only the "From" types.
34+
func bitExplicit() {
35+
framework.MustAddExplicitTypeCast(framework.TypeCast{
36+
FromType: pgtypes.Bit,
37+
ToType: pgtypes.Int32,
38+
Function: func(ctx *sql.Context, val any, targetType *pgtypes.DoltgresType) (any, error) {
39+
array, err := tree.ParseDBitArray(val.(string))
40+
if err != nil {
41+
return nil, err
42+
}
43+
if array.BitLen() > 32 {
44+
return nil, errors.Wrap(pgtypes.ErrCastOutOfRange, "integer out of range")
45+
}
46+
return int32(array.AsInt64(32)), nil
47+
},
48+
})
49+
framework.MustAddExplicitTypeCast(framework.TypeCast{
50+
FromType: pgtypes.Bit,
51+
ToType: pgtypes.Int64,
52+
Function: func(ctx *sql.Context, val any, targetType *pgtypes.DoltgresType) (any, error) {
53+
array, err := tree.ParseDBitArray(val.(string))
54+
if err != nil {
55+
return nil, err
56+
}
57+
if array.BitLen() > 64 {
58+
return nil, errors.Wrap(pgtypes.ErrCastOutOfRange, "bigint out of range")
59+
}
60+
return array.AsInt64(64), nil
61+
},
62+
})
63+
}
64+
65+
// bitImplicit registers all implicit casts. This comprises only the "From" types.
66+
func bitImplicit() {
67+
framework.MustAddImplicitTypeCast(framework.TypeCast{
68+
FromType: pgtypes.Bit,
69+
ToType: pgtypes.Bit,
70+
Function: func(ctx *sql.Context, val any, targetType *pgtypes.DoltgresType) (any, error) {
71+
input := val.(string)
72+
array, err := tree.ParseDBitArray(input)
73+
if err != nil {
74+
return nil, err
75+
}
76+
expectedLength := pgtypes.GetCharLengthFromTypmod(targetType.GetAttTypMod())
77+
if array.BitLen() != uint(expectedLength) {
78+
return nil, pgtypes.ErrWrongLengthBit.New(len(input), expectedLength)
79+
}
80+
return tree.AsStringWithFlags(array, tree.FmtPgwireText), nil
81+
},
82+
})
83+
framework.MustAddImplicitTypeCast(framework.TypeCast{
84+
FromType: pgtypes.Bit,
85+
ToType: pgtypes.VarBit,
86+
Function: func(ctx *sql.Context, val any, targetType *pgtypes.DoltgresType) (any, error) {
87+
input := val.(string)
88+
array, err := tree.ParseDBitArray(input)
89+
if err != nil {
90+
return nil, err
91+
}
92+
atttypmod := targetType.GetAttTypMod()
93+
if atttypmod != -1 {
94+
maxLength := pgtypes.GetCharLengthFromTypmod(atttypmod)
95+
if int32(array.BitLen()) > maxLength {
96+
return nil, pgtypes.ErrVarBitLengthExceeded.New(maxLength)
97+
}
98+
}
99+
return tree.AsStringWithFlags(array, tree.FmtPgwireText), nil
100+
},
101+
})
102+
}

server/cast/init.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
// Init initializes all casts in this package.
2323
func Init() {
24+
initBit()
2425
initBool()
2526
initChar()
2627
initDate()
@@ -44,6 +45,7 @@ func Init() {
4445
initTimestamp()
4546
initTimestampTZ()
4647
initTimeTZ()
48+
initVarBit()
4749
initVarChar()
4850

4951
// This is a hack to get around import cycles. The types package needs these references for type conversions in

server/cast/varbit.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2026 Dolthub, Inc.
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 cast
16+
17+
import (
18+
"github.com/dolthub/go-mysql-server/sql"
19+
20+
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
21+
22+
"github.com/dolthub/doltgresql/server/functions/framework"
23+
pgtypes "github.com/dolthub/doltgresql/server/types"
24+
)
25+
26+
// initVarBit handles all casts that are built-in. This comprises only the "From" types.
27+
func initVarBit() {
28+
varBitImplicit()
29+
}
30+
31+
// varBitImplicit registers all implicit casts. This comprises only the "From" types.
32+
func varBitImplicit() {
33+
framework.MustAddImplicitTypeCast(framework.TypeCast{
34+
FromType: pgtypes.VarBit,
35+
ToType: pgtypes.Bit,
36+
Function: func(ctx *sql.Context, val any, targetType *pgtypes.DoltgresType) (any, error) {
37+
input := val.(string)
38+
array, err := tree.ParseDBitArray(input)
39+
if err != nil {
40+
return nil, err
41+
}
42+
expectedLength := pgtypes.GetCharLengthFromTypmod(targetType.GetAttTypMod())
43+
if array.BitLen() != uint(expectedLength) {
44+
return nil, pgtypes.ErrWrongLengthBit.New(len(input), expectedLength)
45+
}
46+
return tree.AsStringWithFlags(array, tree.FmtPgwireText), nil
47+
},
48+
})
49+
framework.MustAddImplicitTypeCast(framework.TypeCast{
50+
FromType: pgtypes.VarBit,
51+
ToType: pgtypes.VarBit,
52+
Function: func(ctx *sql.Context, val any, targetType *pgtypes.DoltgresType) (any, error) {
53+
input := val.(string)
54+
array, err := tree.ParseDBitArray(input)
55+
if err != nil {
56+
return nil, err
57+
}
58+
atttypmod := targetType.GetAttTypMod()
59+
if atttypmod != -1 {
60+
maxLength := pgtypes.GetCharLengthFromTypmod(atttypmod)
61+
if int32(array.BitLen()) > maxLength {
62+
return nil, pgtypes.ErrVarBitLengthExceeded.New(maxLength)
63+
}
64+
}
65+
return tree.AsStringWithFlags(array, tree.FmtPgwireText), nil
66+
},
67+
})
68+
}

server/functions/binary/equal.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333

3434
// initBinaryEqual registers the functions to the catalog.
3535
func initBinaryEqual() {
36+
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, biteq)
3637
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, booleq)
3738
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, bpchareq)
3839
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, byteaeq)
@@ -72,6 +73,7 @@ func initBinaryEqual() {
7273
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, timestamptz_eq)
7374
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, timetz_eq)
7475
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, uuid_eq)
76+
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, varbiteq)
7577
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, xideqint4)
7678
framework.RegisterBinaryFunction(framework.Operator_BinaryEqual, xideq)
7779
}
@@ -523,6 +525,36 @@ func record_eq_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any,
523525
return compare.CompareRecords(ctx, framework.Operator_BinaryEqual, val1, val2)
524526
}
525527

528+
// varbiteq represents the PostgreSQL function of the same name, taking the same parameters.
529+
var varbiteq = framework.Function2{
530+
Name: "varbiteq",
531+
Return: pgtypes.Bool,
532+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.VarBit, pgtypes.VarBit},
533+
Strict: true,
534+
Callable: varbit_eq_callable,
535+
}
536+
537+
// biteq represents the PostgreSQL function of the same name, taking the same parameters.
538+
var biteq = framework.Function2{
539+
Name: "biteq",
540+
Return: pgtypes.Bool,
541+
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Bit, pgtypes.Bit},
542+
Strict: true,
543+
Callable: bit_eq_callable,
544+
}
545+
546+
// varbit_eq_callable is the callable logic for the varbiteq function.
547+
func varbit_eq_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
548+
res, err := pgtypes.VarBit.Compare(ctx, val1, val2)
549+
return res == 0, err
550+
}
551+
552+
// bit_eq_callable is the callable logic for the varbiteq function.
553+
func bit_eq_callable(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1 any, val2 any) (any, error) {
554+
res, err := pgtypes.Bit.Compare(ctx, val1, val2)
555+
return res == 0, err
556+
}
557+
526558
// record_eq represents the PostgreSQL function of the same name, taking the same parameters.
527559
var record_eq = framework.Function2{
528560
Name: "record_eq",

0 commit comments

Comments
 (0)