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/pslTrack.c src/hg/hgTracks/pslTrack.c
index e8cad8bc691..d1b79ccc173 100644
--- src/hg/hgTracks/pslTrack.c
+++ src/hg/hgTracks/pslTrack.c
@@ -1,30 +1,32 @@
 /* pslTrack - stuff to handle loading and display of
  * psl (blat format) based tracks. */
 
 /* Copyright (C) 2013 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "hCommon.h"
 #include "hash.h"
 #include "linefile.h"
 #include "jksql.h"
 #include "hdb.h"
 #include "hgTracks.h"
 #include "psl.h"
 #include "genbank.h"
+#include "quickLift.h"
+#include "trackHub.h"
 
 #ifndef GBROWSE
 #include "../gsid/gsidTable/gsidTable.h"
 #define SELECT_SUBJ	"selectSubject"
 struct gsidSubj *gsidSelectedSubjList = NULL;
 struct gsidSeq *gsidSelectedSeqList = NULL;
 #endif /* GBROWSE */
 
 int pslGrayIx(struct psl *psl, boolean isXeno, int maxShade)
 /* Figure out gray level for an RNA block. */
 {
 double misFactor;
 double hitFactor;
 int res;
 
@@ -89,31 +91,35 @@
 if (*pLfList == NULL)
     return;
 
 type = cartUsualStringClosestToHome(cart, tg->tdb, FALSE, mud->filterTypeSuffix, "red");
 if (sameString(type, "exclude"))
     isExclude = TRUE;
 else if (sameString(type, "include"))
     isExclude = FALSE;
 else
     colorIx = getFilterColor(type, MG_BLACK);
 type = cartUsualStringClosestToHome(cart, tg->tdb, FALSE, mud->logicTypeSuffix, "and");
 andLogic = sameString(type, "and");
 
 /* Make a pass though each filter, and start setting up search for
  * those that have some text. */
-conn = hAllocConn(database);
+// For a quickLifted track the accessions, and the gbCdnaInfo ids the filter tables point
+// at, belong to the assembly the alignments came from, not the one on screen.
+char *liftDb = trackDbSetting(tg->tdb, "quickLiftDb");
+char *filterDb = (liftDb != NULL) ? liftDb : database;
+conn = hAllocConn(filterDb);
 for (fil = mud->filterList; fil != NULL; fil = fil->next)
     {
     if (fil->pattern[0] != 0)   // Filled above
 	{
 	fil->hash = newHash(10);
 	if ((fil->mrnaTableIx = sqlFieldIndex(conn, gbCdnaInfoTable, skipDb(fil->table))) < 0)
 	    internalErr();
 	}
     }
 
 /* Scan tables id/name tables to build up hash of matching id's. */
 for (fil = mud->filterList; fil != NULL; fil = fil->next)
     {
     struct hash *hash = fil->hash;
     int wordIx, wordCount;
@@ -226,31 +232,38 @@
 tg->limitedVisSet = FALSE;	/* Need to recalculate this after filtering. */
 
 /* Free up hashes, etc. */
 for (fil = mud->filterList; fil != NULL; fil = fil->next)
     {
     hashFree(&fil->hash);
     }
 hFreeConn(&conn);
 }
 
 static boolean shouldFilterGenbankPatentSequences(struct track *tg)
 /* is this a genbank track with patent sequence filtering enabled */
 {
 char name[256];
 safef(name, sizeof(name), "%s.%s", tg->tdb->track, SHOW_PATENT_SEQUENCES_SUFFIX);
-return (sameString(tg->tdb->track, "mrna")|| sameString(tg->tdb->track, "intronEst"))
+// Strip the hub prefix on a quickLifted track so hub_NNN_mrna is recognized.  The
+// accession test itself needs no database, so it is right on a lifted track too.  Leave
+// other hubs alone: a hub track that merely happens to be named mrna was not being
+// filtered before and should not start now.
+char *bareTrack = tg->tdb->track;
+if (trackDbSetting(tg->tdb, "quickLifted") != NULL)
+    bareTrack = trackHubSkipHubName(bareTrack);
+return (sameString(bareTrack, "mrna")|| sameString(bareTrack, "intronEst"))
         && !cartUsualBoolean(cart, name, FALSE);
 }
 
 static void filterGenbankPatentSequences(struct track *tg, struct linkedFeatures **pLfList)
 /* remove genbank patent sequences  */
 {
 struct linkedFeatures *lf, *newLf = NULL;
 while ((lf = slPopHead(pLfList)) != NULL)
     {
     if (!isGenbankPatentAccession(lf->name))
         slAddHead(&newLf, lf);
     }
 slReverse(&newLf);
 *pLfList = newLf;
 }
@@ -320,84 +333,139 @@
 lf->end = lf->tallEnd = psl->tEnd;
 
 /* Hang on to psl for use in drawing phase (this is why caller must not free psl!): */
 lf->original = psl;
 
 return lf;
 }
 
 struct linkedFeatures *lfFromPsl(struct psl *psl, boolean isXeno)
 /* Create a linked feature item from psl.
  * Don't free psl afterwards! */
 {
 return lfFromPslx(psl, 1, isXeno, FALSE, NULL);
 }
 
