544f7054d812254a0883a412b98a08dcd78f1b84
braney
  Fri Sep 4 11:39:41 2026 -0700
hgTracks: draw quickLifted psl and bigPsl tracks

pslTrack.c had no quickLift path at all.  It now reads the alignments out of the
assembly the track came from through quickLiftSql and maps them onto the
reference.  The chromFilter clause and the sort-and-filter tail moved into
helpers so the two loaders cannot drift apart.

bigBedTrack.c built the alignment out of the source interval but labelled it with
the reference sequence name and never lifted it, so a quickLifted bigPsl track
drew its items in the wrong place.  It now takes the real source name out of the
bigBed, lifts, counts a failure the way the bed path does, and rewrites the
coordinate fields so a $chromStart in a mouseOver reports where the item is drawn.

cds.c asked the assembly on screen for the CDS and for the sequence the alignment
is to.  Both belong to the assembly the alignment came from.  This was already
the wrong answer for a quickLifted track on any target, and on a hub-backed
target it fails outright, because the name of such an assembly is not a database.
The table-exists answer is now remembered per assembly rather than once for the
process, since one page can hold both native and lifted alignment tracks.

The mRNA filter reads the same source assembly, for the same reason: the
accessions and the gbCdnaInfo ids are the source assembly's.

refs #38249

diff --git src/hg/hgTracks/cds.c src/hg/hgTracks/cds.c
index a307ebf0e8c..484711d074a 100644
--- src/hg/hgTracks/cds.c
+++ src/hg/hgTracks/cds.c
@@ -778,60 +778,76 @@
 {
 char rnaCodon = baseColorLookupCodon(rna);
 
 if (genomicCodon != rnaCodon)
     {
     if (genomicCodon != 'X' && rnaCodon != 'X' && protEquivalent(genomicCodon, rnaCodon))
         return(GRAYIX_CDS_SYN_PROT);     // yellow, "synonymous" protein 
     else
         return(GRAYIX_CDS_STOP);
     }
 else
     return peptideToGrayIx(genomicCodon, codonFirstColor);
 }
 
 
-static void getGenbankCds(char *acc, struct genbankCds* cds)
+static char *cdsDb(struct track *tg)
+/* The assembly whose CDS and sequence tables go with this track's items.  A quickLifted
+ * track's items came from another assembly, and that is where their CDS is described.
+ * The destination is not merely the wrong answer:  when it is a hub-backed assembly its
+ * name is not a database at all, and asking for a connection to it fails. */
+{
+char *liftDb = (tg->tdb != NULL) ? trackDbSetting(tg->tdb, "quickLiftDb") : NULL;
+
+return (liftDb != NULL) ? liftDb : database;
+}
+
+static void getGenbankCds(char *db, char *acc, struct genbankCds* cds)
 /* Get cds start and stop from genbank tables, if available. Otherwise it
  * does nothing */
 {
-static boolean first = TRUE, haveGbCdnaInfo = FALSE;
-struct sqlConnection *conn = hAllocConn(database);
-if (first)
+// Remember whether the table is there per assembly, not once for the process:  one page
+// can hold both native and quickLifted alignment tracks, which read different assemblies.
+static struct hash *haveGbCdnaInfoHash = NULL;
+struct sqlConnection *conn = hAllocConn(db);
+if (haveGbCdnaInfoHash == NULL)
+    haveGbCdnaInfoHash = hashNew(0);
+int haveGbCdnaInfo = hashIntValDefault(haveGbCdnaInfoHash, db, -1);
+if (haveGbCdnaInfo < 0)
     {
     haveGbCdnaInfo = sqlTableExists(conn, gbCdnaInfoTable);
-    first = FALSE;
+    hashAddInt(haveGbCdnaInfoHash, db, haveGbCdnaInfo);
     }
 if (haveGbCdnaInfo)
     {
     char query[4096], buf[4096], *cdsStr;
     sqlSafef(query, sizeof query, "select c.name from %s g,%s c where (acc = '%s') and (g.cds = c.id)", gbCdnaInfoTable, cdsTable, acc);
     cdsStr = sqlQuickQuery(conn, query, buf, sizeof(buf));
     if (cdsStr != NULL)
         genbankCdsParse(cdsStr, cds);
     }
 hFreeConn(&conn);
 }
 
