@@ -39,6 +39,7 @@ type parserState struct {
3939 quoteAdjust uint64 // bytes to skip for opening quote (0 or 1)
4040 lastSepOrNewline int64 // last separator/newline position (-1 initially)
4141 lastClosingQuote int64 // last closing quote position (-1 if none)
42+ sawQuote bool // true if quote was seen in current field (for validation optimization)
4243}
4344
4445// newParserState creates an initialized parser state.
@@ -67,6 +68,7 @@ func (s *parserState) resetForNextField(delimiterPos uint64) {
6768 s .quoteAdjust = 0
6869 s .lastSepOrNewline = int64 (delimiterPos )
6970 s .lastClosingQuote = - 1
71+ s .sawQuote = false
7072}
7173
7274// =============================================================================
@@ -122,12 +124,13 @@ type fieldInfo struct {
122124 start uint32 // content start offset (after opening quote if quoted)
123125 length uint32 // content length (excluding quotes)
124126 rawEndDelta uint8 // delta from start+length to raw end position
125- flags uint8 // bit0: needsUnescape, bit1: isQuoted
127+ flags uint8 // bit0: needsUnescape, bit1: isQuoted, bit2: containsQuote
126128}
127129
128130const (
129131 fieldFlagNeedsUnescape = 1 << 0
130132 fieldFlagIsQuoted = 1 << 1
133+ fieldFlagContainsQuote = 1 << 2 // field contains quote character (for validation optimization)
131134)
132135
133136// rawStart returns the raw start position (including opening quote if quoted).
@@ -144,11 +147,14 @@ func (f *fieldInfo) rawEnd() uint32 {
144147}
145148
146149// newFieldInfo creates a fieldInfo from parsed boundaries.
147- func newFieldInfo (start , length uint64 , rawEndDelta uint8 , isQuoted bool ) fieldInfo {
150+ func newFieldInfo (start , length uint64 , rawEndDelta uint8 , isQuoted , containsQuote bool ) fieldInfo {
148151 var flags uint8
149152 if isQuoted {
150153 flags = fieldFlagIsQuoted
151154 }
155+ if containsQuote {
156+ flags |= fieldFlagContainsQuote
157+ }
152158 return fieldInfo {
153159 start : uint32 (start ),
154160 length : uint32 (length ),
@@ -171,6 +177,12 @@ func (f *fieldInfo) needsUnescape() bool {
171177 return f .flags & fieldFlagNeedsUnescape != 0
172178}
173179
180+ // containsQuote returns whether the field contains any quote characters.
181+ // Used for validation optimization - fields without quotes don't need quote validation.
182+ func (f * fieldInfo ) containsQuote () bool {
183+ return f .flags & fieldFlagContainsQuote != 0
184+ }
185+
174186// rowInfo holds row metadata.
175187type rowInfo struct {
176188 firstField int // index of first field in parseResult.fields
@@ -387,6 +399,7 @@ func classifyEvent(bit, quoteMask, sepMask uint64) eventType {
387399
388400// handleQuoteEvent processes a quote character, toggling the quoted state.
389401func handleQuoteEvent (absPos uint64 , state * parserState ) {
402+ state .sawQuote = true // Mark that this field contains a quote
390403 if state .quoted {
391404 state .exitQuotedState (absPos )
392405 } else {
@@ -430,6 +443,7 @@ func skipBlankLine(state *parserState, absPos uint64, lineNum *int) {
430443 state .fieldStart = absPos + 1
431444 state .quoteAdjust = 0
432445 state .lastClosingQuote = - 1
446+ state .sawQuote = false
433447 (* lineNum )++
434448}
435449
@@ -441,7 +455,8 @@ func skipBlankLine(state *parserState, absPos uint64, lineNum *int) {
441455// For newline delimiters (isNewline=true), excludes trailing CR from CRLF sequences.
442456func recordField (buf []byte , absPos uint64 , state * parserState , result * parseResult , isNewline bool ) {
443457 bounds := computeFieldBounds (buf , absPos , state , isNewline )
444- result .fields = append (result .fields , newFieldInfo (bounds .start , bounds .length , bounds .rawEndDelta , bounds .isQuoted ))
458+ containsQuote := state .sawQuote
459+ result .fields = append (result .fields , newFieldInfo (bounds .start , bounds .length , bounds .rawEndDelta , bounds .isQuoted , containsQuote ))
445460 state .resetForNextField (absPos )
446461}
447462
@@ -544,8 +559,9 @@ func finalizeLastField(buf []byte, state *parserState, result *parseResult, rowF
544559 fieldLen := computeFieldLength (bufLen , start , state )
545560 rawEndDelta := computeRawEndDelta (bufLen , start , fieldLen )
546561 isQuoted := state .quoteAdjust > 0
562+ containsQuote := state .sawQuote
547563
548- result .fields = append (result .fields , newFieldInfo (start , fieldLen , rawEndDelta , isQuoted ))
564+ result .fields = append (result .fields , newFieldInfo (start , fieldLen , rawEndDelta , isQuoted , containsQuote ))
549565 result .rows = append (result .rows , rowInfo {
550566 firstField : rowFirstField ,
551567 fieldCount : len (result .fields ) - rowFirstField ,
0 commit comments