@@ -17,7 +17,7 @@ import (
1717)
1818
1919// duckdb-go exports the following type wrappers:
20- // UUID, Map, Interval, Decimal, Union, Composite (optional, used to scan LIST and STRUCT).
20+ // UUID, Bit, Map, Interval, Decimal, Union, Composite (optional, used to scan LIST and STRUCT).
2121
2222// Pre-computed reflect type values to avoid repeated allocations.
2323var (
4444 reflectTypeUnion = reflect .TypeFor [Union ]()
4545 reflectTypeAny = reflect .TypeFor [any ]()
4646 reflectTypeUUID = reflect .TypeFor [UUID ]()
47+ reflectTypeBit = reflect .TypeFor [Bit ]()
4748)
4849
4950type numericType interface {
@@ -136,6 +137,97 @@ func uuidToHugeInt(uuid UUID) mapping.HugeInt {
136137 return mapping .NewHugeInt (lower , int64 (upper ^ (1 << 63 )))
137138}
138139
140+ // Bit represents a DuckDB BIT value as a sequence of bits.
141+ // Data stores DuckDB's internal format: a padding-count prefix byte followed by
142+ // the bit bytes (right-aligned with 1-padded MSB bits).
143+ // For example, "10101" (5 bits) is stored as [3, 11110101] where 3 is the padding count.
144+ type Bit struct {
145+ Data []byte
146+ }
147+
148+ // NewBitFromString creates a Bit from a string of '0' and '1' characters.
149+ func NewBitFromString (s string ) (Bit , error ) {
150+ if len (s ) == 0 {
151+ return Bit {}, nil
152+ }
153+
154+ numBytes := (len (s ) + 7 ) / 8
155+ padding := (8 - (len (s ) % 8 )) % 8
156+ data := make ([]byte , numBytes + 1 )
157+ data [0 ] = byte (padding )
158+
159+ // Set padding bits to 1
160+ if padding > 0 {
161+ data [1 ] = byte (0xFF ) << (8 - padding )
162+ }
163+
164+ for i , c := range s {
165+ switch c {
166+ case '1' :
167+ bitPos := padding + i
168+ byteIdx := bitPos / 8 + 1
169+ bitIdx := 7 - (bitPos % 8 )
170+ data [byteIdx ] |= 1 << bitIdx
171+ case '0' :
172+ default :
173+ return Bit {}, fmt .Errorf ("invalid character in bit string: %c" , c )
174+ }
175+ }
176+
177+ return Bit {Data : data }, nil
178+ }
179+
180+ // Validate checks that Data is a valid DuckDB bit encoding: the padding count
181+ // (first byte) must be 0-7, and the padding bits in the first data byte must
182+ // all be set to 1.
183+ func (b Bit ) Validate () error {
184+ if len (b .Data ) <= 1 {
185+ return nil
186+ }
187+ padding := int (b .Data [0 ])
188+ if padding > 7 {
189+ return fmt .Errorf ("invalid padding count %d, must be 0-7" , padding )
190+ }
191+ if padding > 0 {
192+ expectedMask := byte (0xFF ) << (8 - padding )
193+ if (b .Data [1 ] & expectedMask ) != expectedMask {
194+ return fmt .Errorf ("padding bits must be 1s, expected high %d bits of first byte to be set" , padding )
195+ }
196+ }
197+ return nil
198+ }
199+
200+ // Len returns the number of bits.
201+ func (b Bit ) Len () int {
202+ if len (b .Data ) == 0 {
203+ return 0
204+ }
205+ return (len (b .Data )- 1 )* 8 - int (b .Data [0 ])
206+ }
207+
208+ // String returns the bit string representation (e.g., "10101").
209+ func (b Bit ) String () string {
210+ length := b .Len ()
211+ if length == 0 {
212+ return ""
213+ }
214+ var sb strings.Builder
215+ sb .Grow (length )
216+ padding := int (b .Data [0 ])
217+ bitData := b .Data [1 :]
218+ for i := range length {
219+ bitPos := padding + i
220+ byteIdx := bitPos / 8
221+ bitIdx := 7 - (bitPos % 8 )
222+ if (bitData [byteIdx ] & (1 << bitIdx )) != 0 {
223+ sb .WriteByte ('1' )
224+ } else {
225+ sb .WriteByte ('0' )
226+ }
227+ }
228+ return sb .String ()
229+ }
230+
139231func hugeIntToNative (hugeInt * mapping.HugeInt ) * big.Int {
140232 lower , upper := mapping .HugeIntMembers (hugeInt )
141233 i := big .NewInt (upper )
0 commit comments