-static void getCdsFromTbl(char *acc, char *baseColorSetting, struct genbankCds* cds)
+static void getCdsFromTbl(char *db, char *acc, char *baseColorSetting, struct genbankCds* cds)
 /* Get CDS from a specified table, doing nothing if not found */
 {
 char *p = skipToSpaces(baseColorSetting);
 char *cdsSpecTbl = skipLeadingSpaces(p);
 if (*cdsSpecTbl == '\0')
     errAbort("%s table requires a table name as an argument", BASE_COLOR_USE_CDS);
-struct sqlConnection *conn = hAllocConnDbTbl(cdsSpecTbl, &cdsSpecTbl, database);
+struct sqlConnection *conn = hAllocConnDbTbl(cdsSpecTbl, &cdsSpecTbl, db);
 // allow multiple, but only use the first, since transMapGene table might have
 // multiple entries for same gene from different source dbs.
 struct cdsSpec *cdsSpec
     = sqlQueryObjs(conn, (sqlLoadFunc)cdsSpecLoad, sqlQueryMulti,
                    "SELECT * FROM %s WHERE id=\"%s\"", cdsSpecTbl, acc);
 hFreeConn(&conn);
 if (cdsSpec != NULL)
     genbankCdsParse(cdsSpec->cds, cds);
 if (cds != NULL)
 cdsSpecFreeList(&cdsSpec);
 }
 
 static void getPslCds(struct psl *psl, struct track *tg, struct linkedFeatures *lf,
                       struct genbankCds *cds)
 /* get CDS defintion for a PSL */
@@ -843,33 +859,33 @@
     cds->start=0;
     cds->end=psl->qSize*3;
     cds->startComplete = TRUE;
     cds->endComplete = TRUE; 
     }
 else if (startsWith("bigPsl", tg->tdb->type))
     {
     if (lf->cds)
         genbankCdsParse(lf->cds, cds);
     }
 else 
     {
     char *setting = trackDbSetting(tg->tdb, BASE_COLOR_USE_CDS);
     char *dataName = getItemDataName(tg, psl->qName);
     if ((setting != NULL) && startsWith("table", setting))
-        getCdsFromTbl(dataName, setting, cds);
+        getCdsFromTbl(cdsDb(tg), dataName, setting, cds);
     else
-        getGenbankCds(dataName, cds);
+        getGenbankCds(cdsDb(tg), dataName, cds);
     }
 }
 
 static void getHiddenGaps(struct psl *psl, unsigned *gaps)
 /*return the insertions in the query sequence of a psl that are 'hidden' 
   in the browser. This lets these insertions be indicated with the
   color orange.*/
 {
 int i;
 gaps[0] = psl->qStarts[0] - psl->qStart;
 for (i=1; i<psl->blockCount; i++)
     {
     gaps[i] = psl->qStarts[i] - 
 	(psl->qStarts[i-1] + psl->blockSizes[i-1]);
     }
@@ -1108,88 +1124,91 @@
     if (tpsl->strand[0] == '-')
 	reverseComplement(seq->dna, seq->size);
     }
 else
     {
     seq = hChromSeq(database, chromName, lf->start, lf->end);
     if (lf->orientation < 0)
 	reverseComplement(seq->dna, seq->size);
     CopyArray(fPrimer, seq->dna, fPrimerSize);
     CopyArray(rPrimer, (seq->dna + seq->size - rPrimerSize), rPrimerSize);
     }
 return seq;
 }
 #endif /* GBROWSE */
 
