github.com/segmentio/parquet-go@v0.0.0-20230712180008-5d42db8f0d47/writer.go (about) 1 package parquet 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/binary" 7 "fmt" 8 "hash/crc32" 9 "io" 10 "math/bits" 11 "sort" 12 13 "github.com/segmentio/encoding/thrift" 14 "github.com/segmentio/parquet-go/compress" 15 "github.com/segmentio/parquet-go/encoding" 16 "github.com/segmentio/parquet-go/encoding/plain" 17 "github.com/segmentio/parquet-go/format" 18 ) 19 20 // Deprecated: A Writer uses a parquet schema and sequence of Go values to 21 // produce a parquet file to an io.Writer. 22 // 23 // This example showcases a typical use of parquet writers: 24 // 25 // writer := parquet.NewWriter(output) 26 // 27 // for _, row := range rows { 28 // if err := writer.Write(row); err != nil { 29 // ... 30 // } 31 // } 32 // 33 // if err := writer.Close(); err != nil { 34 // ... 35 // } 36 // 37 // The Writer type optimizes for minimal memory usage, each page is written as 38 // soon as it has been filled so only a single page per column needs to be held 39 // in memory and as a result, there are no opportunities to sort rows within an 40 // entire row group. Programs that need to produce parquet files with sorted 41 // row groups should use the Buffer type to buffer and sort the rows prior to 42 // writing them to a Writer. 43 // 44 // For programs building with Go 1.18 or later, the GenericWriter[T] type 45 // supersedes this one. 46 type Writer struct { 47 output io.Writer 48 config *WriterConfig 49 schema *Schema 50 writer *writer 51 rowbuf []Row 52 } 53 54 // NewWriter constructs a parquet writer writing a file to the given io.Writer. 55 // 56 // The function panics if the writer configuration is invalid. Programs that 57 // cannot guarantee the validity of the options passed to NewWriter should 58 // construct the writer configuration independently prior to calling this 59 // function: 60 // 61 // config, err := parquet.NewWriterConfig(options...) 62 // if err != nil { 63 // // handle the configuration error 64 // ... 65 // } else { 66 // // this call to create a writer is guaranteed not to panic 67 // writer := parquet.NewWriter(output, config) 68 // ... 69 // } 70 func NewWriter(output io.Writer, options ...WriterOption) *Writer { 71 config, err := NewWriterConfig(options...) 72 if err != nil { 73 panic(err) 74 } 75 w := &Writer{ 76 output: output, 77 config: config, 78 } 79 if config.Schema != nil { 80 w.configure(config.Schema) 81 } 82 return w 83 } 84 85 func (w *Writer) configure(schema *Schema) { 86 if schema != nil { 87 w.config.Schema = schema 88 w.schema = schema 89 w.writer = newWriter(w.output, w.config) 90 } 91 } 92 93 // Close must be called after all values were produced to the writer in order to 94 // flush all buffers and write the parquet footer. 95 func (w *Writer) Close() error { 96 if w.writer != nil { 97 return w.writer.close() 98 } 99 return nil 100 } 101 102 // Flush flushes all buffers into a row group to the underlying io.Writer. 103 // 104 // Flush is called automatically on Close, it is only useful to call explicitly 105 // if the application needs to limit the size of row groups or wants to produce 106 // multiple row groups per file. 107 // 108 // If the writer attempts to create more than MaxRowGroups row groups the method 109 // returns ErrTooManyRowGroups. 110 func (w *Writer) Flush() error { 111 if w.writer != nil { 112 return w.writer.flush() 113 } 114 return nil 115 } 116 117 // Reset clears the state of the writer without flushing any of the buffers, 118 // and setting the output to the io.Writer passed as argument, allowing the 119 // writer to be reused to produce another parquet file. 120 // 121 // Reset may be called at any time, including after a writer was closed. 122 func (w *Writer) Reset(output io.Writer) { 123 if w.output = output; w.writer != nil { 124 w.writer.reset(w.output) 125 } 126 } 127 128 // Write is called to write another row to the parquet file. 129 // 130 // The method uses the parquet schema configured on w to traverse the Go value 131 // and decompose it into a set of columns and values. If no schema were passed 132 // to NewWriter, it is deducted from the Go type of the row, which then have to 133 // be a struct or pointer to struct. 134 func (w *Writer) Write(row interface{}) error { 135 if w.schema == nil { 136 w.configure(SchemaOf(row)) 137 } 138 if cap(w.rowbuf) == 0 { 139 w.rowbuf = make([]Row, 1) 140 } else { 141 w.rowbuf = w.rowbuf[:1] 142 } 143 defer clearRows(w.rowbuf) 144 w.rowbuf[0] = w.schema.Deconstruct(w.rowbuf[0][:0], row) 145 _, err := w.WriteRows(w.rowbuf) 146 return err 147 } 148 149 // WriteRows is called to write rows to the parquet file. 150 // 151 // The Writer must have been given a schema when NewWriter was called, otherwise 152 // the structure of the parquet file cannot be determined from the row only. 153 // 154 // The row is expected to contain values for each column of the writer's schema, 155 // in the order produced by the parquet.(*Schema).Deconstruct method. 156 func (w *Writer) WriteRows(rows []Row) (int, error) { 157 return w.writer.WriteRows(rows) 158 } 159 160 // WriteRowGroup writes a row group to the parquet file. 161 // 162 // Buffered rows will be flushed prior to writing rows from the group, unless 163 // the row group was empty in which case nothing is written to the file. 164 // 165 // The content of the row group is flushed to the writer; after the method 166 // returns successfully, the row group will be empty and in ready to be reused. 167 func (w *Writer) WriteRowGroup(rowGroup RowGroup) (int64, error) { 168 rowGroupSchema := rowGroup.Schema() 169 switch { 170 case rowGroupSchema == nil: 171 return 0, ErrRowGroupSchemaMissing 172 case w.schema == nil: 173 w.configure(rowGroupSchema) 174 case !nodesAreEqual(w.schema, rowGroupSchema): 175 return 0, ErrRowGroupSchemaMismatch 176 } 177 if err := w.writer.flush(); err != nil { 178 return 0, err 179 } 180 w.writer.configureBloomFilters(rowGroup.ColumnChunks()) 181 rows := rowGroup.Rows() 182 defer rows.Close() 183 n, err := CopyRows(w.writer, rows) 184 if err != nil { 185 return n, err 186 } 187 return w.writer.writeRowGroup(rowGroup.Schema(), rowGroup.SortingColumns()) 188 } 189 190 // ReadRowsFrom reads rows from the reader passed as arguments and writes them 191 // to w. 192 // 193 // This is similar to calling WriteRow repeatedly, but will be more efficient 194 // if optimizations are supported by the reader. 195 func (w *Writer) ReadRowsFrom(rows RowReader) (written int64, err error) { 196 if w.schema == nil { 197 if r, ok := rows.(RowReaderWithSchema); ok { 198 w.configure(r.Schema()) 199 } 200 } 201 if cap(w.rowbuf) < defaultRowBufferSize { 202 w.rowbuf = make([]Row, defaultRowBufferSize) 203 } else { 204 w.rowbuf = w.rowbuf[:cap(w.rowbuf)] 205 } 206 return copyRows(w.writer, rows, w.rowbuf) 207 } 208 209 // Schema returns the schema of rows written by w. 210 // 211 // The returned value will be nil if no schema has yet been configured on w. 212 func (w *Writer) Schema() *Schema { return w.schema } 213 214 // SetKeyValueMetadata sets a key/value pair in the Parquet file metadata. 215 // 216 // Keys are assumed to be unique, if the same key is repeated multiple times the 217 // last value is retained. While the parquet format does not require unique keys, 218 // this design decision was made to optimize for the most common use case where 219 // applications leverage this extension mechanism to associate single values to 220 // keys. This may create incompatibilities with other parquet libraries, or may 221 // cause some key/value pairs to be lost when open parquet files written with 222 // repeated keys. We can revisit this decision if it ever becomes a blocker. 223 func (w *Writer) SetKeyValueMetadata(key, value string) { 224 for i, kv := range w.writer.metadata { 225 if kv.Key == key { 226 kv.Value = value 227 w.writer.metadata[i] = kv 228 return 229 } 230 } 231 w.writer.metadata = append(w.writer.metadata, format.KeyValue{ 232 Key: key, 233 Value: value, 234 }) 235 } 236 237 type writer struct { 238 buffer *bufio.Writer 239 writer offsetTrackingWriter 240 values [][]Value 241 numRows int64 242 maxRows int64 243 244 createdBy string 245 metadata []format.KeyValue 246 247 columns []*writerColumn 248 columnChunk []format.ColumnChunk 249 columnIndex []format.ColumnIndex 250 offsetIndex []format.OffsetIndex 251 252 columnOrders []format.ColumnOrder 253 schemaElements []format.SchemaElement 254 rowGroups []format.RowGroup 255 columnIndexes [][]format.ColumnIndex 256 offsetIndexes [][]format.OffsetIndex 257 sortingColumns []format.SortingColumn 258 } 259 260 func newWriter(output io.Writer, config *WriterConfig) *writer { 261 w := new(writer) 262 if config.WriteBufferSize <= 0 { 263 w.writer.Reset(output) 264 } else { 265 w.buffer = bufio.NewWriterSize(output, config.WriteBufferSize) 266 w.writer.Reset(w.buffer) 267 } 268 w.maxRows = config.MaxRowsPerRowGroup 269 w.createdBy = config.CreatedBy 270 w.metadata = make([]format.KeyValue, 0, len(config.KeyValueMetadata)) 271 for k, v := range config.KeyValueMetadata { 272 w.metadata = append(w.metadata, format.KeyValue{Key: k, Value: v}) 273 } 274 sortKeyValueMetadata(w.metadata) 275 w.sortingColumns = make([]format.SortingColumn, len(config.Sorting.SortingColumns)) 276 277 config.Schema.forEachNode(func(name string, node Node) { 278 nodeType := node.Type() 279 280 repetitionType := (*format.FieldRepetitionType)(nil) 281 if node != config.Schema { // the root has no repetition type 282 repetitionType = fieldRepetitionTypePtrOf(node) 283 } 284 285 // For backward compatibility with older readers, the parquet specification 286 // recommends to set the scale and precision on schema elements when the 287 // column is of logical type decimal. 288 logicalType := nodeType.LogicalType() 289 scale, precision := (*int32)(nil), (*int32)(nil) 290 if logicalType != nil && logicalType.Decimal != nil { 291 scale = &logicalType.Decimal.Scale 292 precision = &logicalType.Decimal.Precision 293 } 294 295 typeLength := (*int32)(nil) 296 if n := int32(nodeType.Length()); n > 0 { 297 typeLength = &n 298 } 299 300 w.schemaElements = append(w.schemaElements, format.SchemaElement{ 301 Type: nodeType.PhysicalType(), 302 TypeLength: typeLength, 303 RepetitionType: repetitionType, 304 Name: name, 305 NumChildren: int32(len(node.Fields())), 306 ConvertedType: nodeType.ConvertedType(), 307 Scale: scale, 308 Precision: precision, 309 LogicalType: logicalType, 310 }) 311 }) 312 313 dataPageType := format.DataPage 314 if config.DataPageVersion == 2 { 315 dataPageType = format.DataPageV2 316 } 317 318 defaultCompression := config.Compression 319 if defaultCompression == nil { 320 defaultCompression = &Uncompressed 321 } 322 323 // Those buffers are scratch space used to generate the page header and 324 // content, they are shared by all column chunks because they are only 325 // used during calls to writeDictionaryPage or writeDataPage, which are 326 // not done concurrently. 327 buffers := new(writerBuffers) 328 329 forEachLeafColumnOf(config.Schema, func(leaf leafColumn) { 330 encoding := encodingOf(leaf.node) 331 dictionary := Dictionary(nil) 332 columnType := leaf.node.Type() 333 columnIndex := int(leaf.columnIndex) 334 compression := leaf.node.Compression() 335 336 if compression == nil { 337 compression = defaultCompression 338 } 339 340 if isDictionaryEncoding(encoding) { 341 dictBuffer := columnType.NewValues( 342 make([]byte, 0, defaultDictBufferSize), 343 nil, 344 ) 345 dictionary = columnType.NewDictionary(columnIndex, 0, dictBuffer) 346 columnType = dictionary.Type() 347 } 348 349 c := &writerColumn{ 350 buffers: buffers, 351 pool: config.ColumnPageBuffers, 352 columnPath: leaf.path, 353 columnType: columnType, 354 columnIndex: columnType.NewColumnIndexer(config.ColumnIndexSizeLimit), 355 columnFilter: searchBloomFilterColumn(config.BloomFilters, leaf.path), 356 compression: compression, 357 dictionary: dictionary, 358 dataPageType: dataPageType, 359 maxRepetitionLevel: leaf.maxRepetitionLevel, 360 maxDefinitionLevel: leaf.maxDefinitionLevel, 361 bufferIndex: int32(leaf.columnIndex), 362 bufferSize: int32(float64(config.PageBufferSize) * 0.98), 363 writePageStats: config.DataPageStatistics, 364 encodings: make([]format.Encoding, 0, 3), 365 // Data pages in version 2 can omit compression when dictionary 366 // encoding is employed; only the dictionary page needs to be 367 // compressed, the data pages are encoded with the hybrid 368 // RLE/Bit-Pack encoding which doesn't benefit from an extra 369 // compression layer. 370 isCompressed: isCompressed(compression) && (dataPageType != format.DataPageV2 || dictionary == nil), 371 } 372 373 c.header.encoder.Reset(c.header.protocol.NewWriter(&buffers.header)) 374 375 if leaf.maxDefinitionLevel > 0 { 376 c.encodings = addEncoding(c.encodings, format.RLE) 377 } 378 379 if isDictionaryEncoding(encoding) { 380 c.encodings = addEncoding(c.encodings, format.Plain) 381 } 382 383 c.encoding = encoding 384 c.encodings = addEncoding(c.encodings, c.encoding.Encoding()) 385 sortPageEncodings(c.encodings) 386 387 w.columns = append(w.columns, c) 388 389 if sortingIndex := searchSortingColumn(config.Sorting.SortingColumns, leaf.path); sortingIndex < len(w.sortingColumns) { 390 w.sortingColumns[sortingIndex] = format.SortingColumn{ 391 ColumnIdx: int32(leaf.columnIndex), 392 Descending: config.Sorting.SortingColumns[sortingIndex].Descending(), 393 NullsFirst: config.Sorting.SortingColumns[sortingIndex].NullsFirst(), 394 } 395 } 396 }) 397 398 // Pre-allocate the backing array so that in most cases where the rows 399 // contain a single value we will hit collocated memory areas when writing 400 // rows to the writer. This won't benefit repeated columns much but in that 401 // case we would just waste a bit of memory which we can afford. 402 values := make([]Value, len(w.columns)) 403 w.values = make([][]Value, len(w.columns)) 404 for i := range values { 405 w.values[i] = values[i : i : i+1] 406 } 407 408 w.columnChunk = make([]format.ColumnChunk, len(w.columns)) 409 w.columnIndex = make([]format.ColumnIndex, len(w.columns)) 410 w.offsetIndex = make([]format.OffsetIndex, len(w.columns)) 411 w.columnOrders = make([]format.ColumnOrder, len(w.columns)) 412 413 for i, c := range w.columns { 414 w.columnChunk[i] = format.ColumnChunk{ 415 MetaData: format.ColumnMetaData{ 416 Type: format.Type(c.columnType.Kind()), 417 Encoding: c.encodings, 418 PathInSchema: c.columnPath, 419 Codec: c.compression.CompressionCodec(), 420 KeyValueMetadata: nil, // TODO 421 }, 422 } 423 } 424 425 for i, c := range w.columns { 426 c.columnChunk = &w.columnChunk[i] 427 c.offsetIndex = &w.offsetIndex[i] 428 } 429 430 for i, c := range w.columns { 431 w.columnOrders[i] = *c.columnType.ColumnOrder() 432 } 433 434 return w 435 } 436 437 func (w *writer) reset(writer io.Writer) { 438 if w.buffer == nil { 439 w.writer.Reset(writer) 440 } else { 441 w.buffer.Reset(writer) 442 w.writer.Reset(w.buffer) 443 } 444 for _, c := range w.columns { 445 c.reset() 446 } 447 for i := range w.rowGroups { 448 w.rowGroups[i] = format.RowGroup{} 449 } 450 for i := range w.columnIndexes { 451 w.columnIndexes[i] = nil 452 } 453 for i := range w.offsetIndexes { 454 w.offsetIndexes[i] = nil 455 } 456 w.rowGroups = w.rowGroups[:0] 457 w.columnIndexes = w.columnIndexes[:0] 458 w.offsetIndexes = w.offsetIndexes[:0] 459 } 460 461 func (w *writer) close() error { 462 if err := w.writeFileHeader(); err != nil { 463 return err 464 } 465 if err := w.flush(); err != nil { 466 return err 467 } 468 if err := w.writeFileFooter(); err != nil { 469 return err 470 } 471 if w.buffer != nil { 472 return w.buffer.Flush() 473 } 474 return nil 475 } 476 477 func (w *writer) flush() error { 478 _, err := w.writeRowGroup(nil, nil) 479 return err 480 } 481 482 func (w *writer) writeFileHeader() error { 483 if w.writer.writer == nil { 484 return io.ErrClosedPipe 485 } 486 if w.writer.offset == 0 { 487 _, err := w.writer.WriteString("PAR1") 488 return err 489 } 490 return nil 491 } 492 493 func (w *writer) configureBloomFilters(columnChunks []ColumnChunk) { 494 for i, c := range w.columns { 495 if c.columnFilter != nil { 496 c.resizeBloomFilter(columnChunks[i].NumValues()) 497 } 498 } 499 } 500 501 func (w *writer) writeFileFooter() error { 502 // The page index is composed of two sections: column and offset indexes. 503 // They are written after the row groups, right before the footer (which 504 // is written by the parent Writer.Close call). 505 // 506 // This section both writes the page index and generates the values of 507 // ColumnIndexOffset, ColumnIndexLength, OffsetIndexOffset, and 508 // OffsetIndexLength in the corresponding columns of the file metadata. 509 // 510 // Note: the page index is always written, even if we created data pages v1 511 // because the parquet format is backward compatible in this case. Older 512 // readers will simply ignore this section since they do not know how to 513 // decode its content, nor have loaded any metadata to reference it. 514 protocol := new(thrift.CompactProtocol) 515 encoder := thrift.NewEncoder(protocol.NewWriter(&w.writer)) 516 517 for i, columnIndexes := range w.columnIndexes { 518 rowGroup := &w.rowGroups[i] 519 for j := range columnIndexes { 520 column := &rowGroup.Columns[j] 521 column.ColumnIndexOffset = w.writer.offset 522 if err := encoder.Encode(&columnIndexes[j]); err != nil { 523 return err 524 } 525 column.ColumnIndexLength = int32(w.writer.offset - column.ColumnIndexOffset) 526 } 527 } 528 529 for i, offsetIndexes := range w.offsetIndexes { 530 rowGroup := &w.rowGroups[i] 531 for j := range offsetIndexes { 532 column := &rowGroup.Columns[j] 533 column.OffsetIndexOffset = w.writer.offset 534 if err := encoder.Encode(&offsetIndexes[j]); err != nil { 535 return err 536 } 537 column.OffsetIndexLength = int32(w.writer.offset - column.OffsetIndexOffset) 538 } 539 } 540 541 numRows := int64(0) 542 for rowGroupIndex := range w.rowGroups { 543 numRows += w.rowGroups[rowGroupIndex].NumRows 544 } 545 546 footer, err := thrift.Marshal(new(thrift.CompactProtocol), &format.FileMetaData{ 547 Version: 1, 548 Schema: w.schemaElements, 549 NumRows: numRows, 550 RowGroups: w.rowGroups, 551 KeyValueMetadata: w.metadata, 552 CreatedBy: w.createdBy, 553 ColumnOrders: w.columnOrders, 554 }) 555 if err != nil { 556 return err 557 } 558 559 length := len(footer) 560 footer = append(footer, 0, 0, 0, 0) 561 footer = append(footer, "PAR1"...) 562 binary.LittleEndian.PutUint32(footer[length:], uint32(length)) 563 564 _, err = w.writer.Write(footer) 565 return err 566 } 567 568 func (w *writer) writeRowGroup(rowGroupSchema *Schema, rowGroupSortingColumns []SortingColumn) (int64, error) { 569 numRows := w.columns[0].totalRowCount() 570 if numRows == 0 { 571 return 0, nil 572 } 573 574 if len(w.rowGroups) == MaxRowGroups { 575 return 0, ErrTooManyRowGroups 576 } 577 578 defer func() { 579 w.numRows = 0 580 for _, c := range w.columns { 581 c.reset() 582 } 583 for i := range w.columnIndex { 584 w.columnIndex[i] = format.ColumnIndex{} 585 } 586 }() 587 588 for _, c := range w.columns { 589 if err := c.flush(); err != nil { 590 return 0, err 591 } 592 if err := c.flushFilterPages(); err != nil { 593 return 0, err 594 } 595 } 596 597 if err := w.writeFileHeader(); err != nil { 598 return 0, err 599 } 600 fileOffset := w.writer.offset 601 602 for _, c := range w.columns { 603 if len(c.filter) > 0 { 604 c.columnChunk.MetaData.BloomFilterOffset = w.writer.offset 605 if err := c.writeBloomFilter(&w.writer); err != nil { 606 return 0, err 607 } 608 } 609 } 610 611 for i, c := range w.columns { 612 w.columnIndex[i] = format.ColumnIndex(c.columnIndex.ColumnIndex()) 613 614 if c.dictionary != nil { 615 c.columnChunk.MetaData.DictionaryPageOffset = w.writer.offset 616 if err := c.writeDictionaryPage(&w.writer, c.dictionary); err != nil { 617 return 0, fmt.Errorf("writing dictionary page of row group colum %d: %w", i, err) 618 } 619 } 620 621 dataPageOffset := w.writer.offset 622 c.columnChunk.MetaData.DataPageOffset = dataPageOffset 623 for j := range c.offsetIndex.PageLocations { 624 c.offsetIndex.PageLocations[j].Offset += dataPageOffset 625 } 626 627 for _, page := range c.pages { 628 if _, err := io.Copy(&w.writer, page); err != nil { 629 return 0, fmt.Errorf("writing buffered pages of row group column %d: %w", i, err) 630 } 631 } 632 } 633 634 totalByteSize := int64(0) 635 totalCompressedSize := int64(0) 636 637 for i := range w.columnChunk { 638 c := &w.columnChunk[i].MetaData 639 sortPageEncodingStats(c.EncodingStats) 640 totalByteSize += int64(c.TotalUncompressedSize) 641 totalCompressedSize += int64(c.TotalCompressedSize) 642 } 643 644 sortingColumns := w.sortingColumns 645 if len(sortingColumns) == 0 && len(rowGroupSortingColumns) > 0 { 646 sortingColumns = make([]format.SortingColumn, 0, len(rowGroupSortingColumns)) 647 forEachLeafColumnOf(rowGroupSchema, func(leaf leafColumn) { 648 if sortingIndex := searchSortingColumn(rowGroupSortingColumns, leaf.path); sortingIndex < len(sortingColumns) { 649 sortingColumns[sortingIndex] = format.SortingColumn{ 650 ColumnIdx: int32(leaf.columnIndex), 651 Descending: rowGroupSortingColumns[sortingIndex].Descending(), 652 NullsFirst: rowGroupSortingColumns[sortingIndex].NullsFirst(), 653 } 654 } 655 }) 656 } 657 658 columns := make([]format.ColumnChunk, len(w.columnChunk)) 659 copy(columns, w.columnChunk) 660 661 columnIndex := make([]format.ColumnIndex, len(w.columnIndex)) 662 copy(columnIndex, w.columnIndex) 663 664 offsetIndex := make([]format.OffsetIndex, len(w.offsetIndex)) 665 copy(offsetIndex, w.offsetIndex) 666 667 for i := range columns { 668 c := &columns[i] 669 c.MetaData.EncodingStats = make([]format.PageEncodingStats, len(c.MetaData.EncodingStats)) 670 copy(c.MetaData.EncodingStats, w.columnChunk[i].MetaData.EncodingStats) 671 } 672 673 for i := range offsetIndex { 674 c := &offsetIndex[i] 675 c.PageLocations = make([]format.PageLocation, len(c.PageLocations)) 676 copy(c.PageLocations, w.offsetIndex[i].PageLocations) 677 } 678 679 w.rowGroups = append(w.rowGroups, format.RowGroup{ 680 Columns: columns, 681 TotalByteSize: totalByteSize, 682 NumRows: numRows, 683 SortingColumns: sortingColumns, 684 FileOffset: fileOffset, 685 TotalCompressedSize: totalCompressedSize, 686 Ordinal: int16(len(w.rowGroups)), 687 }) 688 689 w.columnIndexes = append(w.columnIndexes, columnIndex) 690 w.offsetIndexes = append(w.offsetIndexes, offsetIndex) 691 return numRows, nil 692 } 693 694 func (w *writer) WriteRows(rows []Row) (int, error) { 695 return w.writeRows(len(rows), func(start, end int) (int, error) { 696 defer func() { 697 for i, values := range w.values { 698 clearValues(values) 699 w.values[i] = values[:0] 700 } 701 }() 702 703 // TODO: if an error occurs in this method the writer may be left in an 704 // partially functional state. Applications are not expected to continue 705 // using the writer after getting an error, but maybe we could ensure that 706 // we are preventing further use as well? 707 for _, row := range rows[start:end] { 708 row.Range(func(columnIndex int, columnValues []Value) bool { 709 w.values[columnIndex] = append(w.values[columnIndex], columnValues...) 710 return true 711 }) 712 } 713 714 for i, values := range w.values { 715 if len(values) > 0 { 716 if err := w.columns[i].writeRows(values); err != nil { 717 return 0, err 718 } 719 } 720 } 721 722 return end - start, nil 723 }) 724 } 725 726 func (w *writer) writeRows(numRows int, write func(i, j int) (int, error)) (int, error) { 727 written := 0 728 729 for written < numRows { 730 remain := w.maxRows - w.numRows 731 length := numRows - written 732 733 if remain == 0 { 734 remain = w.maxRows 735 736 if err := w.flush(); err != nil { 737 return written, err 738 } 739 } 740 741 if remain < int64(length) { 742 length = int(remain) 743 } 744 745 // Since the writer cannot flush pages across row boundaries, calls to 746 // WriteRows with very large slices can result in greatly exceeding the 747 // target page size. To set a limit to the impact of these large writes 748 // we chunk the input in slices of 64 rows. 749 // 750 // Note that this mechanism isn't perfect; for example, values may hold 751 // large byte slices which could still cause the column buffers to grow 752 // beyond the target page size. 753 const maxRowsPerWrite = 64 754 if length > maxRowsPerWrite { 755 length = maxRowsPerWrite 756 } 757 758 n, err := write(written, written+length) 759 written += n 760 w.numRows += int64(n) 761 if err != nil { 762 return written, err 763 } 764 } 765 766 return written, nil 767 } 768 769 // The WriteValues method is intended to work in pair with WritePage to allow 770 // programs to target writing values to specific columns of of the writer. 771 func (w *writer) WriteValues(values []Value) (numValues int, err error) { 772 return w.columns[values[0].Column()].WriteValues(values) 773 } 774 775 // One writerBuffers is used by each writer instance, the memory buffers here 776 // are shared by all columns of the writer because serialization is not done 777 // concurrently, which helps keep memory utilization low, both in the total 778 // footprint and GC cost. 779 // 780 // The type also exposes helper methods to facilitate the generation of parquet 781 // pages. A scratch space is used when serialization requires combining multiple 782 // buffers or compressing the page data, with double-buffering technique being 783 // employed by swapping the scratch and page buffers to minimize memory copies. 784 type writerBuffers struct { 785 header bytes.Buffer // buffer where page headers are encoded 786 repetitions []byte // buffer used to encode repetition levels 787 definitions []byte // buffer used to encode definition levels 788 page []byte // page buffer holding the page data 789 scratch []byte // scratch space used for compression 790 } 791 792 func (wb *writerBuffers) crc32() (checksum uint32) { 793 checksum = crc32.Update(checksum, crc32.IEEETable, wb.repetitions) 794 checksum = crc32.Update(checksum, crc32.IEEETable, wb.definitions) 795 checksum = crc32.Update(checksum, crc32.IEEETable, wb.page) 796 return checksum 797 } 798 799 func (wb *writerBuffers) size() int { 800 return len(wb.repetitions) + len(wb.definitions) + len(wb.page) 801 } 802 803 func (wb *writerBuffers) reset() { 804 wb.repetitions = wb.repetitions[:0] 805 wb.definitions = wb.definitions[:0] 806 wb.page = wb.page[:0] 807 } 808 809 func encodeLevels(dst, src []byte, maxLevel byte) ([]byte, error) { 810 bitWidth := bits.Len8(maxLevel) 811 return levelEncodingsRLE[bitWidth-1].EncodeLevels(dst, src) 812 } 813 814 func (wb *writerBuffers) encodeRepetitionLevels(page Page, maxRepetitionLevel byte) (err error) { 815 wb.repetitions, err = encodeLevels(wb.repetitions, page.RepetitionLevels(), maxRepetitionLevel) 816 return 817 } 818 819 func (wb *writerBuffers) encodeDefinitionLevels(page Page, maxDefinitionLevel byte) (err error) { 820 wb.definitions, err = encodeLevels(wb.definitions, page.DefinitionLevels(), maxDefinitionLevel) 821 return 822 } 823 824 func (wb *writerBuffers) prependLevelsToDataPageV1(maxRepetitionLevel, maxDefinitionLevel byte) { 825 hasRepetitionLevels := maxRepetitionLevel > 0 826 hasDefinitionLevels := maxDefinitionLevel > 0 827 828 if hasRepetitionLevels || hasDefinitionLevels { 829 wb.scratch = wb.scratch[:0] 830 // In data pages v1, the repetition and definition levels are prefixed 831 // with the 4 bytes length of the sections. While the parquet-format 832 // documentation indicates that the length prefix is part of the hybrid 833 // RLE/Bit-Pack encoding, this is the only condition where it is used 834 // so we treat it as a special case rather than implementing it in the 835 // encoding. 836 // 837 // Reference https://github.com/apache/parquet-format/blob/master/Encodings.md#run-length-encoding--bit-packing-hybrid-rle--3 838 if hasRepetitionLevels { 839 wb.scratch = plain.AppendInt32(wb.scratch, int32(len(wb.repetitions))) 840 wb.scratch = append(wb.scratch, wb.repetitions...) 841 wb.repetitions = wb.repetitions[:0] 842 } 843 if hasDefinitionLevels { 844 wb.scratch = plain.AppendInt32(wb.scratch, int32(len(wb.definitions))) 845 wb.scratch = append(wb.scratch, wb.definitions...) 846 wb.definitions = wb.definitions[:0] 847 } 848 wb.scratch = append(wb.scratch, wb.page...) 849 wb.swapPageAndScratchBuffers() 850 } 851 } 852 853 func (wb *writerBuffers) encode(page Page, enc encoding.Encoding) (err error) { 854 pageType := page.Type() 855 pageData := page.Data() 856 wb.page, err = pageType.Encode(wb.page[:0], pageData, enc) 857 return err 858 } 859 860 func (wb *writerBuffers) compress(codec compress.Codec) (err error) { 861 wb.scratch, err = codec.Encode(wb.scratch[:0], wb.page) 862 wb.swapPageAndScratchBuffers() 863 return err 864 } 865 866 func (wb *writerBuffers) swapPageAndScratchBuffers() { 867 wb.page, wb.scratch = wb.scratch, wb.page[:0] 868 } 869 870 type writerColumn struct { 871 pool BufferPool 872 pages []io.ReadWriteSeeker 873 874 columnPath columnPath 875 columnType Type 876 columnIndex ColumnIndexer 877 columnBuffer ColumnBuffer 878 columnFilter BloomFilterColumn 879 encoding encoding.Encoding 880 compression compress.Codec 881 dictionary Dictionary 882 883 dataPageType format.PageType 884 maxRepetitionLevel byte 885 maxDefinitionLevel byte 886 887 buffers *writerBuffers 888 889 header struct { 890 protocol thrift.CompactProtocol 891 encoder thrift.Encoder 892 } 893 894 filter []byte 895 numRows int64 896 bufferIndex int32 897 bufferSize int32 898 writePageStats bool 899 isCompressed bool 900 encodings []format.Encoding 901 902 columnChunk *format.ColumnChunk 903 offsetIndex *format.OffsetIndex 904 } 905 906 func (c *writerColumn) reset() { 907 if c.columnBuffer != nil { 908 c.columnBuffer.Reset() 909 } 910 if c.columnIndex != nil { 911 c.columnIndex.Reset() 912 } 913 if c.dictionary != nil { 914 c.dictionary.Reset() 915 } 916 for _, page := range c.pages { 917 c.pool.PutBuffer(page) 918 } 919 for i := range c.pages { 920 c.pages[i] = nil 921 } 922 c.pages = c.pages[:0] 923 // Bloom filters may change in size between row groups, but we retain the 924 // buffer to avoid reallocating large memory blocks. 925 c.filter = c.filter[:0] 926 c.numRows = 0 927 // Reset the fields of column chunks that change between row groups, 928 // but keep the ones that remain unchanged. 929 c.columnChunk.MetaData.NumValues = 0 930 c.columnChunk.MetaData.TotalUncompressedSize = 0 931 c.columnChunk.MetaData.TotalCompressedSize = 0 932 c.columnChunk.MetaData.DataPageOffset = 0 933 c.columnChunk.MetaData.DictionaryPageOffset = 0 934 c.columnChunk.MetaData.Statistics = format.Statistics{} 935 c.columnChunk.MetaData.EncodingStats = c.columnChunk.MetaData.EncodingStats[:0] 936 c.columnChunk.MetaData.BloomFilterOffset = 0 937 c.offsetIndex.PageLocations = c.offsetIndex.PageLocations[:0] 938 } 939 940 func (c *writerColumn) totalRowCount() int64 { 941 n := c.numRows 942 if c.columnBuffer != nil { 943 n += int64(c.columnBuffer.Len()) 944 } 945 return n 946 } 947 948 func (c *writerColumn) flush() (err error) { 949 if c.columnBuffer.Len() > 0 { 950 defer c.columnBuffer.Reset() 951 _, err = c.writeDataPage(c.columnBuffer.Page()) 952 } 953 return err 954 } 955 956 func (c *writerColumn) flushFilterPages() error { 957 if c.columnFilter == nil { 958 return nil 959 } 960 961 // If there is a dictionary, it contains all the values that we need to 962 // write to the filter. 963 if dict := c.dictionary; dict != nil { 964 // Need to always attempt to resize the filter, as the writer might 965 // be reused after resetting which would have reset the length of 966 // the filter to 0. 967 c.resizeBloomFilter(int64(dict.Len())) 968 return c.writePageToFilter(dict.Page()) 969 } 970 971 // When the filter was already allocated, pages have been written to it as 972 // they were seen by the column writer. 973 if len(c.filter) > 0 { 974 return nil 975 } 976 977 // When the filter was not allocated, the writer did not know how many 978 // values were going to be seen and therefore could not properly size the 979 // filter ahead of time. In this case, we read back all the pages that we 980 // have encoded and copy their values back to the filter. 981 // 982 // A prior implementation of the column writer used to create in-memory 983 // copies of the pages to avoid this decoding step; however, this unbounded 984 // allocation caused memory exhaustion in production applications. CPU being 985 // a somewhat more stretchable resource, we prefer spending time on this 986 // decoding step than having to trigger incident response when production 987 // systems are getting OOM-Killed. 988 c.resizeBloomFilter(c.columnChunk.MetaData.NumValues) 989 990 column := &Column{ 991 // Set all the fields required by the decodeDataPage* methods. 992 typ: c.columnType, 993 encoding: c.encoding, 994 compression: c.compression, 995 maxRepetitionLevel: c.maxRepetitionLevel, 996 maxDefinitionLevel: c.maxDefinitionLevel, 997 index: int16(c.bufferIndex), 998 } 999 1000 rbuf, pool := getBufioReader(nil, 1024) 1001 pbuf := (*buffer)(nil) 1002 defer func() { 1003 putBufioReader(rbuf, pool) 1004 if pbuf != nil { 1005 pbuf.unref() 1006 } 1007 }() 1008 1009 decoder := thrift.NewDecoder(c.header.protocol.NewReader(rbuf)) 1010 1011 for _, p := range c.pages { 1012 rbuf.Reset(p) 1013 1014 header := new(format.PageHeader) 1015 if err := decoder.Decode(header); err != nil { 1016 return err 1017 } 1018 1019 if pbuf != nil { 1020 pbuf.unref() 1021 } 1022 pbuf = buffers.get(int(header.CompressedPageSize)) 1023 if _, err := io.ReadFull(rbuf, pbuf.data); err != nil { 1024 return err 1025 } 1026 if _, err := p.Seek(0, io.SeekStart); err != nil { 1027 return err 1028 } 1029 1030 var page Page 1031 var err error 1032 1033 switch header.Type { 1034 case format.DataPage: 1035 page, err = column.decodeDataPageV1(DataPageHeaderV1{header.DataPageHeader}, pbuf, nil, header.UncompressedPageSize) 1036 case format.DataPageV2: 1037 page, err = column.decodeDataPageV2(DataPageHeaderV2{header.DataPageHeaderV2}, pbuf, nil, header.UncompressedPageSize) 1038 } 1039 if page != nil { 1040 err = c.writePageToFilter(page) 1041 Release(page) 1042 } 1043 if err != nil { 1044 return err 1045 } 1046 } 1047 1048 return nil 1049 } 1050 1051 func (c *writerColumn) resizeBloomFilter(numValues int64) { 1052 filterSize := c.columnFilter.Size(numValues) 1053 if cap(c.filter) < filterSize { 1054 c.filter = make([]byte, filterSize) 1055 } else { 1056 c.filter = c.filter[:filterSize] 1057 for i := range c.filter { 1058 c.filter[i] = 0 1059 } 1060 } 1061 } 1062 1063 func (c *writerColumn) newColumnBuffer() ColumnBuffer { 1064 column := c.columnType.NewColumnBuffer(int(c.bufferIndex), c.columnType.EstimateNumValues(int(c.bufferSize))) 1065 switch { 1066 case c.maxRepetitionLevel > 0: 1067 column = newRepeatedColumnBuffer(column, c.maxRepetitionLevel, c.maxDefinitionLevel, nullsGoLast) 1068 case c.maxDefinitionLevel > 0: 1069 column = newOptionalColumnBuffer(column, c.maxDefinitionLevel, nullsGoLast) 1070 } 1071 return column 1072 } 1073 1074 func (c *writerColumn) writeRows(rows []Value) error { 1075 if c.columnBuffer == nil { 1076 // Lazily create the row group column so we don't need to allocate it if 1077 // rows are not written individually to the column. 1078 c.columnBuffer = c.newColumnBuffer() 1079 } 1080 if _, err := c.columnBuffer.WriteValues(rows); err != nil { 1081 return err 1082 } 1083 if c.columnBuffer.Size() >= int64(c.bufferSize) { 1084 return c.flush() 1085 } 1086 return nil 1087 } 1088 1089 func (c *writerColumn) WriteValues(values []Value) (numValues int, err error) { 1090 if c.columnBuffer == nil { 1091 c.columnBuffer = c.newColumnBuffer() 1092 } 1093 return c.columnBuffer.WriteValues(values) 1094 } 1095 1096 func (c *writerColumn) writeBloomFilter(w io.Writer) error { 1097 e := thrift.NewEncoder(c.header.protocol.NewWriter(w)) 1098 h := bloomFilterHeader(c.columnFilter) 1099 h.NumBytes = int32(len(c.filter)) 1100 if err := e.Encode(&h); err != nil { 1101 return err 1102 } 1103 _, err := w.Write(c.filter) 1104 return err 1105 } 1106 1107 func (c *writerColumn) writeDataPage(page Page) (int64, error) { 1108 numValues := page.NumValues() 1109 if numValues == 0 { 1110 return 0, nil 1111 } 1112 1113 buf := c.buffers 1114 buf.reset() 1115 1116 if c.maxRepetitionLevel > 0 { 1117 buf.encodeRepetitionLevels(page, c.maxRepetitionLevel) 1118 } 1119 if c.maxDefinitionLevel > 0 { 1120 buf.encodeDefinitionLevels(page, c.maxDefinitionLevel) 1121 } 1122 1123 if err := buf.encode(page, c.encoding); err != nil { 1124 return 0, fmt.Errorf("encoding parquet data page: %w", err) 1125 } 1126 if c.dataPageType == format.DataPage { 1127 buf.prependLevelsToDataPageV1(c.maxDefinitionLevel, c.maxDefinitionLevel) 1128 } 1129 1130 uncompressedPageSize := buf.size() 1131 if c.isCompressed { 1132 if err := buf.compress(c.compression); err != nil { 1133 return 0, fmt.Errorf("compressing parquet data page: %w", err) 1134 } 1135 } 1136 1137 if page.Dictionary() == nil && len(c.filter) > 0 { 1138 // When the writer knows the number of values in advance (e.g. when 1139 // writing a full row group), the filter encoding is set and the page 1140 // can be directly applied to the filter, which minimizes memory usage 1141 // since there is no need to buffer the values in order to determine 1142 // the size of the filter. 1143 if err := c.writePageToFilter(page); err != nil { 1144 return 0, err 1145 } 1146 } 1147 1148 statistics := format.Statistics{} 1149 if c.writePageStats { 1150 statistics = c.makePageStatistics(page) 1151 } 1152 1153 pageHeader := &format.PageHeader{ 1154 Type: c.dataPageType, 1155 UncompressedPageSize: int32(uncompressedPageSize), 1156 CompressedPageSize: int32(buf.size()), 1157 CRC: int32(buf.crc32()), 1158 } 1159 1160 numRows := page.NumRows() 1161 numNulls := page.NumNulls() 1162 switch c.dataPageType { 1163 case format.DataPage: 1164 pageHeader.DataPageHeader = &format.DataPageHeader{ 1165 NumValues: int32(numValues), 1166 Encoding: c.encoding.Encoding(), 1167 DefinitionLevelEncoding: format.RLE, 1168 RepetitionLevelEncoding: format.RLE, 1169 Statistics: statistics, 1170 } 1171 case format.DataPageV2: 1172 pageHeader.DataPageHeaderV2 = &format.DataPageHeaderV2{ 1173 NumValues: int32(numValues), 1174 NumNulls: int32(numNulls), 1175 NumRows: int32(numRows), 1176 Encoding: c.encoding.Encoding(), 1177 DefinitionLevelsByteLength: int32(len(buf.definitions)), 1178 RepetitionLevelsByteLength: int32(len(buf.repetitions)), 1179 IsCompressed: &c.isCompressed, 1180 Statistics: statistics, 1181 } 1182 } 1183 1184 buf.header.Reset() 1185 if err := c.header.encoder.Encode(pageHeader); err != nil { 1186 return 0, err 1187 } 1188 1189 size := int64(buf.header.Len()) + 1190 int64(len(buf.repetitions)) + 1191 int64(len(buf.definitions)) + 1192 int64(len(buf.page)) 1193 1194 err := c.writePageTo(size, func(output io.Writer) (written int64, err error) { 1195 for _, data := range [...][]byte{ 1196 buf.header.Bytes(), 1197 buf.repetitions, 1198 buf.definitions, 1199 buf.page, 1200 } { 1201 wn, err := output.Write(data) 1202 written += int64(wn) 1203 if err != nil { 1204 return written, err 1205 } 1206 } 1207 return written, nil 1208 }) 1209 if err != nil { 1210 return 0, err 1211 } 1212 1213 c.recordPageStats(int32(buf.header.Len()), pageHeader, page) 1214 return numValues, nil 1215 } 1216 1217 func (c *writerColumn) writeDictionaryPage(output io.Writer, dict Dictionary) (err error) { 1218 buf := c.buffers 1219 buf.reset() 1220 1221 if err := buf.encode(dict.Page(), &Plain); err != nil { 1222 return fmt.Errorf("writing parquet dictionary page: %w", err) 1223 } 1224 1225 uncompressedPageSize := buf.size() 1226 if isCompressed(c.compression) { 1227 if err := buf.compress(c.compression); err != nil { 1228 return fmt.Errorf("copmressing parquet dictionary page: %w", err) 1229 } 1230 } 1231 1232 pageHeader := &format.PageHeader{ 1233 Type: format.DictionaryPage, 1234 UncompressedPageSize: int32(uncompressedPageSize), 1235 CompressedPageSize: int32(buf.size()), 1236 CRC: int32(buf.crc32()), 1237 DictionaryPageHeader: &format.DictionaryPageHeader{ 1238 NumValues: int32(dict.Len()), 1239 Encoding: format.Plain, 1240 IsSorted: false, 1241 }, 1242 } 1243 1244 header := &c.buffers.header 1245 header.Reset() 1246 if err := c.header.encoder.Encode(pageHeader); err != nil { 1247 return err 1248 } 1249 if _, err := output.Write(header.Bytes()); err != nil { 1250 return err 1251 } 1252 if _, err := output.Write(buf.page); err != nil { 1253 return err 1254 } 1255 c.recordPageStats(int32(header.Len()), pageHeader, nil) 1256 return nil 1257 } 1258 1259 func (w *writerColumn) writePageToFilter(page Page) (err error) { 1260 pageType := page.Type() 1261 pageData := page.Data() 1262 w.filter, err = pageType.Encode(w.filter, pageData, w.columnFilter.Encoding()) 1263 return err 1264 } 1265 1266 func (c *writerColumn) writePageTo(size int64, writeTo func(io.Writer) (int64, error)) error { 1267 buffer := c.pool.GetBuffer() 1268 defer func() { 1269 if buffer != nil { 1270 c.pool.PutBuffer(buffer) 1271 } 1272 }() 1273 written, err := writeTo(buffer) 1274 if err != nil { 1275 return err 1276 } 1277 if written != size { 1278 return fmt.Errorf("writing parquet column page expected %dB but got %dB: %w", size, written, io.ErrShortWrite) 1279 } 1280 offset, err := buffer.Seek(0, io.SeekStart) 1281 if err != nil { 1282 return err 1283 } 1284 if offset != 0 { 1285 return fmt.Errorf("resetting parquet page buffer to the start expected offset zero but got %d", offset) 1286 } 1287 c.pages, buffer = append(c.pages, buffer), nil 1288 return nil 1289 } 1290 1291 func (c *writerColumn) makePageStatistics(page Page) format.Statistics { 1292 numNulls := page.NumNulls() 1293 minValue, maxValue, _ := page.Bounds() 1294 minValueBytes := minValue.Bytes() 1295 maxValueBytes := maxValue.Bytes() 1296 return format.Statistics{ 1297 Min: minValueBytes, // deprecated 1298 Max: maxValueBytes, // deprecated 1299 NullCount: numNulls, 1300 MinValue: minValueBytes, 1301 MaxValue: maxValueBytes, 1302 } 1303 } 1304 1305 func (c *writerColumn) recordPageStats(headerSize int32, header *format.PageHeader, page Page) { 1306 uncompressedSize := headerSize + header.UncompressedPageSize 1307 compressedSize := headerSize + header.CompressedPageSize 1308 1309 if page != nil { 1310 numNulls := page.NumNulls() 1311 numValues := page.NumValues() 1312 minValue, maxValue, pageHasBounds := page.Bounds() 1313 c.columnIndex.IndexPage(numValues, numNulls, minValue, maxValue) 1314 c.columnChunk.MetaData.NumValues += numValues 1315 c.columnChunk.MetaData.Statistics.NullCount += numNulls 1316 1317 if pageHasBounds { 1318 var existingMaxValue, existingMinValue Value 1319 1320 if c.columnChunk.MetaData.Statistics.MaxValue != nil && c.columnChunk.MetaData.Statistics.MinValue != nil { 1321 existingMaxValue = c.columnType.Kind().Value(c.columnChunk.MetaData.Statistics.MaxValue) 1322 existingMinValue = c.columnType.Kind().Value(c.columnChunk.MetaData.Statistics.MinValue) 1323 } 1324 1325 if existingMaxValue.isNull() || c.columnType.Compare(maxValue, existingMaxValue) > 0 { 1326 c.columnChunk.MetaData.Statistics.MaxValue = maxValue.Bytes() 1327 } 1328 1329 if existingMinValue.isNull() || c.columnType.Compare(minValue, existingMinValue) < 0 { 1330 c.columnChunk.MetaData.Statistics.MinValue = minValue.Bytes() 1331 } 1332 } 1333 1334 c.offsetIndex.PageLocations = append(c.offsetIndex.PageLocations, format.PageLocation{ 1335 Offset: c.columnChunk.MetaData.TotalCompressedSize, 1336 CompressedPageSize: compressedSize, 1337 FirstRowIndex: c.numRows, 1338 }) 1339 1340 c.numRows += page.NumRows() 1341 } 1342 1343 pageType := header.Type 1344 encoding := format.Encoding(-1) 1345 switch pageType { 1346 case format.DataPageV2: 1347 encoding = header.DataPageHeaderV2.Encoding 1348 case format.DataPage: 1349 encoding = header.DataPageHeader.Encoding 1350 case format.DictionaryPage: 1351 encoding = header.DictionaryPageHeader.Encoding 1352 } 1353 1354 c.columnChunk.MetaData.TotalUncompressedSize += int64(uncompressedSize) 1355 c.columnChunk.MetaData.TotalCompressedSize += int64(compressedSize) 1356 c.columnChunk.MetaData.EncodingStats = addPageEncodingStats(c.columnChunk.MetaData.EncodingStats, format.PageEncodingStats{ 1357 PageType: pageType, 1358 Encoding: encoding, 1359 Count: 1, 1360 }) 1361 } 1362 1363 func addEncoding(encodings []format.Encoding, add format.Encoding) []format.Encoding { 1364 for _, enc := range encodings { 1365 if enc == add { 1366 return encodings 1367 } 1368 } 1369 return append(encodings, add) 1370 } 1371 1372 func addPageEncodingStats(stats []format.PageEncodingStats, pages ...format.PageEncodingStats) []format.PageEncodingStats { 1373 addPages: 1374 for _, add := range pages { 1375 for i, st := range stats { 1376 if st.PageType == add.PageType && st.Encoding == add.Encoding { 1377 stats[i].Count += add.Count 1378 continue addPages 1379 } 1380 } 1381 stats = append(stats, add) 1382 } 1383 return stats 1384 } 1385 1386 func sortPageEncodings(encodings []format.Encoding) { 1387 sort.Slice(encodings, func(i, j int) bool { 1388 return encodings[i] < encodings[j] 1389 }) 1390 } 1391 1392 func sortPageEncodingStats(stats []format.PageEncodingStats) { 1393 sort.Slice(stats, func(i, j int) bool { 1394 s1 := &stats[i] 1395 s2 := &stats[j] 1396 if s1.PageType != s2.PageType { 1397 return s1.PageType < s2.PageType 1398 } 1399 return s1.Encoding < s2.Encoding 1400 }) 1401 } 1402 1403 type offsetTrackingWriter struct { 1404 writer io.Writer 1405 offset int64 1406 } 1407 1408 func (w *offsetTrackingWriter) Reset(writer io.Writer) { 1409 w.writer = writer 1410 w.offset = 0 1411 } 1412 1413 func (w *offsetTrackingWriter) Write(b []byte) (int, error) { 1414 n, err := w.writer.Write(b) 1415 w.offset += int64(n) 1416 return n, err 1417 } 1418 1419 func (w *offsetTrackingWriter) WriteString(s string) (int, error) { 1420 n, err := io.WriteString(w.writer, s) 1421 w.offset += int64(n) 1422 return n, err 1423 } 1424 1425 func (w *offsetTrackingWriter) ReadFrom(r io.Reader) (int64, error) { 1426 // io.Copy will make use of io.ReaderFrom if w.writer implements it. 1427 n, err := io.Copy(w.writer, r) 1428 w.offset += n 1429 return n, err 1430 } 1431 1432 var ( 1433 _ RowWriterWithSchema = (*Writer)(nil) 1434 _ RowReaderFrom = (*Writer)(nil) 1435 _ RowGroupWriter = (*Writer)(nil) 1436 1437 _ RowWriter = (*writer)(nil) 1438 _ ValueWriter = (*writer)(nil) 1439 1440 _ ValueWriter = (*writerColumn)(nil) 1441 1442 _ io.ReaderFrom = (*offsetTrackingWriter)(nil) 1443 _ io.StringWriter = (*offsetTrackingWriter)(nil) 1444 )