bb8470a90643b057df7bbf5d0140a5b2bbf9dddd
braney
  Fri Jun 12 08:44:37 2026 -0700
quickLift: load genePreds by column name, not a fixed 15-col loader

Lifting a genePred track to another assembly used genePredExtLoad15, a
positional loader that assumes the extended genePred columns. Classic
knownGene-format tables instead carry proteinID and alignID after the ten
core columns, so the loader read proteinID as the integer score field and
aborted with "invalid signed integer" (e.g. "O54946", or "" when empty).
This showed up when lifting from an assembly whose knownGene is the legacy
format, such as mm10 to mm39 or rheMac10.

Add quickLiftGenePreds(), which loads through genePredReader so the actual
set of columns in the table is honored by name (proteinID maps to name2,
score defaults), matching how the tracks load when not lifted. The three
call sites that hardcoded genePredExtLoad15 (hgTracks gene loading and two
hgc detail handlers) now use it. The chain-walking shared with quickLiftSql
is factored into quickLiftLoadChains() and quickLiftChainQueryRange().

refs #37535

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 475400b7526..bc3905e0bc2 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -2981,34 +2981,32 @@
 
 if (!hFindSplitTable(db, seqName, rootTable, table, sizeof table, NULL))
     errAbort("showGenePos track %s not found", rootTable);
 sqlSafef(query, sizeof(query), "name = \"%s\"", name);
 if (liftDb != NULL)
     {
     // Fetch via quickLiftSql so we get both the items and the set of
     // swapped chains that map them back to the destination assembly, then
     // lift the genePreds with calcLiftOverGenePreds.  seqName/winStart/
     // winEnd are destination coords; quickLiftSql picks up the chains
     // that overlap that window on the destination side.
     char *quickLiftFile = trackDbSetting(tdb, "quickLiftUrl");
     struct hash *chainHash = newHash(8);
     char extraWhere[512];
     sqlSafef(extraWhere, sizeof extraWhere, "name = \"%s\"", name);
-    extern struct genePred *genePredExtLoad15(char **row);
-    gpList = (struct genePred *)quickLiftSql(conn, quickLiftFile, rootTable,
-        seqName, winStart, winEnd, NULL, extraWhere,
-        (ItemLoader2)genePredExtLoad15, 0, chainHash);
+    gpList = quickLiftGenePreds(conn, quickLiftFile, rootTable,
+        seqName, winStart, winEnd, extraWhere, chainHash);
     calcLiftOverGenePreds(gpList, chainHash, 0.0, 0.0, TRUE, NULL, NULL, TRUE, FALSE);
     }
 else
     gpList = genePredReaderLoadQuery(conn, table, query);
 for (gp = gpList; gp != NULL; gp = gp->next)
     {
     printPos(gp->chrom, gp->txStart, gp->txEnd, gp->strand, FALSE, NULL);
     if(sameString(tdb->type,"genePred")
     && startsWith("ENCODE Gencode",tdb->longLabel)
     && startsWith("ENST",name))
         {
         char *ensemblIdUrl = trackDbSetting(tdb, "ensemblIdUrl");
 
         printf("<b>Ensembl Transcript Id:&nbsp</b>");
         if (ensemblIdUrl != NULL)
@@ -9653,36 +9651,33 @@
 struct genePred *gp;
 int rowOffset = hOffsetPastBin(db, seqName, tdb->table);
 if (liftDb != NULL)
     {
     char *table;
     if (isCustomTrack(tdb->table))
         {
         liftDb = CUSTOM_TRASH;
         table = trackDbSetting(tdb, "dbTableName");
         }
     else
         table = trackHubSkipHubName(tdb->table);
     struct hash *chainHash = newHash(8);
     struct sqlConnection *conn = hAllocConn(liftDb);
 
-// using this loader on genePred tables with less than 15 fields may be a problem.
-extern struct genePred *genePredExtLoad15(char **row);
-
     char extraWhere[4096];
     sqlSafef(extraWhere, sizeof extraWhere, "name = \"%s\"", geneName);
-    gpList = (struct genePred *)quickLiftSql(conn, quickLiftFile, table, seqName, winStart, winEnd,  NULL, extraWhere, (ItemLoader2)genePredExtLoad15, 0, chainHash);
+    gpList = quickLiftGenePreds(conn, quickLiftFile, table, seqName, winStart, winEnd, extraWhere, chainHash);
     hFreeConn(&conn);
 
     calcLiftOverGenePreds( gpList, chainHash, 0.0, 0.0, TRUE, NULL, NULL,  TRUE, FALSE);
     }
 else
     {
     sqlSafef(query, sizeof(query), "select * from %s where name = \"%s\"", tdb->table, geneName);
     sr = sqlGetResult(conn, query);
     while ((row = sqlNextRow(sr)) != NULL)
         {
         gp = genePredLoad(row+rowOffset);
         slAddHead(&gpList, gp);
         }
 
     sqlFreeResult(&sr);