+static char *pslChromFilterWhere(struct track *tg, char *extraWhere, int extraWhereSize)
+/* Return the SQL clause for this track's chromFilter setting, written into extraWhere,
+ * or NULL when the setting asks for every chromosome. */
+{
+char optionChr[128]; /* Option -  chromosome filter */
+
+safef( optionChr, sizeof(optionChr), "%s.chromFilter", tg->track);
+char *optionChrStr = cartUsualString(cart, optionChr, "All");
+if (!startsWith("chr",optionChrStr))
+    return NULL;
+
+sqlSafef(extraWhere, extraWhereSize, "qName = \"%s\"",optionChrStr);
+return extraWhere;
+}
+
+static void finishPslLfList(struct track *tg, struct linkedFeatures *lfList)
+/* Put the linked features built from an alignment table in order, apply the filters, and
+ * hang them on the track. */
+{
+slReverse(&lfList);
+if (tg->visibility != tvDense)
+    slSort(&lfList, linkedFeaturesCmpStart);
+if (tg->extraUiData)
+    filterMrna(tg, &lfList);
+if (shouldFilterGenbankPatentSequences(tg))
+    filterGenbankPatentSequences(tg, &lfList);
+tg->items = lfList;
+}
+
 static void connectedLfFromPslsInRange(struct sqlConnection *conn,
     struct track *tg, int start, int end, char *chromName,
     boolean isXeno, boolean nameGetsPos, int sizeMul)
 /* Return linked features from range of table after have
  * already connected to database.. */
 {
 struct sqlResult *sr = NULL;
 char **row;
 int rowOffset;
-char *optionChrStr;
 struct linkedFeatures *lfList = NULL, *lf;
-char optionChr[128]; /* Option -  chromosome filter */
 char extraWhere[128];
 
-safef( optionChr, sizeof(optionChr), "%s.chromFilter", tg->track);
-optionChrStr = cartUsualString(cart, optionChr, "All");
-if (startsWith("chr",optionChrStr))
-    {
-    sqlSafef(extraWhere, sizeof(extraWhere), "qName = \"%s\"",optionChrStr);
-    sr = hRangeQuery(conn, tg->table, chromName, start, end, extraWhere, &rowOffset);
-    }
-else
-    {
-    safef(extraWhere, sizeof(extraWhere), " ");
-    sr = hRangeQuery(conn, tg->table, chromName, start, end, NULL, &rowOffset);
-    }
+sr = hRangeQuery(conn, tg->table, chromName, start, end,
+                 pslChromFilterWhere(tg, extraWhere, sizeof(extraWhere)), &rowOffset);
 
 if (sqlCountColumns(sr) < 21+rowOffset)
     errAbort("trackDb has incorrect table type for track \"%s\"",
 	     tg->track);
 while ((row = sqlNextRow(sr)) != NULL)
     {
     struct psl *psl = pslLoad(row+rowOffset);
     lf = lfFromPslx(psl, sizeMul, isXeno, nameGetsPos, tg);
     slAddHead(&lfList, lf);
     // Don't free psl - may be used by baseColor code (and freeing is slow)
     }
-slReverse(&lfList);
-if (tg->visibility != tvDense)
-    slSort(&lfList, linkedFeaturesCmpStart);
-if (tg->extraUiData)
-    filterMrna(tg, &lfList);
-if (shouldFilterGenbankPatentSequences(tg))
-    filterGenbankPatentSequences(tg, &lfList);
-tg->items = lfList;
 sqlFreeResult(&sr);
+finishPslLfList(tg, lfList);
+}
+
+static struct slList *pslRowLoader(char **row, int numFields)
+/* Load one alignment out of a SQL row, in the shape quickLiftSql wants. */
+{
+return (struct slList *)pslLoad(row);
+}
+
+static void quickLiftLfFromPsls(struct track *tg, char *chrom, int start, int end,
+	boolean isXeno, boolean nameGetsPos)
+/* Read alignments out of the assembly the track was lifted from, map them onto the
+ * reference, and turn them into linked features. */
+{
+char *liftDb = trackDbSetting(tg->tdb, "quickLiftDb");
+char *table = NULL;
+quickLiftResolveTable(tg->tdb, tg->table, &table, &liftDb);
+char *quickLiftFile = trackDbSetting(tg->tdb, "quickLiftUrl");
+char extraWhere[128];
+
+struct hash *chainHash = newHash(8);
+struct sqlConnection *conn = hAllocConn(liftDb);
+struct psl *pslList = (struct psl *)quickLiftSql(conn, quickLiftFile, table,
+    chrom, start, end, NULL, pslChromFilterWhere(tg, extraWhere, sizeof(extraWhere)),
+    pslRowLoader, 0, chainHash);
+hFreeConn(&conn);
+
+struct linkedFeatures *lfList = NULL;
+struct psl *psl, *nextPsl;
+for(psl = quickLiftPsls(chainHash, pslList); psl != NULL; psl = nextPsl)
+    {
+    nextPsl = psl->next;
+    psl->next = NULL;    // lfFromPslx hangs on to the psl, so don't leave it in a list
+
+    // sizeMul is 1 whatever the caller passed:  the lift returns an untranslated
+    // alignment, and it puts a protein alignment into nucleotide space on the way, so the
+    // block sizes are already in bases.
+    slAddHead(&lfList, lfFromPslx(psl, 1, isXeno, nameGetsPos, tg));
+    }
+finishPslLfList(tg, lfList);
 }
 
 static void lfFromPslsInRange(struct track *tg, int start, int end,
-	char *chromName, boolean isXeno, boolean nameGetsPos, int sizeMul)
+	char *chrom, boolean isXeno, boolean nameGetsPos, int sizeMul)
 /* Return linked features from range of table. */
 {
+if (trackDbSetting(tg->tdb, "quickLiftDb") != NULL)
+    {
+    quickLiftLfFromPsls(tg, chrom, start, end, isXeno, nameGetsPos);
+    return;
+    }
+
 struct sqlConnection *conn = hAllocConn(database);
-connectedLfFromPslsInRange(conn, tg, start, end, chromName,
+connectedLfFromPslsInRange(conn, tg, start, end, chrom,
 	isXeno, nameGetsPos, sizeMul);
 hFreeConn(&conn);
 }
 
 static void loadXenoPslWithPos(struct track *tg)
 /* load up all of the psls from correct table into tg->items item list*/
 {
 lfFromPslsInRange(tg, winStart,winEnd, chromName, TRUE, TRUE, 1);
 }
 
 void pslChromMethods(struct track *tg, char *colorChromDefault)
 /* Fill in custom parts of xeno psl <otherdb> track */
 {
 char option[128]; /* Option -  rainbow chromosome color */
 char *optionStr ;