5a884a9bce7e45d3f30423fd8e2e26bb7bc44b80
angie
  Wed Mar 23 10:50:57 2022 -0700
Fix memory error revealed by Lou & valgrind: use rowBuf in nextRowFromSqlResult so hashJoin doesn't write past end of mysql row storage.

diff --git src/hg/cgilib/annoStreamDb.c src/hg/cgilib/annoStreamDb.c
index a724497..fb5ff3e 100644
--- src/hg/cgilib/annoStreamDb.c
+++ src/hg/cgilib/annoStreamDb.c
@@ -161,33 +161,39 @@
 annoStreamerSetRegion(vSelf, chrom, regionStart, regionEnd);
 struct annoStreamDb *self = (struct annoStreamDb *)vSelf;
 // If splitTable differs from table, use new chrom in splitTable:
 if (differentString(self->table, self->trackTable))
     {
     char newSplitTable[PATH_LEN];
     safef(newSplitTable, sizeof(newSplitTable), "%s_%s", chrom, self->trackTable);
     freeMem(self->table);
     self->table = cloneString(newSplitTable);
     }
 resetQueryState(self);
 asdUpdateBaselineQuery(self);
 }
 
 static char **nextRowFromSqlResult(struct annoStreamDb *self)
-/* Stream rows directly from self->sr. */
+/* Stream rows directly from self->sr, but copy into rowBuf in case we need extra columns for
+ * hashJoin. */
 {
-return sqlNextRow(self->sr);
+// Use only the first row in rowBuf.
+if (self->rowBuf.buf[0] == NULL)
+    lmAllocArray(self->rowBuf.lm, self->rowBuf.buf[0], self->bigRowSize);
+char **row = sqlNextRow(self->sr);
+CopyArray(row, self->rowBuf.buf[0], self->sqlRowSize);
+return self->rowBuf.buf[0];
 }
 
 INLINE boolean useSplitTable(struct annoStreamDb *self, struct joinerDtf *dtf)
 /* Return TRUE if dtf matches self->{db,table} and table is split. */
 {
 return (sameString(dtf->database, self->db) &&
         sameString(dtf->table, self->trackTable) &&
         differentString(self->table, self->trackTable));
 }
 
 static void appendFieldList(struct annoStreamDb *self, struct dyString *query)
 /* Append SQL field list to query. */
 {
 struct joinerDtf *fieldList = self->joinMixer ? self->joinMixer->sqlFieldList :
                                                 self->mainTableDtfList;
@@ -1311,30 +1317,31 @@
 // Special case: genbank-updated tables are not sorted because new mappings are
 // tacked on at the end.  Max didn't sort the pubs* tables but I hope he will
 // sort the tables for any future tracks.  :)
 if (isIncrementallyUpdated(table) || isPubsTable(table))
     self->notSorted = TRUE;
 self->mergeBins = FALSE;
 self->maxOutRows = maxOutRows;
 self->useMaxOutRows = (maxOutRows > 0);
 self->needQuery = TRUE;
 self->chromList = annoAssemblySeqNames(aa);
 if (slCount(self->chromList) > 1000)
     {
     // Assembly has many sequences (e.g. scaffold-based assembly) --
     // don't break up into per-sequence queries.  Take our chances
     // with mysql being unhappy about the sqlResult being open too long.
+    rowBufInit(&self->rowBuf, 1);
     self->doQuery = asdDoQuerySimple;
     self->nextRowRaw = nextRowFromSqlResult;
     }
 else
     {
     // All-chromosome assembly -- if table is large, perform a series of
     // chunked queries.
     self->doQuery = asdDoQueryChunking;
     self->nextRowRaw = nextRowFromBuffer;
     }
 asdInitBaselineQuery(self);
 asdUpdateBaselineQuery(self);
 struct annoStreamer *sSelf = (struct annoStreamer *)self;
 if (asdDebug)
     sSelf->getHeader = asdGetHeader;