@@ -723,8 +723,9 @@ func (f *framer) readTypeInfo() TypeInfo {
723723 typ : Type (id ),
724724 }
725725
726- // Fast path: simple native types (0x0001-0x0015) need no further processing
727- if id > 0 && id <= 0x0015 {
726+ // Fast path: simple native types (0x0001 through TypeDuration) need no further processing.
727+ // These are all scalar types that require no additional wire reads beyond the type ID.
728+ if id > 0 && id <= uint16 (TypeDuration ) {
728729 return simple
729730 }
730731
@@ -851,23 +852,16 @@ func (f *framer) parsePreparedMetadata() preparedMetadata {
851852 }
852853
853854 var cols []ColumnInfo
854- sameKeyspaceTable := true
855- firstKeyspace := ""
856- firstTable := ""
857855 readPerColumnSpec := ! globalSpec
856+ var tracker keyspaceTableTracker
858857 if meta .colCount < 1000 {
859858 // preallocate columninfo to avoid excess copying
860859 cols = make ([]ColumnInfo , meta .colCount )
861860 for i := 0 ; i < meta .colCount ; i ++ {
862861 col := & cols [i ]
863862 keyspace , table := f .readColWithSpec (col , & meta .resultMetadata , globalSpec , meta .keyspace , meta .table , i , readPerColumnSpec )
864863 if readPerColumnSpec {
865- if i == 0 {
866- firstKeyspace = keyspace
867- firstTable = table
868- } else if keyspace != firstKeyspace || table != firstTable {
869- sameKeyspaceTable = false
870- }
864+ tracker .track (i , keyspace , table )
871865 }
872866 }
873867 } else {
@@ -877,20 +871,15 @@ func (f *framer) parsePreparedMetadata() preparedMetadata {
877871 var col ColumnInfo
878872 keyspace , table := f .readColWithSpec (& col , & meta .resultMetadata , globalSpec , meta .keyspace , meta .table , i , readPerColumnSpec )
879873 if readPerColumnSpec {
880- if i == 0 {
881- firstKeyspace = keyspace
882- firstTable = table
883- } else if keyspace != firstKeyspace || table != firstTable {
884- sameKeyspaceTable = false
885- }
874+ tracker .track (i , keyspace , table )
886875 }
887876 cols = append (cols , col )
888877 }
889878 }
890879
891- if ! globalSpec && meta .colCount > 0 && sameKeyspaceTable {
892- meta .keyspace = firstKeyspace
893- meta .table = firstTable
880+ if ! globalSpec && meta .colCount > 0 && tracker . allSame {
881+ meta .keyspace = tracker . keyspace
882+ meta .table = tracker . table
894883 }
895884
896885 meta .columns = cols
@@ -919,6 +908,24 @@ func (r resultMetadata) String() string {
919908 return fmt .Sprintf ("[metadata flags=0x%x paging_state=% X columns=%v]" , r .flags , r .pagingState , r .columns )
920909}
921910
911+ // keyspaceTableTracker tracks whether all columns in a prepared statement
912+ // share the same keyspace and table, recording the first column's values.
913+ type keyspaceTableTracker struct {
914+ keyspace string
915+ table string
916+ allSame bool
917+ }
918+
919+ func (t * keyspaceTableTracker ) track (colIndex int , keyspace , table string ) {
920+ if colIndex == 0 {
921+ t .keyspace = keyspace
922+ t .table = table
923+ t .allSame = true
924+ } else if keyspace != t .keyspace || table != t .table {
925+ t .allSame = false
926+ }
927+ }
928+
922929func (f * framer ) readColWithSpec (col * ColumnInfo , meta * resultMetadata , globalSpec bool , keyspace , table string , colIndex int , readPerColumnSpec bool ) (string , string ) {
923930 if readPerColumnSpec {
924931 // In case of prepared statements with per-column spec, we need to read the keyspace and table for each column.
@@ -964,7 +971,16 @@ func (f *framer) parseResultMetadata() resultMetadata {
964971
965972 globalSpec := meta .flags & frm .FlagGlobalTableSpec == frm .FlagGlobalTableSpec
966973 if globalSpec || meta .colCount > 0 {
967- // globalSpec: read from metadata position; !globalSpec: read from first column position
974+ // When globalSpec is set, keyspace/table are encoded once at the metadata level.
975+ // When !globalSpec, the protocol technically encodes keyspace/table per-column and
976+ // they *could* differ. However, CQL ROWS results always come from a single table
977+ // (SELECT cannot produce columns from multiple tables), so we read the first
978+ // column's keyspace/table and reuse them for all columns, avoiding per-column
979+ // string allocation. Note that parsePreparedMetadata() does NOT apply this
980+ // optimization — it uses readPerColumnSpec for the result-set part, where prepared
981+ // batch metadata may legitimately reference multiple tables.
982+ // See readColWithSpec: column 0 skips reading (already consumed here), columns 1+
983+ // skip the redundant per-column keyspace/table bytes on the wire.
968984 meta .keyspace = f .readString ()
969985 meta .table = f .readString ()
970986 }
@@ -1539,7 +1555,7 @@ func (f *framer) skipString() {
15391555 size := f .readShort ()
15401556
15411557 if len (f .buf ) < int (size ) {
1542- panic (fmt .Errorf ("not enough bytes in buffer to skip string require %d got: %d" , size , len (f .buf )))
1558+ panic (fmt .Errorf ("not enough bytes in buffer to skip string, requires %d got %d" , size , len (f .buf )))
15431559 }
15441560
15451561 f .buf = f .buf [size :]
0 commit comments