@@ -65,7 +65,8 @@ func (r *Reader) buildRecordWithValidation(row rowInfo, rowIdx int) ([]string, e
6565 return r .buildFinalRecord (fieldCount ), nil
6666}
6767
68- // buildRecordWithValidationZeroCopy builds a record with zero-copy strings while still validating.
68+ // buildRecordWithValidationZeroCopy builds a record with zero-copy strings while validating.
69+ // Zero-copy is safe here because rawBuffer outlives the returned record strings.
6970func (r * Reader ) buildRecordWithValidationZeroCopy (row rowInfo , fields []fieldInfo ) ([]string , error ) {
7071 fieldCount := row .fieldCount
7172 record := r .allocateRecord (fieldCount )
@@ -75,108 +76,126 @@ func (r *Reader) buildRecordWithValidationZeroCopy(row rowInfo, fields []fieldIn
7576 bufLen := uint32 (len (buf ))
7677
7778 for i , field := range fields {
78- // Validate even in zero-copy path
7979 if err := r .validateFieldIfNeeded (field , row .lineNum ); err != nil {
8080 return record [:i ], err
8181 }
8282
83- start := field .start
84- end := start + field .length
85- if start >= bufLen {
86- record [i ] = ""
87- } else {
88- if end > bufLen {
89- end = bufLen
90- }
91- // Zero-copy string from rawBuffer
92- record [i ] = unsafe .String (& buf [start ], int (end - start ))
93- }
83+ record [i ] = r .extractFieldString (buf , bufLen , field )
9484 r .state .fieldPositions [i ] = position {line : row .lineNum , column : int (field .rawStart ()) + 1 }
9585 }
9686 return record , nil
9787}
9888
89+ // extractFieldString returns a zero-copy string for the field content.
90+ // Returns empty string if field is out of bounds.
91+ func (r * Reader ) extractFieldString (buf []byte , bufLen uint32 , field fieldInfo ) string {
92+ start := field .start
93+ end := start + field .length
94+ if start >= bufLen {
95+ return ""
96+ }
97+ if end > bufLen {
98+ end = bufLen
99+ }
100+ return unsafe .String (& buf [start ], int (end - start ))
101+ }
102+
99103// buildRecordNoQuotes builds a record when the input contains no quotes.
100104// Uses a single row string to avoid per-field allocations.
105+ // Zero-copy when TrimLeadingSpace is disabled; copies when trimming is needed.
101106func (r * Reader ) buildRecordNoQuotes (row rowInfo ) []string {
102107 fieldCount := row .fieldCount
103108 record := r .allocateRecord (fieldCount )
104109 r .state .fieldPositions = r .ensureFieldPositionsCapacity (fieldCount )
105110
106111 fields := r .getFieldsForRow (row , fieldCount )
107- buf := r .state .rawBuffer
108- bufLen := uint32 (len (buf ))
109-
110112 if len (fields ) == 0 {
111113 return record
112114 }
113115
116+ buf := r .state .rawBuffer
117+ bufLen := uint32 (len (buf ))
118+
119+ // Calculate row span in buffer
114120 rowStart := fields [0 ].rawStart ()
115121 rowEnd := fields [len (fields )- 1 ].rawEnd ()
122+
123+ // Handle out-of-bounds row
116124 if rowStart >= bufLen {
117125 for i , field := range fields {
118126 record [i ] = ""
119127 r .state .fieldPositions [i ] = position {line : row .lineNum , column : int (field .rawStart ()) + 1 }
120128 }
121129 return record
122130 }
123- if rowEnd > bufLen {
124- rowEnd = bufLen
131+
132+ // Clamp row bounds
133+ rowEnd = clampUint32 (rowEnd , rowStart , bufLen )
134+
135+ // Create row string (copy if trimming, zero-copy otherwise)
136+ rowStr := r .createRowString (buf , rowStart , rowEnd )
137+ rowStrLen := len (rowStr )
138+
139+ // Extract fields from row string
140+ for i , field := range fields {
141+ record [i ] = r .extractFieldFromRow (buf , bufLen , rowStr , rowStrLen , rowStart , field )
142+ r .state .fieldPositions [i ] = position {line : row .lineNum , column : int (field .rawStart ()) + 1 }
143+ }
144+ return record
145+ }
146+
147+ // clampUint32 clamps value to [minVal, maxVal].
148+ func clampUint32 (value , minVal , maxVal uint32 ) uint32 {
149+ if value < minVal {
150+ return minVal
125151 }
126- if rowEnd < rowStart {
127- rowEnd = rowStart
152+ if value > maxVal {
153+ return maxVal
128154 }
155+ return value
156+ }
129157
130- var rowStr string
158+ // createRowString creates a string for the row span.
159+ // Copies when TrimLeadingSpace is enabled; zero-copy otherwise.
160+ func (r * Reader ) createRowString (buf []byte , rowStart , rowEnd uint32 ) string {
131161 if r .TrimLeadingSpace {
132- rowStr = string (buf [rowStart :rowEnd ])
133- } else {
134- // Zero-copy string from rawBuffer - safe because rawBuffer outlives record.
135- rowStr = unsafe .String (& buf [rowStart ], int (rowEnd - rowStart ))
162+ return string (buf [rowStart :rowEnd ])
136163 }
137- rowStrLen := len (rowStr )
138-
139- for i , field := range fields {
140- start := field .start
141- end := start + field .length
142- rawStart := field .rawStart ()
164+ return unsafe .String (& buf [rowStart ], int (rowEnd - rowStart ))
165+ }
143166
144- if start < bufLen {
145- if end > bufLen {
146- end = bufLen
147- }
148- if r .TrimLeadingSpace && start < end {
149- for start < end && (buf [start ] == ' ' || buf [start ] == '\t' ) {
150- start ++
151- }
152- }
153- }
167+ // extractFieldFromRow extracts a field string from the row string.
168+ func (r * Reader ) extractFieldFromRow (buf []byte , bufLen uint32 , rowStr string , rowStrLen int , rowStart uint32 , field fieldInfo ) string {
169+ start := field .start
170+ end := start + field .length
154171
155- if start < rowStart {
156- start = rowStart
157- }
158- if end < start {
159- end = start
160- }
161- relStart := int (start - rowStart )
162- relEnd := int (end - rowStart )
163- if relStart < 0 {
164- relStart = 0
172+ // Apply trimming if needed
173+ if r .TrimLeadingSpace && start < bufLen && start < end {
174+ trimEnd := end
175+ if trimEnd > bufLen {
176+ trimEnd = bufLen
165177 }
166- if relStart > rowStrLen {
167- relStart = rowStrLen
168- }
169- if relEnd < relStart {
170- relEnd = relStart
171- }
172- if relEnd > rowStrLen {
173- relEnd = rowStrLen
178+ for start < trimEnd && (buf [start ] == ' ' || buf [start ] == '\t' ) {
179+ start ++
174180 }
181+ }
175182
176- record [i ] = rowStr [relStart :relEnd ]
177- r .state .fieldPositions [i ] = position {line : row .lineNum , column : int (rawStart ) + 1 }
183+ // Calculate relative positions in row string
184+ relStart := clampInt (int (start )- int (rowStart ), 0 , rowStrLen )
185+ relEnd := clampInt (int (end )- int (rowStart ), relStart , rowStrLen )
186+
187+ return rowStr [relStart :relEnd ]
188+ }
189+
190+ // clampInt clamps value to [minVal, maxVal].
191+ func clampInt (value , minVal , maxVal int ) int {
192+ if value < minVal {
193+ return minVal
178194 }
179- return record
195+ if value > maxVal {
196+ return maxVal
197+ }
198+ return value
180199}
181200
182201// getFieldsForRow extracts the slice of fieldInfo for the given row.
0 commit comments