Skip to content

Commit 39902a3

Browse files
committed
Fix row-group streaming reader against arrow-go v18.5.2
pqarrow.ReadRowGroups treats nil columnIndices as "no columns", not "all columns", and its arg order is (cols, rowGroups). The streaming refactor passed the row group as cols and nil as rows, so every batch returned 0 rows. Cache an explicit list of all leaf column indices at reader construction (matching what ReadTable does internally) and pass the args in the correct order.
1 parent 1fe9359 commit 39902a3

1 file changed

Lines changed: 72 additions & 38 deletions

File tree

internal/parquet/reader.go

Lines changed: 72 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@ import (
1313
"github.com/apache/arrow-go/v18/parquet/pqarrow"
1414
)
1515

16-
// ParquetReader reads data from Parquet files
16+
// ParquetReader reads data from Parquet files using row-group streaming
17+
// to avoid loading the entire file into memory
1718
type ParquetReader struct {
1819
file *os.File
1920
reader *file.Reader
2021
arrowReader *pqarrow.FileReader
2122
schema *arrow.Schema
2223
columnNames []string
2324
columnTypes []string
24-
rowGroupIdx int
25-
currentBatch arrow.RecordBatch
26-
batchIdx int
25+
allColumns []int // Explicit list of all leaf column indices; nil means "no columns" in pqarrow
26+
rowGroupIdx int // Current row group being processed
27+
numRowGroups int // Total number of row groups
28+
currentBatch arrow.RecordBatch // Current record batch
29+
batchIdx int // Current row index within current batch
2730
totalRows int64
31+
exhausted bool // True when all data has been read
2832
}
2933

3034
// NewParquetReader creates a new Parquet reader
@@ -73,13 +77,23 @@ func NewParquetReader(filename string) (*ParquetReader, error) {
7377
}
7478

7579

80+
// Build explicit list of all leaf column indices. pqarrow.ReadRowGroups
81+
// treats nil as "no columns", not "all columns" — see ReadTable in
82+
// vendor/.../pqarrow/file_reader.go for the equivalent pattern.
83+
numCols := reader.MetaData().Schema.NumColumns()
84+
allColumns := make([]int, numCols)
85+
for i := range allColumns {
86+
allColumns[i] = i
87+
}
88+
7689
return &ParquetReader{
7790
file: f,
7891
reader: reader,
7992
arrowReader: arrowReader,
8093
schema: schema,
8194
columnNames: columnNames,
8295
columnTypes: columnTypes,
96+
allColumns: allColumns,
8397
totalRows: reader.NumRows(),
8498
}, nil
8599
}
@@ -104,63 +118,83 @@ func (r *ParquetReader) NumRowGroups() int {
104118
return r.reader.NumRowGroups()
105119
}
106120

107-
// ReadBatch reads the next batch of rows
121+
// ReadBatch reads the next batch of rows using row-group streaming
122+
// to avoid loading the entire file into memory
108123
func (r *ParquetReader) ReadBatch(batchSize int) ([]map[string]any, error) {
109124
ctx := context.Background()
110125

111-
// If we don't have a current batch or we've consumed it, get the next batch
112-
if r.currentBatch == nil || r.batchIdx >= int(r.currentBatch.NumRows()) {
113-
// For the first batch, read the entire table
114-
// Note: Row group reading seems to have issues with nested types
115-
if r.rowGroupIdx == 0 {
116-
table, err := r.arrowReader.ReadTable(ctx)
126+
// Initialize numRowGroups on first call
127+
if r.numRowGroups == 0 {
128+
r.numRowGroups = r.reader.NumRowGroups()
129+
}
130+
131+
// Check if we've exhausted all data
132+
if r.exhausted {
133+
return nil, io.EOF
134+
}
135+
136+
rows := make([]map[string]any, 0, batchSize)
137+
138+
for len(rows) < batchSize {
139+
// If we don't have a current batch or we've consumed it, get the next one
140+
if r.currentBatch == nil || r.batchIdx >= int(r.currentBatch.NumRows()) {
141+
// Release previous batch if any
142+
if r.currentBatch != nil {
143+
r.currentBatch.Release()
144+
r.currentBatch = nil
145+
}
146+
147+
// Move to next row group if needed
148+
if r.rowGroupIdx >= r.numRowGroups {
149+
r.exhausted = true
150+
break
151+
}
152+
153+
// Read the next row group
154+
table, err := r.arrowReader.ReadRowGroups(ctx, r.allColumns, []int{r.rowGroupIdx})
117155
if err != nil {
118-
return nil, fmt.Errorf("failed to read table: %w", err)
156+
return nil, fmt.Errorf("failed to read row group %d: %w", r.rowGroupIdx, err)
119157
}
120-
r.rowGroupIdx++ // Mark that we've read the table
158+
r.rowGroupIdx++
121159

122160
if table.NumRows() == 0 {
123161
table.Release()
124-
return nil, io.EOF
162+
continue // Try next row group
125163
}
126164

127-
// Create a record batch from the table
165+
// Create a record reader from the table
128166
chunkSize := int64(batchSize)
129167
if chunkSize <= 0 || chunkSize > table.NumRows() {
130168
chunkSize = table.NumRows()
131169
}
132-
reader := array.NewTableReader(table, chunkSize)
170+
tableReader := array.NewTableReader(table, chunkSize)
133171

134-
if reader.Next() {
135-
r.currentBatch = reader.RecordBatch()
136-
r.currentBatch.Retain() // Keep the record alive
172+
if tableReader.Next() {
173+
r.currentBatch = tableReader.RecordBatch()
174+
r.currentBatch.Retain() // Keep the record alive after releasing tableReader
137175
r.batchIdx = 0
138-
} else {
139-
table.Release()
140-
reader.Release()
141-
return nil, fmt.Errorf("failed to read record batch from table with %d rows", table.NumRows())
142176
}
143-
reader.Release()
177+
tableReader.Release()
144178
table.Release()
145-
} else {
146-
return nil, io.EOF
179+
180+
if r.currentBatch == nil {
181+
continue // Try next row group
182+
}
147183
}
148-
}
149184

150-
// Extract rows from the current batch
151-
rows := make([]map[string]any, 0, batchSize)
185+
// Extract rows from the current batch
186+
for len(rows) < batchSize && r.batchIdx < int(r.currentBatch.NumRows()) {
187+
row := make(map[string]any)
152188

153-
for i := 0; i < batchSize && r.batchIdx < int(r.currentBatch.NumRows()); i++ {
154-
row := make(map[string]any)
189+
for colIdx, colName := range r.columnNames {
190+
col := r.currentBatch.Column(colIdx)
191+
value := extractValue(col, r.batchIdx)
192+
row[colName] = value
193+
}
155194

156-
for colIdx, colName := range r.columnNames {
157-
col := r.currentBatch.Column(colIdx)
158-
value := extractValue(col, r.batchIdx)
159-
row[colName] = value
195+
rows = append(rows, row)
196+
r.batchIdx++
160197
}
161-
162-
rows = append(rows, row)
163-
r.batchIdx++
164198
}
165199

166200
if len(rows) == 0 {

0 commit comments

Comments
 (0)