9af188ea6147f9edb20bd531d3ec988501cf997c chmalee Fri Feb 6 12:18:25 2026 -0800 Fix jsonOutputArrays columnTypes output when more than one track is requested with /getData/track. Leaves /list/schema alone. Add option to hgTracks Downloads -> Download track data in view menu to include column headers in output, refs #36858 diff --git src/hg/hubApi/getData.c src/hg/hubApi/getData.c index a5440083cfb..89c2f62c026 100644 --- src/hg/hubApi/getData.c +++ src/hg/hubApi/getData.c @@ -84,31 +84,31 @@ jsonWriteObjectStart(jw, NULL); for (i = 0; i < columnCount; ++i) jsonDatumOut(jw, columnNames[i], row[i], jsonTypes[i]); jsonWriteObjectEnd(jw); } ++itemCount; } sqlFreeResult(&sr); if ((itemCount + itemsDone) >= maxItemsOutput) reachedMaxItems = TRUE; return itemCount; } /* static unsigned sqlQueryJsonOutput(...) */ static void tableDataOutput(char *db, struct trackDb *tdb, struct sqlConnection *conn, struct jsonWrite *jw, char *track, - char *chrom, unsigned start, unsigned end) + char *chrom, unsigned start, unsigned end, struct jsonWrite *columnTypesJw) /* output the SQL table data for given track */ { /* for MySQL select statements, name for 'chrom' 'start' 'end' to use * for a table which has different names than that */ char chromName[256]; char startName[256]; char endName[256]; /* defaults, normal stuff */ safef(chromName, sizeof(chromName), "chrom"); safef(startName, sizeof(startName), "chromStart"); safef(endName, sizeof(endName), "chromEnd"); /* 'track' name in trackDb often refers to a SQL 'table' */ @@ -192,54 +192,54 @@ */ sqlDyStringPrintf(query, "select * from %s", splitSqlTable); } else if (0 == (start + end)) /* have chrom, no start,end == full chr */ { if (! sqlColumnExists(conn, splitSqlTable, chromName)) apiErrAbort(err400, err400Msg, "track '%s' is not a position track, request track without chrom specification, genome: '%s'", track, db); jsonWriteString(jw, "chrom", chrom); struct chromInfo *ci = hGetChromInfo(db, chrom); jsonWriteNumber(jw, "start", (long long)0); jsonWriteNumber(jw, "end", (long long)ci->size); if (tdb && isWiggleDataTable(tdb->type)) { if (jsonOutputArrays || debug) - wigColumnTypes(jw); + wigColumnTypes(columnTypesJw, track); jsonWriteListStart(jw, chrom); itemsReturned += wigTableDataOutput(jw, db, splitSqlTable, chrom, 0, ci->size, 0); jsonWriteListEnd(jw); return; /* DONE */ } else { if (sqlColumnExists(conn, splitSqlTable, startName)) sqlDyStringPrintf(query, "select * from %s where %s='%s' order by %s", splitSqlTable, chromName, chrom, startName); else sqlDyStringPrintf(query, "select * from %s where %s='%s'", splitSqlTable, chromName, chrom); } } else /* fully specified chrom:start-end */ { if (! sqlColumnExists(conn, splitSqlTable, chromName)) apiErrAbort(err400, err400Msg, "track '%s' is not a position track, request track without chrom and start,end specifications, genome: '%s'", track, db); jsonWriteString(jw, "chrom", chrom); if (tdb && isWiggleDataTable(tdb->type)) { if (jsonOutputArrays || debug) - wigColumnTypes(jw); + wigColumnTypes(columnTypesJw, track); jsonWriteListStart(jw, chrom); itemsReturned += wigTableDataOutput(jw, db, splitSqlTable, chrom, start, end, 0); jsonWriteListEnd(jw); return; /* DONE */ } else { sqlDyStringPrintf(query, "select * from %s where ", splitSqlTable); if (sqlColumnExists(conn, splitSqlTable, startName)) { if (hti->hasBin) hAddBinToQuery(start, end, query); sqlDyStringPrintf(query, "%s='%s' AND %s > %u AND %s < %u ORDER BY %s", chromName, chrom, endName, start, startName, end, startName); } else @@ -250,32 +250,32 @@ if (debug) jsonWriteString(jw, "select", query->string); /* continuing, could be wiggle output with no chrom specified */ char **columnNames = NULL; char **columnTypes = NULL; int *jsonTypes = NULL; struct asObject *as = asForTable(conn, splitSqlTable, tdb); if (! as) apiErrAbort(err500, err500Msg, "can not find schema definition for table '%s', genome: '%s'", splitSqlTable, db); struct asColumn *columnEl = as->columnList; int asColumnCount = slCount(columnEl); int columnCount = tableColumns(conn, splitSqlTable, &columnNames, &columnTypes, &jsonTypes); if (jsonOutputArrays || debug) { - outputSchema(tdb, jw, columnNames, columnTypes, jsonTypes, hti, - columnCount, asColumnCount, columnEl); + outputSchema(tdb, columnTypesJw, columnNames, columnTypes, jsonTypes, hti, + columnCount, asColumnCount, columnEl, track); } unsigned itemsDone = 0; /* empty chrom, needs to run through all chrom names */ if (isEmpty(chrom)) { jsonWriteObjectStart(jw, track); /* begin track data output */ char fullTableName[256]; struct chromInfo *ciList = createChromInfoList(NULL, db); slSort(ciList, chromInfoCmp); struct chromInfo *ci = ciList; for ( ; ci && itemsDone < maxItemsOutput; ci = ci->next ) { jsonWriteListStart(jw, ci->chrom); /* starting a chrom output */ @@ -458,30 +458,37 @@ hubUrl); hubAliasSetup(hubGenome); char *chrom = chrOrAlias(genome, hubUrl); struct trackDb *tdb = obtainTdb(hubGenome, NULL); if (NULL == tdb) apiErrAbort(err400, err400Msg, "failed to find a track hub definition in genome=%s for endpoint '/getData/track' given hubUrl='%s'", genome, hubUrl); struct jsonWrite *jw = apiStartOutput(); jsonWriteString(jw, "hubUrl", hubUrl); jsonWriteString(jw, "genome", genome); +struct jsonWrite *columnTypesJw = NULL; +if (jsonOutputArrays || debug) + { + columnTypesJw = jsonWriteNew(); + jsonWriteObjectStart(columnTypesJw, "columnTypes"); + } + // allow optional comma sep list of tracks char *tracks[100]; int numTracks = chopByChar(trackArg, ',', tracks, sizeof(tracks)); int i = 0; for (i = 0; i < numTracks; i++) { char *track = cloneString(tracks[i]); struct trackDb *thisTrack = findTrackDb(track, tdb); if (NULL == thisTrack) apiErrAbort(err400, err400Msg, "failed to find specified track=%s in genome=%s for endpoint '/getData/track' given hubUrl='%s'", track, genome, hubUrl); if (trackHasNoData(thisTrack)) apiErrAbort(err400, err400Msg, "container track '%s' does not contain data, use the children of this container for data access", track); if (! isSupportedType(thisTrack->type)) apiErrAbort(err415, err415Msg, "track type '%s' for track=%s not supported at this time", thisTrack->type, track); @@ -513,61 +520,67 @@ uEnd = sqlUnsigned(end); jsonWriteNumber(jw, "start", uStart); jsonWriteNumber(jw, "end", uEnd); } jsonWriteString(jw, "bigDataUrl", bigDataUrl); jsonWriteString(jw, "trackType", thisTrack->type); if (allowedBigBedType(thisTrack->type)) { struct asObject *as = bigBedAsOrDefault(bbi); if (! as) apiErrAbort(err500, err500Msg, "can not find schema definition for bigDataUrl '%s', track=%s genome: '%s' for endpoint '/getData/track' given hubUrl='%s'", bigDataUrl, track, genome, hubUrl); struct sqlFieldType *fiList = sqlFieldTypesFromAs(as); if (jsonOutputArrays || debug) - bigColumnTypes(jw, fiList, as); + bigColumnTypes(columnTypesJw, fiList, as, track); jsonWriteListStart(jw, track); unsigned itemsDone = 0; if (isEmpty(chrom)) { struct bbiChromInfo *bci; for (bci = chromList; bci && (itemsDone < maxItemsOutput); bci = bci->next) { itemsDone += bbiDataOutput(jw, bbi, bci->name, 0, bci->size, fiList, thisTrack, itemsDone); } if (itemsDone >= maxItemsOutput) reachedMaxItems = TRUE; } else itemsDone += bbiDataOutput(jw, bbi, chrom, uStart, uEnd, fiList, thisTrack, itemsDone); itemsReturned += itemsDone; jsonWriteListEnd(jw); } else if (startsWith("bigWig", thisTrack->type)) { if (jsonOutputArrays || debug) - wigColumnTypes(jw); + wigColumnTypes(columnTypesJw, track); jsonWriteObjectStart(jw, track); bigWigData(jw, bbi, chrom, uStart, uEnd); jsonWriteObjectEnd(jw); } bbiFileClose(&bbi); } +if (jsonOutputArrays || debug) + { + jsonWriteObjectEnd(columnTypesJw); + jsonWriteAppend(jw, NULL, columnTypesJw); + jsonWriteFree(&columnTypesJw); + } apiFinishOutput(0, NULL, jw); } /* static void getHubTrackData(char *hubUrl) */ static void getTrackData() /* return data from a track, optionally just one chrom data, * optionally just one section of that chrom data */ { char *db = cgiOptionalString("genome"); char *chrom = chrOrAlias(db, NULL); char *start = cgiOptionalString("start"); char *end = cgiOptionalString("end"); /* 'track' name in trackDb often refers to a SQL 'table' */ char *trackArg = cgiOptionalString("track"); //char *sqlTable = cloneString(trackArg); /* might be something else */ @@ -596,30 +609,36 @@ if (NULL == conn) apiErrAbort(err400, err400Msg, "can not find genome 'genome=%s' for endpoint '/getData/track", db); struct jsonWrite *jw = apiStartOutput(); jsonWriteString(jw, "genome", db); // load the tracks struct trackDb *tdbList = NULL; cartTrackDbInitForApi(NULL, db, &tdbList, NULL, TRUE); // allow optional comma sep list of tracks char *tracks[100]; int numTracks = chopByChar(trackArg, ',', tracks, sizeof(tracks)); int i = 0; struct hash *trackHash = hashNew(0); // let hub tracks work +struct jsonWrite *columnTypesJw = NULL; +if (jsonOutputArrays || debug) + { + columnTypesJw = jsonWriteNew(); + jsonWriteObjectStart(columnTypesJw, "columnTypes"); + } for (i = 0; i < numTracks; i++) { char *track = cloneString(tracks[i]); char *sqlTable = cloneString(track); if (cartTrackDbIsAccessDenied(db, sqlTable) || (cartTrackDbIsNoGenome(db, sqlTable) && !(chrom && start && end))) apiErrAbort(err403, err403Msg, "this data request: 'db=%s;track=%s' is protected data, see also: https://genome.ucsc.edu/FAQ/FAQdownloads.html#download40", db, track); struct trackDb *thisTrack = tdbForTrack(db, track, &tdbList); if (NULL == thisTrack) { // maybe we have a hub track, try to look it up if (startsWith("hub_", track)) { thisTrack = hubConnectAddHubForTrackAndFindTdb(db, track, &tdbList, trackHash); @@ -744,63 +763,69 @@ /* when start, end given, show them */ if ( uEnd > uStart ) { jsonWriteNumber(jw, "start", uStart); jsonWriteNumber(jw, "end", uEnd); } if (thisTrack && allowedBigBedType(thisTrack->type)) { struct asObject *as = bigBedAsOrDefault(bbi); if (! as) apiErrAbort(err500, err500Msg, "can not find schema definition for bigDataUrl '%s', track=%s genome='%s' for endpoint '/getData/track'", bigDataUrl, track, db); struct sqlFieldType *fiList = sqlFieldTypesFromAs(as); if (jsonOutputArrays || debug) - bigColumnTypes(jw, fiList, as); + bigColumnTypes(columnTypesJw, fiList, as, track); jsonWriteListStart(jw, track); unsigned itemsDone = 0; if (isEmpty(chrom)) { struct bbiChromInfo *bci; for (bci = chromList; bci && (itemsDone < maxItemsOutput); bci = bci->next) { itemsDone += bbiDataOutput(jw, bbi, bci->name, 0, bci->size, fiList, thisTrack, itemsDone); } if (itemsDone >= maxItemsOutput) reachedMaxItems = TRUE; } else itemsDone += bbiDataOutput(jw, bbi, chrom, uStart, uEnd, fiList, thisTrack, itemsDone); itemsReturned += itemsDone; jsonWriteListEnd(jw); } else if (thisTrack && startsWith("bigWig", thisTrack->type)) { if (jsonOutputArrays || debug) - wigColumnTypes(jw); + wigColumnTypes(columnTypesJw, track); jsonWriteObjectStart(jw, track); bigWigData(jw, bbi, chrom, uStart, uEnd); jsonWriteObjectEnd(jw); bbiFileClose(&bbi); } else - tableDataOutput(db, thisTrack, conn, jw, track, chrom, uStart, uEnd); + tableDataOutput(db, thisTrack, conn, jw, track, chrom, uStart, uEnd, columnTypesJw); + } +if (jsonOutputArrays || debug) + { + jsonWriteObjectEnd(columnTypesJw); + jsonWriteAppend(jw, NULL, columnTypesJw); + jsonWriteFree(&columnTypesJw); } apiFinishOutput(0, NULL, jw); hFreeConn(&conn); } /* static void getTrackData() */ static void getSequenceData(char *db, char *hubUrl) /* return DNA sequence, given at least a genome=name and chrom=chr, optionally start and end, might be a track hub for UCSC database */ { char *chrom = chrOrAlias(db, hubUrl); char *start = cgiOptionalString("start"); char *end = cgiOptionalString("end"); boolean revComp = FALSE; char *revCompStr = cgiOptionalString("revComp");