42cfb8cd4a22209d8183ffcad97e5ca5e9ae070b
braney
  Fri Sep 4 12:47:47 2026 -0700
chainNetDbLoad: add chainLoadRange, every chain in a range with its blocks

chainLoadIdRange loads one chain by id, and the bigChain reader takes -1 to mean
every chain in the range, but there was no SQL equivalent of the latter.  This is
it: one query for the headers, one for the links, and each link handed to the
chain it belongs to.

The blocks are sorted rather than just reversed.  A bin indexed range query does
not return rows in position order, and anything that walks a chain's blocks
expects them ascending, which is why chainLinkAddResult sorts them too.

refs #38249

diff --git src/hg/lib/chainNetDbLoad.c src/hg/lib/chainNetDbLoad.c
index 5dd8f80f02b..7a938bf6ac0 100644
--- src/hg/lib/chainNetDbLoad.c
+++ src/hg/lib/chainNetDbLoad.c
@@ -208,30 +208,86 @@
 struct chainLink link;
 while ((row = sqlNextRow(sr)) != NULL)
     {
     chainLinkStaticLoad(row+rowOffset, &link);
     AllocVar(b);
     b->tStart = link.tStart;
     b->tEnd = link.tEnd;
     b->qStart = link.qStart;
     b->qEnd = link.qStart + (link.tEnd - link.tStart);
     slAddHead(&list, b);
     }
 slSort(&list, cBlockCmpTarget);
 chain->blockList = list;
 }
 
+struct chain *chainLoadRange(char *database, char *track, char *chrom, int start, int end)
+/* Load every chain in a range from the database, each carrying the blocks that overlap the
+ * range.  As with chainLoadIdRange the chain header still describes the whole chain, not
+ * just the part in range. */
+{
+struct sqlConnection *conn = sqlConnect(database);
+struct hash *chainHash = newHash(0);
+struct chain *chainList = NULL, *chain;
+struct sqlResult *sr;
+char **row;
+int rowOffset;
+char id[32];
+
+/* Load the chain headers in range, and index them so the links can find them. */
+sr = hRangeQuery(conn, track, chrom, start, end, NULL, &rowOffset);
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    chain = chainHeadLoad(row + rowOffset);
+    slAddHead(&chainList, chain);
+    safef(id, sizeof id, "%d", chain->id);
+    hashAdd(chainHash, id, chain);
+    }
+sqlFreeResult(&sr);
+
+/* One pass over the links in range, handing each to the chain it belongs to. */
+if (chainList != NULL)
+    {
+    char linkTable[HDB_MAX_TABLE_STRING];
+    safef(linkTable, sizeof linkTable, "%sLink", track);
+    sr = hRangeQuery(conn, linkTable, chrom, start, end, NULL, &rowOffset);
+    while ((row = sqlNextRow(sr)) != NULL)
+        {
+        struct chainLink link;
+        chainLinkStaticLoad(row + rowOffset, &link);
+        if ((chain = hashFindVal(chainHash, row[rowOffset + 4])) != NULL)
+            {
+            struct cBlock *cBlock;
+            AllocVar(cBlock);
+            cBlock->tStart = link.tStart;
+            cBlock->tEnd = link.tEnd;
+            cBlock->qStart = link.qStart;
+            cBlock->qEnd = link.qStart + (link.tEnd - link.tStart);
+            slAddHead(&chain->blockList, cBlock);
+            }
+        }
+    sqlFreeResult(&sr);
+    for (chain = chainList; chain != NULL; chain = chain->next)
+        slSort(&chain->blockList, cBlockCmpTarget);
+    }
+
+hashFree(&chainHash);
+sqlDisconnect(&conn);
+slReverse(&chainList);
+return chainList;
+}
+
 struct chain *chainLoadIdRangeHub(char *db, char *fileName, char *linkFileName,   char *chrom, int start, int end, int id)
 /* Load parts of chain of given ID from bigChain file.  Note the chain header
  * including score, tStart, tEnd, will still reflect the whole chain,
  * not just the part in range.  However only the blocks of the chain
  * overlapping the range will be loaded. */
 {
 struct lm *lm = lmInit(0);
 struct bbiFile *bbi =  bigBedFileOpenAlias(fileName, chromAliasFindAliases);
 struct bigBedInterval *bb, *bbList =  bigBedIntervalQuery(bbi, chrom, start, end, 0, lm);
 struct bbiFile *linkBbi =  bigBedFileOpenAlias(linkFileName, chromAliasFindAliases);
 struct bigBedInterval *linkBb, *linkBbList =  bigBedIntervalQuery(linkBbi, chrom, start, end, 0, lm);
 char *bedRow[12];
 char startBuf[16], endBuf[16];
 
 struct chain *chainList = NULL;