-static struct dnaSeq *maybeGetExtFileSeq(char *seqSource, char *name)
+static struct dnaSeq *maybeGetExtFileSeq(char *db, char *seqSource, char *name)
 /* look up sequence name in seq and extFile tables specified in seqSource */
 {
 /* seqSource is: extFile seqTbl extFileTbl */
 static struct dyString *buf = NULL;
 if (buf == NULL)
     buf = dyStringNew(0);
 dyStringClear(buf);
 dyStringAppend(buf, seqSource);
 char *words[3];
 int nwords = chopByWhite(buf->string, words, ArraySize(words));
 if ((nwords != ArraySize(words)) || !sameString(words[0], "extFile"))
     errAbort("invalid %s track setting: %s", BASE_COLOR_USE_SEQUENCE,
              seqSource);
-return hDnaSeqGet(database, name, words[1], words[2]);
+return hDnaSeqGet(db, name, words[1], words[2]);
 }
 
 
 struct cacheTwoBitRanges *cdsQueryCache = NULL;
 
 static struct dnaSeq *fetchCachedTwoBitSeq(char *url, char *seqName, 
     int seqStart, int seqEnd, boolean doRc, int *retSeqOffset)
 /* fetch a sequence from a 2bit.  Caches open two bit files and sequence in 
  * both forward and reverse strand */
 {
 /* Init static url cache */
 if (cdsQueryCache == NULL)
     cdsQueryCache = cacheTwoBitRangesNew(TRUE);
 return cacheTwoBitRangesMayFetch(cdsQueryCache, url, seqName, seqStart, seqEnd, doRc, retSeqOffset);
 }
 
 static struct dnaSeq *maybeGetSeqUpper(struct linkedFeatures *lf, 
 		    char *mrnaName, int mrnaStart, int mrnaEnd,
 		    char *tableName, struct track *tg, boolean doRc, int *retMrnaOffset)
 /* Look up the sequence in genbank tables (hGenBankGetMrna also searches 
  * seq if it can't find it in GB tables) or user's blat sequence, 
  * uppercase and return it if we find it, return NULL if we don't find it. */
 {
 boolean doUpper = TRUE;
 struct dnaSeq *mrnaSeq = NULL;
 char *name = getItemDataName(tg, mrnaName);
+// The sequence the alignment is to belongs with the alignment, so on a quickLifted track
+// it comes from the assembly the alignment came from, not the one on screen.
+char *seqDb = cdsDb(tg);
 if (sameString(tableName,"refGene") || sameString(tableName,"refSeqAli"))
-    mrnaSeq = hGenBankGetMrna(database, name, "refMrna");
+    mrnaSeq = hGenBankGetMrna(seqDb, name, "refMrna");
 else
     {
     char *seqSource = trackDbSetting(tg->tdb, BASE_COLOR_USE_SEQUENCE);
     if (seqSource != NULL)
 	{
 	if (sameString(seqSource, "ss"))
 	    mrnaSeq = maybeGetUserSeq(name);
 #ifndef GBROWSE
 	else if (sameString(seqSource, PCR_RESULT_TRACK_NAME))
 	    mrnaSeq = maybeGetPcrResultSeq(lf);
 #endif /* GBROWSE */
 	else if (startsWith("extFile", seqSource))
-	    mrnaSeq = maybeGetExtFileSeq(seqSource, name);
+	    mrnaSeq = maybeGetExtFileSeq(seqDb, seqSource, name);
 	else if (endsWith("ExtFile", seqSource))
-	    mrnaSeq = maybeGetExtFileSeq(seqSource, name);
+	    mrnaSeq = maybeGetExtFileSeq(seqDb, seqSource, name);
 	else if (sameString("nameIsSequence", seqSource))
 	    {
 	    mrnaSeq = newDnaSeq(cloneString(name), strlen(name), name);
 	    if (lf->orientation == -1)
 		reverseComplement(mrnaSeq->dna, mrnaSeq->size);
 	    }
 	else if (sameString("seq1Seq2", seqSource))
 	    {
 	    mrnaSeq = lf->extra;
 	    if (lf->orientation == -1)
 		reverseComplement(mrnaSeq->dna, mrnaSeq->size);
 	    }
 	else if (sameString("lfExtra", seqSource))
 	    {
 	    if (lf->extra == NULL)
@@ -1204,42 +1223,42 @@
 	    mrnaSeq = lrgReconstructSequence(lrg, database);
 	    }
 	else if (sameString("2bit", seqSource))
 	    {
 	    char *url = trackDbSetting(tg->tdb, "otherTwoBitUrl");
 	    if (url == NULL)
 		errAbort("missing otherTwoBitUrl in baseColorUseSequence 2bit trackDb setting");
 	    mrnaSeq = fetchCachedTwoBitSeq(url, name, mrnaStart, mrnaEnd, doRc, retMrnaOffset);
 	    doRc = FALSE;	    // Handled it already
 	    doUpper = FALSE;    // Handled it already
 	    }
 	else if (startsWith("table ", seqSource))
 	    {
 	    char *table = seqSource;
 	    nextWord(&table);
-	    mrnaSeq = hGenBankGetMrna(database, name, table);
+	    mrnaSeq = hGenBankGetMrna(seqDb, name, table);
 	    }
 	else if (startsWithWord("db", seqSource))
 	    {
 	    char *sourceDb = seqSource;
 	    nextWord(&sourceDb);
 	    if (isEmpty(sourceDb))
-		sourceDb = database;
+		sourceDb = seqDb;
 	    mrnaSeq = hChromSeq(sourceDb, name, 0, 0);
 	    }
 	else
-	    mrnaSeq = hGenBankGetMrna(database, name, NULL);
+	    mrnaSeq = hGenBankGetMrna(seqDb, name, NULL);
 	}
     }
 if (mrnaSeq != NULL && doUpper)
     touppers(mrnaSeq->dna);
 if (mrnaSeq != NULL && doRc)
     reverseComplement(mrnaSeq->dna, mrnaSeq->size);
 return mrnaSeq;
 }
 
 
 
 static void makeCdsShades(struct hvGfx *hvg, Color *cdsColor)
 /* setup CDS colors */
 {
 cdsColor[CDS_ERROR] = hvGfxFindColorIx(hvg,0,0,0);