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