-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbinary_storage.go
More file actions
1893 lines (1648 loc) · 56.3 KB
/
Copy pathbinary_storage.go
File metadata and controls
1893 lines (1648 loc) · 56.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package storage
import (
"bytes"
"encoding/binary"
"fmt"
"hash/crc32"
"os"
"path/filepath"
"runtime"
"sync"
"unsafe"
"github.com/LlamasScripters/PostgresInGo/internal/types"
)
// Cache line size for modern CPUs (64 bytes)
const (
CacheLineSize = 64
BinaryMagic = 0x504754524553 // "PGTRES" in hex
MetadataVersion = 1
TupleBinaryMagic = 0x4254 // "BT" - Binary Tuple magic number
// Cache alignment masks and helpers
CacheLineMask = CacheLineSize - 1
CacheAlignedTupleHeaderSize = 64 // Full cache line for optimal performance
)
// BinaryStorageManager implements cache-aligned binary storage
type BinaryStorageManager struct {
*StorageManager
metadataFilePath string
binaryMode bool
schemaHashCache map[string]uint32 // Cache schema hashes by table name
// Memory pools for performance optimization
bufferPool *sync.Pool // Reusable byte buffers
byteSlicePool *sync.Pool // Reusable byte slices
valueArrayPool *sync.Pool // Reusable value arrays
bitmapPool *sync.Pool // Reusable null bitmaps
}
// BinaryMetadataHeader is cache-aligned metadata header (64 bytes)
type BinaryMetadataHeader struct {
Magic uint64 // 8 bytes - Magic number for format validation
Version uint32 // 4 bytes - Format version
TableCount uint32 // 4 bytes - Number of tables
NextPageID uint64 // 8 bytes - Next page ID
NextTupleID uint64 // 8 bytes - Next tuple ID
Reserved1 uint64 // 8 bytes - Reserved for future use
Reserved2 uint64 // 8 bytes - Reserved for future use
Reserved3 uint64 // 8 bytes - Reserved for future use
Checksum uint32 // 4 bytes - Header checksum
Padding [4]byte // 4 bytes - Padding to reach 64 bytes
}
// BinaryTableHeader represents a table in binary format (cache-aligned to 64 bytes)
type BinaryTableHeader struct {
TableID uint64 // 8 bytes - Table ID
NameLength uint32 // 4 bytes - Length of table name
ColumnCount uint32 // 4 bytes - Number of columns
ConstraintCount uint32 // 4 bytes - Number of constraints
IndexCount uint32 // 4 bytes - Number of indexes
PageCount uint32 // 4 bytes - Number of pages
TupleCount uint64 // 8 bytes - Total tuple count
TableSize uint64 // 8 bytes - Size in bytes
Reserved1 uint64 // 8 bytes - Reserved for future use
Reserved2 uint64 // 8 bytes - Reserved for future use
Checksum uint32 // 4 bytes - Header checksum
Padding [4]byte // 4 bytes - Padding to reach 64 bytes
}
// BinaryColumnInfo represents column metadata (cache-aligned to 32 bytes)
type BinaryColumnInfo struct {
NameOffset uint32 // 4 bytes - Offset to column name in string table
DataType types.DataType // 4 bytes - Column data type
Size uint32 // 4 bytes - Column size
Flags uint32 // 4 bytes - Flags (nullable, primary key, unique, auto increment)
DefaultOffset uint32 // 4 bytes - Offset to default value in string table
Reserved uint32 // 4 bytes - Reserved for future use
Padding [8]byte // 8 bytes - Padding to reach 32 bytes
}
// Column flags
const (
FlagNullable uint32 = 1 << 0
FlagPrimaryKey uint32 = 1 << 1
FlagUnique uint32 = 1 << 2
FlagAutoIncrement uint32 = 1 << 3
)
// BinaryConstraintInfo represents constraint metadata (cache-aligned to 64 bytes)
type BinaryConstraintInfo struct {
NameOffset uint32 // 4 bytes - Offset to constraint name
Type types.ConstraintType // 4 bytes - Constraint type
ColumnCount uint32 // 4 bytes - Number of columns in constraint
RefTableOffset uint32 // 4 bytes - Offset to referenced table name
RefColumnCount uint32 // 4 bytes - Number of referenced columns
OnDeleteRule uint32 // 4 bytes - On delete rule
OnUpdateRule uint32 // 4 bytes - On update rule
CheckExprOffset uint32 // 4 bytes - Offset to check expression
Reserved1 uint64 // 8 bytes - Reserved
Reserved2 uint64 // 8 bytes - Reserved
Reserved3 uint64 // 8 bytes - Reserved
Checksum uint32 // 4 bytes - Constraint checksum
Padding [4]byte // 4 bytes - Padding to 64 bytes
}
// BinaryTupleHeader represents tuple metadata (cache-aligned to 32 bytes)
type BinaryTupleHeader struct {
TupleID types.TupleID // 10 bytes (8 + 2) - Tuple ID
DataSize uint32 // 4 bytes - Size of tuple data
ColumnCount uint32 // 4 bytes - Number of columns
Flags uint32 // 4 bytes - Tuple flags (deleted, updated, etc.)
Timestamp uint64 // 8 bytes - Timestamp
Checksum uint32 // 4 bytes - Data checksum
Padding [6]byte // 6 bytes - Padding to reach 32 bytes
}
// Tuple flags
const (
TupleFlagDeleted uint32 = 1 << 0
TupleFlagUpdated uint32 = 1 << 1
)
// NewBinaryStorageManager creates a binary storage manager
func NewBinaryStorageManager(dataDir string) (*BinaryStorageManager, error) {
// Create base storage manager
baseStorage, err := NewStorageManager(dataDir)
if err != nil {
return nil, err
}
bsm := &BinaryStorageManager{
StorageManager: baseStorage,
metadataFilePath: filepath.Join(dataDir, "metadata.bin"),
binaryMode: true,
schemaHashCache: make(map[string]uint32),
// Initialize memory pools for high-performance operations
bufferPool: &sync.Pool{
New: func() interface{} {
// Pre-allocate buffer with typical tuple size
return bytes.NewBuffer(make([]byte, 0, 512))
},
},
byteSlicePool: &sync.Pool{
New: func() interface{} {
// Pre-allocate 8-byte slice for numeric values
return make([]byte, 8)
},
},
valueArrayPool: &sync.Pool{
New: func() interface{} {
// Pre-allocate array for typical column count
return make([]any, 0, 32)
},
},
bitmapPool: &sync.Pool{
New: func() interface{} {
// Pre-allocate bitmap for typical column count (32 columns = 4 bytes)
return make([]byte, 4)
},
},
}
// Try to load existing binary metadata
err = bsm.loadBinaryMetadata()
if err != nil {
// If binary metadata doesn't exist, try to migrate from JSON
if os.IsNotExist(err) {
err = bsm.migrateFromJSON()
if err != nil {
// If no JSON exists either, start fresh
return bsm, nil
}
} else {
return nil, err
}
}
return bsm, nil
}
// loadBinaryMetadata loads metadata from binary format
func (bsm *BinaryStorageManager) loadBinaryMetadata() error {
file, err := os.Open(bsm.metadataFilePath)
if err != nil {
return err
}
defer file.Close()
// Read and validate header
var header BinaryMetadataHeader
err = binary.Read(file, binary.LittleEndian, &header)
if err != nil {
return fmt.Errorf("failed to read metadata header: %w", err)
}
if header.Magic != BinaryMagic {
return fmt.Errorf("invalid binary metadata format")
}
if header.Version != MetadataVersion {
return fmt.Errorf("unsupported metadata version: %d", header.Version)
}
// Validate header checksum
headerBytes := (*[unsafe.Sizeof(header)]byte)(unsafe.Pointer(&header))[:unsafe.Sizeof(header)-8] // Exclude checksum and padding
calculatedChecksum := crc32.ChecksumIEEE(headerBytes)
if calculatedChecksum != header.Checksum {
return fmt.Errorf("metadata header checksum mismatch")
}
// Update storage manager state
bsm.nextPageID = header.NextPageID
bsm.nextTupleID = header.NextTupleID
bsm.tables = make(map[string]*types.Table)
// Read table data
for i := uint32(0); i < header.TableCount; i++ {
table, err := bsm.readBinaryTable(file)
if err != nil {
return fmt.Errorf("failed to read table %d: %w", i, err)
}
bsm.tables[table.Name] = table
bsm.tableTuples[table.Name] = []*types.Tuple{}
}
return nil
}
// readBinaryTable reads a single table from binary format
func (bsm *BinaryStorageManager) readBinaryTable(file *os.File) (*types.Table, error) {
var tableHeader BinaryTableHeader
err := binary.Read(file, binary.LittleEndian, &tableHeader)
if err != nil {
return nil, err
}
// Read table name
nameBytes := make([]byte, tableHeader.NameLength)
_, err = file.Read(nameBytes)
if err != nil {
return nil, err
}
tableName := string(nameBytes)
// Create table structure
table := &types.Table{
ID: tableHeader.TableID,
Name: tableName,
Pages: make([]uint64, tableHeader.PageCount),
Indexes: make(map[string]*types.Index),
Stats: types.TableStats{
TupleCount: int64(tableHeader.TupleCount),
PageCount: int64(tableHeader.PageCount),
Size: int64(tableHeader.TableSize),
},
}
// Read page IDs
for i := uint32(0); i < tableHeader.PageCount; i++ {
var pageID uint64
err = binary.Read(file, binary.LittleEndian, &pageID)
if err != nil {
return nil, err
}
table.Pages[i] = pageID
}
// Read columns
columns := make([]types.Column, tableHeader.ColumnCount)
columnInfos := make([]BinaryColumnInfo, tableHeader.ColumnCount)
// Read column headers
for i := uint32(0); i < tableHeader.ColumnCount; i++ {
err = binary.Read(file, binary.LittleEndian, &columnInfos[i])
if err != nil {
return nil, err
}
}
// Read string table for column names and defaults
stringTable, err := bsm.readStringTable(file)
if err != nil {
return nil, err
}
// Populate column data
for i, colInfo := range columnInfos {
columns[i] = types.Column{
Name: bsm.getStringFromTable(stringTable, colInfo.NameOffset),
Type: colInfo.DataType,
Size: int(colInfo.Size),
Nullable: (colInfo.Flags & FlagNullable) != 0,
IsPrimaryKey: (colInfo.Flags & FlagPrimaryKey) != 0,
IsUnique: (colInfo.Flags & FlagUnique) != 0,
AutoIncrement: (colInfo.Flags & FlagAutoIncrement) != 0,
}
if colInfo.DefaultOffset > 0 {
columns[i].Default = bsm.getStringFromTable(stringTable, colInfo.DefaultOffset)
}
}
table.Schema.Columns = columns
// Read constraints
constraints := make([]types.Constraint, tableHeader.ConstraintCount)
for i := uint32(0); i < tableHeader.ConstraintCount; i++ {
constraint, err := bsm.readBinaryConstraint(file, stringTable)
if err != nil {
return nil, err
}
constraints[i] = *constraint
}
table.Schema.Constraints = constraints
// Assign constraints to table fields
bsm.assignConstraintsToTable(table, constraints)
return table, nil
}
// readStringTable reads a string table from binary format
func (bsm *BinaryStorageManager) readStringTable(file *os.File) ([]byte, error) {
var tableSize uint32
err := binary.Read(file, binary.LittleEndian, &tableSize)
if err != nil {
return nil, err
}
stringTable := make([]byte, tableSize)
_, err = file.Read(stringTable)
if err != nil {
return nil, err
}
return stringTable, nil
}
// getStringFromTable extracts a null-terminated string from string table
func (bsm *BinaryStorageManager) getStringFromTable(stringTable []byte, offset uint32) string {
if offset >= uint32(len(stringTable)) {
return ""
}
start := int(offset)
end := start
for end < len(stringTable) && stringTable[end] != 0 {
end++
}
return string(stringTable[start:end])
}
// readBinaryConstraint reads a constraint from binary format
func (bsm *BinaryStorageManager) readBinaryConstraint(file *os.File, stringTable []byte) (*types.Constraint, error) {
var constraintInfo BinaryConstraintInfo
err := binary.Read(file, binary.LittleEndian, &constraintInfo)
if err != nil {
return nil, err
}
constraint := &types.Constraint{
Name: bsm.getStringFromTable(stringTable, constraintInfo.NameOffset),
Type: constraintInfo.Type,
}
// Read column names
constraint.Columns = make([]string, constraintInfo.ColumnCount)
for i := uint32(0); i < constraintInfo.ColumnCount; i++ {
var nameOffset uint32
err = binary.Read(file, binary.LittleEndian, &nameOffset)
if err != nil {
return nil, err
}
constraint.Columns[i] = bsm.getStringFromTable(stringTable, nameOffset)
}
// Read referenced columns if foreign key
if constraintInfo.Type == types.ForeignKeyConstraint {
constraint.RefTable = bsm.getStringFromTable(stringTable, constraintInfo.RefTableOffset)
constraint.RefColumns = make([]string, constraintInfo.RefColumnCount)
for i := uint32(0); i < constraintInfo.RefColumnCount; i++ {
var nameOffset uint32
err = binary.Read(file, binary.LittleEndian, &nameOffset)
if err != nil {
return nil, err
}
constraint.RefColumns[i] = bsm.getStringFromTable(stringTable, nameOffset)
}
// Convert rule constants to strings
constraint.OnDeleteRule = bsm.convertRuleToString(constraintInfo.OnDeleteRule)
constraint.OnUpdateRule = bsm.convertRuleToString(constraintInfo.OnUpdateRule)
}
// Read check expression if check constraint
if constraintInfo.Type == types.CheckConstraint && constraintInfo.CheckExprOffset > 0 {
constraint.CheckExpr = bsm.getStringFromTable(stringTable, constraintInfo.CheckExprOffset)
}
return constraint, nil
}
// convertRuleToString converts binary rule constant to string
func (bsm *BinaryStorageManager) convertRuleToString(rule uint32) string {
switch types.ReferentialAction(rule) {
case types.Cascade:
return "CASCADE"
case types.Restrict:
return "RESTRICT"
case types.SetNull:
return "SET NULL"
case types.SetDefault:
return "SET DEFAULT"
default:
return "NO ACTION"
}
}
// assignConstraintsToTable assigns constraints to appropriate table fields
func (bsm *BinaryStorageManager) assignConstraintsToTable(table *types.Table, constraints []types.Constraint) {
for i := range constraints {
constraint := &constraints[i]
switch constraint.Type {
case types.PrimaryKeyConstraint:
table.PrimaryKey = constraint
case types.ForeignKeyConstraint:
table.ForeignKeys = append(table.ForeignKeys, constraint)
case types.UniqueConstraint:
table.UniqueKeys = append(table.UniqueKeys, constraint)
case types.CheckConstraint:
table.CheckConstraints = append(table.CheckConstraints, constraint)
}
}
}
// saveBinaryMetadata saves metadata in optimized binary format
func (bsm *BinaryStorageManager) saveBinaryMetadata() error {
bsm.mu.Lock()
defer bsm.mu.Unlock()
file, err := os.Create(bsm.metadataFilePath)
if err != nil {
return err
}
defer file.Close()
// Prepare header
header := BinaryMetadataHeader{
Magic: BinaryMagic,
Version: MetadataVersion,
TableCount: uint32(len(bsm.tables)),
NextPageID: bsm.nextPageID,
NextTupleID: bsm.nextTupleID,
}
// Calculate and set header checksum
headerBytes := (*[unsafe.Sizeof(header)]byte)(unsafe.Pointer(&header))[:unsafe.Sizeof(header)-8]
header.Checksum = crc32.ChecksumIEEE(headerBytes)
// Write header
err = binary.Write(file, binary.LittleEndian, &header)
if err != nil {
return err
}
// Write each table
for _, table := range bsm.tables {
err = bsm.writeBinaryTable(file, table)
if err != nil {
return err
}
}
return file.Sync()
}
// writeBinaryTable writes a table in binary format
func (bsm *BinaryStorageManager) writeBinaryTable(file *os.File, table *types.Table) error {
// Prepare string table
stringTable := bytes.NewBuffer(nil)
stringOffsets := make(map[string]uint32)
// Prepare string table for column names and values
// Add all column names and defaults
columnOffsets := make([]uint32, len(table.Schema.Columns))
defaultOffsets := make([]uint32, len(table.Schema.Columns))
for i, col := range table.Schema.Columns {
columnOffsets[i] = bsm.addStringToTable(stringTable, stringOffsets, col.Name)
if col.Default != nil {
defaultOffsets[i] = bsm.addStringToTable(stringTable, stringOffsets, fmt.Sprintf("%v", col.Default))
}
}
// Count all constraints
constraintCount := len(table.Schema.Constraints)
if table.PrimaryKey != nil {
constraintCount++
}
constraintCount += len(table.ForeignKeys) + len(table.UniqueKeys) + len(table.CheckConstraints)
// Write table header
tableHeader := BinaryTableHeader{
TableID: table.ID,
NameLength: uint32(len(table.Name)),
ColumnCount: uint32(len(table.Schema.Columns)),
ConstraintCount: uint32(constraintCount),
IndexCount: uint32(len(table.Indexes)),
PageCount: uint32(len(table.Pages)),
TupleCount: uint64(table.Stats.TupleCount),
TableSize: uint64(table.Stats.Size),
}
// Calculate checksum
headerBytes := (*[unsafe.Sizeof(tableHeader)]byte)(unsafe.Pointer(&tableHeader))[:unsafe.Sizeof(tableHeader)-8]
tableHeader.Checksum = crc32.ChecksumIEEE(headerBytes)
err := binary.Write(file, binary.LittleEndian, &tableHeader)
if err != nil {
return err
}
// Write table name
_, err = file.Write([]byte(table.Name))
if err != nil {
return err
}
// Write page IDs
for _, pageID := range table.Pages {
err = binary.Write(file, binary.LittleEndian, pageID)
if err != nil {
return err
}
}
// Write column information
for i, col := range table.Schema.Columns {
flags := uint32(0)
if col.Nullable {
flags |= FlagNullable
}
if col.IsPrimaryKey {
flags |= FlagPrimaryKey
}
if col.IsUnique {
flags |= FlagUnique
}
if col.AutoIncrement {
flags |= FlagAutoIncrement
}
colInfo := BinaryColumnInfo{
NameOffset: columnOffsets[i],
DataType: col.Type,
Size: uint32(col.Size),
Flags: flags,
DefaultOffset: defaultOffsets[i],
}
err = binary.Write(file, binary.LittleEndian, &colInfo)
if err != nil {
return err
}
}
// Write string table
stringTableData := stringTable.Bytes()
err = binary.Write(file, binary.LittleEndian, uint32(len(stringTableData)))
if err != nil {
return err
}
_, err = file.Write(stringTableData)
if err != nil {
return err
}
// Write constraints
allConstraints := bsm.getAllConstraints(table)
for _, constraint := range allConstraints {
err = bsm.writeBinaryConstraint(file, constraint, stringTable, stringOffsets)
if err != nil {
return err
}
}
return nil
}
// addStringToTable adds a string to string table and returns offset
func (bsm *BinaryStorageManager) addStringToTable(buffer *bytes.Buffer, offsets map[string]uint32, str string) uint32 {
if offset, exists := offsets[str]; exists {
return offset
}
offset := uint32(buffer.Len())
buffer.WriteString(str)
buffer.WriteByte(0) // Null terminator
offsets[str] = offset
return offset
}
// getAllConstraints collects all constraints for a table
func (bsm *BinaryStorageManager) getAllConstraints(table *types.Table) []*types.Constraint {
var constraints []*types.Constraint
// Add schema constraints
for i := range table.Schema.Constraints {
constraints = append(constraints, &table.Schema.Constraints[i])
}
// Add primary key
if table.PrimaryKey != nil {
constraints = append(constraints, table.PrimaryKey)
}
// Add foreign keys
constraints = append(constraints, table.ForeignKeys...)
// Add unique keys
constraints = append(constraints, table.UniqueKeys...)
// Add check constraints
constraints = append(constraints, table.CheckConstraints...)
return constraints
}
// writeBinaryConstraint writes a constraint in binary format
func (bsm *BinaryStorageManager) writeBinaryConstraint(file *os.File, constraint *types.Constraint, stringTable *bytes.Buffer, stringOffsets map[string]uint32) error {
nameOffset := bsm.addStringToTable(stringTable, stringOffsets, constraint.Name)
constraintInfo := BinaryConstraintInfo{
NameOffset: nameOffset,
Type: constraint.Type,
ColumnCount: uint32(len(constraint.Columns)),
RefColumnCount: uint32(len(constraint.RefColumns)),
}
// Set foreign key specific fields
if constraint.Type == types.ForeignKeyConstraint {
constraintInfo.RefTableOffset = bsm.addStringToTable(stringTable, stringOffsets, constraint.RefTable)
constraintInfo.OnDeleteRule = uint32(bsm.convertStringToRule(constraint.OnDeleteRule))
constraintInfo.OnUpdateRule = uint32(bsm.convertStringToRule(constraint.OnUpdateRule))
}
// Set check constraint specific fields
if constraint.Type == types.CheckConstraint && constraint.CheckExpr != "" {
constraintInfo.CheckExprOffset = bsm.addStringToTable(stringTable, stringOffsets, constraint.CheckExpr)
}
// Calculate checksum
infoBytes := (*[unsafe.Sizeof(constraintInfo)]byte)(unsafe.Pointer(&constraintInfo))[:unsafe.Sizeof(constraintInfo)-8]
constraintInfo.Checksum = crc32.ChecksumIEEE(infoBytes)
err := binary.Write(file, binary.LittleEndian, &constraintInfo)
if err != nil {
return err
}
// Write column name offsets
for _, colName := range constraint.Columns {
offset := bsm.addStringToTable(stringTable, stringOffsets, colName)
err = binary.Write(file, binary.LittleEndian, offset)
if err != nil {
return err
}
}
// Write referenced column name offsets for foreign keys
for _, refColName := range constraint.RefColumns {
offset := bsm.addStringToTable(stringTable, stringOffsets, refColName)
err = binary.Write(file, binary.LittleEndian, offset)
if err != nil {
return err
}
}
return nil
}
// convertStringToRule converts string rule to binary constant
func (bsm *BinaryStorageManager) convertStringToRule(rule string) types.ReferentialAction {
switch rule {
case "CASCADE":
return types.Cascade
case "RESTRICT":
return types.Restrict
case "SET NULL":
return types.SetNull
case "SET DEFAULT":
return types.SetDefault
default:
return types.NoAction
}
}
// migrateFromJSON migrates existing JSON metadata to binary format
func (bsm *BinaryStorageManager) migrateFromJSON() error {
// Load JSON metadata using parent method (commented out for now)
// bsm.StorageManager.loadMetadata(filepath.Dir(bsm.metadataFilePath))
// Save in binary format
return bsm.saveBinaryMetadata()
}
// Close saves binary metadata and closes storage
func (bsm *BinaryStorageManager) Close() error {
// Save binary metadata
if err := bsm.saveBinaryMetadata(); err != nil {
return err
}
// Close base storage
return bsm.StorageManager.Close()
}
// SerializeTupleBinary serializes tuple data in optimized binary format
func (bsm *BinaryStorageManager) SerializeTupleBinary(data map[string]any, schema types.Schema) []byte {
// Get cached schema hash for this table
tableName := "default" // We need table name, but for now use default
schemaHash, exists := bsm.schemaHashCache[tableName]
if !exists {
schemaHash = bsm.calculateSchemaHash(schema)
bsm.schemaHashCache[tableName] = schemaHash
}
// Get buffer from pool to reduce allocations
buffer := bsm.bufferPool.Get().(*bytes.Buffer)
buffer.Reset() // Clear any existing data
defer bsm.bufferPool.Put(buffer)
// Use cache-aligned header for optimal performance (64 bytes)
cacheAlignedHeader := struct {
Magic uint16 // Magic number for format detection
ColumnCount uint16 // Number of columns
DataSize uint32 // Size of data portion
Timestamp uint32 // Creation timestamp
Checksum uint32 // Data integrity check
TupleFlags uint32 // Tuple status flags
SchemaHash uint32 // Schema version hash for validation
Reserved1 uint32 // Reserved for future use
Reserved2 uint64 // Reserved for future use
Reserved3 uint64 // Reserved for future use
Reserved4 uint64 // Reserved for future use
Reserved5 uint64 // Reserved for future use
Reserved6 uint64 // Reserved for future use
Padding [8]byte // Padding to reach 64 bytes
}{
Magic: TupleBinaryMagic,
ColumnCount: uint16(len(schema.Columns)),
Timestamp: uint32(0), // Will be set by caller
TupleFlags: 0, // No special flags initially
SchemaHash: schemaHash, // Use cached hash
}
// Calculate total size for pre-allocation with cache alignment
estimatedSize := CacheAlignedTupleHeaderSize // 64-byte cache-aligned header
for _, col := range schema.Columns {
if value, exists := data[col.Name]; exists && value != nil {
estimatedSize += bsm.estimateValueSizeCompact(value, col.Type)
}
}
// Pre-grow buffer with estimated size
buffer.Grow(estimatedSize)
// Write cache-aligned header placeholder (will be updated later)
headerPos := buffer.Len()
binary.Write(buffer, binary.LittleEndian, &cacheAlignedHeader)
// Get pooled resources to reduce allocations
bitmapSize := (len(schema.Columns) + 7) / 8
nullBitmap := bsm.getBitmapFromPool(bitmapSize)
defer bsm.returnBitmapToPool(nullBitmap)
nonNullValues := bsm.valueArrayPool.Get().([]any)
nonNullValues = nonNullValues[:0] // Reset length but keep capacity
defer bsm.valueArrayPool.Put(nonNullValues)
nonNullTypes := make([]types.DataType, 0, len(schema.Columns))
// Vectorized bitmap processing - process 8 columns at a time for better performance
bsm.processColumnsBitmapVectorized(schema.Columns, data, nullBitmap, &nonNullValues, &nonNullTypes)
// Write null bitmap
dataStart := buffer.Len()
buffer.Write(nullBitmap)
// Write only non-null values (inline for performance)
for i, value := range nonNullValues {
bsm.writeBinaryValueInline(buffer, value, nonNullTypes[i])
}
// Update header with actual data size
dataSize := buffer.Len() - dataStart
cacheAlignedHeader.DataSize = uint32(dataSize)
// Calculate checksum for data (use fast checksum for small data)
dataBytes := buffer.Bytes()[dataStart:]
if len(dataBytes) < 1024 {
// Use simple checksum for small tuples (much faster)
cacheAlignedHeader.Checksum = bsm.fastChecksum(dataBytes)
} else {
// Use CRC32 for larger tuples where integrity is more critical
cacheAlignedHeader.Checksum = crc32.ChecksumIEEE(dataBytes)
}
// Update header in buffer (64 bytes)
headerBytes := (*[CacheAlignedTupleHeaderSize]byte)(unsafe.Pointer(&cacheAlignedHeader))[:]
copy(buffer.Bytes()[headerPos:], headerBytes)
// Copy result before returning buffer to pool
result := make([]byte, buffer.Len())
copy(result, buffer.Bytes())
return result
}
// SerializeTuplesBinaryBulk serializes multiple tuples in bulk for better performance
func (bsm *BinaryStorageManager) SerializeTuplesBinaryBulk(dataSlice []map[string]any, schema types.Schema) [][]byte {
results := make([][]byte, len(dataSlice))
// Get cached schema hash once for all tuples
tableName := "default"
schemaHash, exists := bsm.schemaHashCache[tableName]
if !exists {
schemaHash = bsm.calculateSchemaHash(schema)
bsm.schemaHashCache[tableName] = schemaHash
}
// Adaptive batch sizing based on data characteristics for optimal performance
batchSize := bsm.calculateOptimalBatchSize(len(dataSlice), len(schema.Columns))
for batchStart := 0; batchStart < len(dataSlice); batchStart += batchSize {
batchEnd := min(batchStart+batchSize, len(dataSlice))
// Process batch with vectorized operations when possible
if batchEnd-batchStart >= 4 && len(schema.Columns) <= 16 {
// Use vectorized processing for small schemas
bsm.serializeBatchVectorized(dataSlice[batchStart:batchEnd], results[batchStart:batchEnd], schema, schemaHash)
} else {
// Standard processing for complex schemas
for i := batchStart; i < batchEnd; i++ {
results[i] = bsm.serializeTupleOptimized(dataSlice[i], schema, schemaHash)
}
}
}
return results
}
// serializeTupleOptimized is an optimized version for bulk processing
func (bsm *BinaryStorageManager) serializeTupleOptimized(data map[string]any, schema types.Schema, schemaHash uint32) []byte {
// Use smaller buffer for typical tuple sizes (reduces memory pressure)
buffer := bytes.NewBuffer(make([]byte, 0, 256))
// Pre-build header with known schema hash
cacheAlignedHeader := struct {
Magic uint16 // Magic number for format detection
ColumnCount uint16 // Number of columns
DataSize uint32 // Size of data portion
Timestamp uint32 // Creation timestamp
Checksum uint32 // Data integrity check
TupleFlags uint32 // Tuple status flags
SchemaHash uint32 // Schema version hash for validation
Reserved1 uint32 // Reserved for future use
Reserved2 uint64 // Reserved for future use
Reserved3 uint64 // Reserved for future use
Reserved4 uint64 // Reserved for future use
Reserved5 uint64 // Reserved for future use
Reserved6 uint64 // Reserved for future use
Padding [8]byte // Padding to reach 64 bytes
}{
Magic: TupleBinaryMagic,
ColumnCount: uint16(len(schema.Columns)),
Timestamp: uint32(0),
TupleFlags: 0,
SchemaHash: schemaHash, // Use pre-calculated hash
}
headerPos := buffer.Len()
binary.Write(buffer, binary.LittleEndian, &cacheAlignedHeader)
// Fast null bitmap and value writing
bitmapSize := (len(schema.Columns) + 7) / 8
nullBitmap := make([]byte, bitmapSize)
dataStart := buffer.Len() + bitmapSize
buffer.Write(nullBitmap) // Write placeholder
// Write values directly, updating bitmap as we go
for i, col := range schema.Columns {
if value, exists := data[col.Name]; exists && value != nil {
nullBitmap[i/8] |= 1 << (i % 8)
bsm.writeBinaryValueInline(buffer, value, col.Type)
}
}
// Update bitmap in buffer
copy(buffer.Bytes()[dataStart-bitmapSize:dataStart], nullBitmap)
// Update header
dataSize := buffer.Len() - dataStart
cacheAlignedHeader.DataSize = uint32(dataSize)
cacheAlignedHeader.Checksum = bsm.fastChecksum(buffer.Bytes()[dataStart:])
headerBytes := (*[CacheAlignedTupleHeaderSize]byte)(unsafe.Pointer(&cacheAlignedHeader))[:]
copy(buffer.Bytes()[headerPos:], headerBytes)
return buffer.Bytes()
}
// CompressedSerializeTupleBinary serializes with optional compression for large tuples
func (bsm *BinaryStorageManager) CompressedSerializeTupleBinary(data map[string]any, schema types.Schema, compress bool) []byte {
// Serialize normally first
rawData := bsm.SerializeTupleBinary(data, schema)
// Apply compression if requested and beneficial
if compress && len(rawData) > 512 { // Only compress larger tuples
return bsm.compressTupleData(rawData)
}
return rawData
}
// compressTupleData applies lightweight compression to tuple data
func (bsm *BinaryStorageManager) compressTupleData(data []byte) []byte {
// Simple run-length encoding for repeated bytes (common in null bitmaps)
if len(data) < 64 {
return data // Not worth compressing small data
}
compressed := make([]byte, 0, len(data))
compressed = append(compressed, 0xFF) // Compression marker
for i := 0; i < len(data); {
current := data[i]
count := 1
// Count consecutive identical bytes
for i+count < len(data) && data[i+count] == current && count < 255 {
count++
}
if count > 3 || current == 0 { // Compress runs of 4+ or null bytes
compressed = append(compressed, 0xFE, byte(count), current)
} else {
// Copy literals
for j := 0; j < count; j++ {
compressed = append(compressed, data[i+j])
}
}
i += count
}
// Only return compressed if it's actually smaller
if len(compressed) < len(data) {
return compressed
}
return data
}
// calculateSchemaHash generates a hash for schema validation with optimized loops
func (bsm *BinaryStorageManager) calculateSchemaHash(schema types.Schema) uint32 {
// Optimized hash based on column names and types with unrolled loops
hash := uint32(2166136261) // FNV-1a offset basis
for _, col := range schema.Columns {
// Hash column name with unrolled loop for better performance
nameBytes := []byte(col.Name)
hash = bsm.hashBytesUnrolled(hash, nameBytes)
// Hash column type
hash ^= uint32(col.Type)
hash *= 16777619
}
return hash
}
// hashBytesUnrolled performs FNV-1a hash with loop unrolling for better performance
func (bsm *BinaryStorageManager) hashBytesUnrolled(hash uint32, data []byte) uint32 {
const prime = uint32(16777619)
length := len(data)
i := 0
// Process 4 bytes at a time (unrolled)
for i+3 < length {
hash ^= uint32(data[i])
hash *= prime
hash ^= uint32(data[i+1])