ef4a5ad1ea45eda4fde693f56f2106c553e87d93
chmalee
  Wed May 20 15:24:30 2026 -0700
Add cnv type to myVariants, refs #33808

diff --git src/hg/lib/myVariants.c src/hg/lib/myVariants.c
index 02ddd3bf6cf..9829cf711f5 100644
--- src/hg/lib/myVariants.c
+++ src/hg/lib/myVariants.c
@@ -1,29 +1,58 @@
 #include "common.h"
 #include "linefile.h"
 #include "dystring.h"
 #include "jksql.h"
 #include "myVariants.h"
 #include "myVariantsShare.h"
 #include "customTrack.h"
 #include "hdb.h"
 #include "hgConfig.h"
 #include "cheapcgi.h"
 #include "trashDir.h"
 #include "obscure.h"
 #include "wikiLink.h"
 
+char *myVariantsItemTypes[] = { "transcript", "snv", "cnv" };
+int myVariantsNumItemTypes = ArraySize(myVariantsItemTypes);
+
+char *myVariantsCnvTypes[] = {
+    "deletion", "duplication", "insertion", "inversion",
+    "translocation", "complex", "breakend"
+};
+int myVariantsNumCnvTypes = ArraySize(myVariantsCnvTypes);
+
+char *myVariantsCanonicalItemType(char *s)
+/* Return the matching canonical entry from myVariantsItemTypes (case-insensitive),
+ * or NULL if s is empty or not in the allow-list. */
+{
+if (isEmpty(s))
+    return NULL;
+int ix = stringArrayIx(s, myVariantsItemTypes, myVariantsNumItemTypes);
+return ix < 0 ? NULL : myVariantsItemTypes[ix];
+}
+
+char *myVariantsCanonicalCnvType(char *s)
+/* Return the matching canonical entry from myVariantsCnvTypes (case-insensitive),
+ * or NULL if s is empty or not in the allow-list. */
+{
+if (isEmpty(s))
+    return NULL;
+int ix = stringArrayIx(s, myVariantsCnvTypes, myVariantsNumCnvTypes);
+return ix < 0 ? NULL : myVariantsCnvTypes[ix];
+}
+
 boolean isMyVariantsType(char *type)
 /* TRUE if type names the myVariants custom-track type. NULL-safe. */
 {
 return sameOk(type, MYVARIANTS_TYPE);
 }
 
 boolean isMyVariantsTrack(char *trackName)
 /* TRUE if trackName is a myVariants custom track (own or shared). NULL-safe. */
 {
 return trackName != NULL && startsWith(MYVARIANTS_TRACK_PREFIX, trackName);
 }
 
 boolean isMyVariantsSharedTrack(char *trackName)
 /* TRUE if trackName is a myVariants shared custom track. NULL-safe. */
 {
@@ -43,31 +72,33 @@
 safecpy(ret->strand, sizeof(ret->strand), row[6]);
 ret->thickStart = sqlUnsigned(row[7]);
 ret->thickEnd = sqlUnsigned(row[8]);
 ret->itemRgb = sqlUnsigned(row[9]);
 ret->blockCount = sqlUnsigned(row[10]);
 sqlSignedDynamicArray(row[11], &ret->blockSizes, &sizeOne);
 assert(sizeOne == ret->blockCount);
 sqlSignedDynamicArray(row[12], &ret->chromStarts, &sizeOne);
 assert(sizeOne == ret->blockCount);
 ret->description = row[13];
 ret->db = row[14];
 ret->ref = row[15];
 ret->alt = row[16];
 ret->project = row[17];
 ret->mouseover = row[18];
-ret->id = sqlUnsigned(row[19]);
+ret->itemType = row[19];
+ret->cnvType = row[20];
+ret->id = sqlUnsigned(row[21]);
 }
 
 struct myVariants *myVariantsLoadByQuery(struct sqlConnection *conn, char *query)
 /* Load all myVariants from table that satisfy the query given. Dispose of this with myVariantsFreeList(). */
 {
 struct myVariants *list = NULL, *el;
 struct sqlResult *sr;
 char **row;
 sr = sqlGetResult(conn, query);
 while ((row = sqlNextRow(sr)) != NULL)
     {
     el = myVariantsLoad(row);
     slAddHead(&list, el);
     }
 slReverse(&list);
@@ -82,44 +113,46 @@
 int i;
 for (i = 0; i < n; i++)
     dyStringPrintf(dy, "%d,", arr[i]);
 return dyStringCannibalize(&dy);
 }
 
 void myVariantsSaveToDb(struct sqlConnection *conn, struct myVariants *el, char *tableName, int updateSize)
 /* Save myVariants as a row to the table specified by tableName.
  * Uses explicit column names so custom fields in el->customFields are included.
  * If el->name is NULL or empty, fills it in post-INSERT as "Variant N" using
  * the row's auto-increment id; sqlLastAutoId wraps MariaDB's mysql_insert_id,
  * which is per-connection and unaffected by concurrent INSERTs on other
  * connections. */
 {
 struct dyString *update = dyStringNew(updateSize);
-sqlDyStringPrintf(update, "insert into %s (bin,chrom,chromStart,chromEnd,name,score,strand,thickStart,thickEnd,itemRgb,blockCount,blockSizes,chromStarts,description,db,ref,alt,project,mouseover", tableName);
+sqlDyStringPrintf(update, "insert into %s (bin,chrom,chromStart,chromEnd,name,score,strand,thickStart,thickEnd,itemRgb,blockCount,blockSizes,chromStarts,description,db,ref,alt,project,mouseover,itemType,cnvType", tableName);
 
 /* Append custom field column names */
 struct slPair *cf;
 for (cf = el->customFields; cf != NULL; cf = cf->next)
     sqlDyStringPrintf(update, ",%s", cf->name);
 
 char *insertName = isEmpty(el->name) ? "" : el->name;
 char *blockSizesStr = commaIntList(el->blockSizes, el->blockCount);
 char *chromStartsStr = commaIntList(el->chromStarts, el->blockCount);
-sqlDyStringPrintf(update, ") values (%u,'%s',%u,%u,'%s',%u,'%s',%u,%u,%u,%u,'%s','%s','%s','%s','%s','%s','%s','%s'",
+sqlDyStringPrintf(update, ") values (%u,'%s',%u,%u,'%s',%u,'%s',%u,%u,%u,%u,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s'",
     el->bin, el->chrom, el->chromStart, el->chromEnd, insertName, el->score, el->strand,
     el->thickStart, el->thickEnd, el->itemRgb, el->blockCount, blockSizesStr, chromStartsStr,
-    el->description, el->db, el->ref, el->alt, el->project, el->mouseover);
+    el->description, el->db, el->ref, el->alt, el->project, el->mouseover,
+    isEmpty(el->itemType) ? "snv" : el->itemType,
+    isEmpty(el->cnvType) ? "" : el->cnvType);
 freeMem(blockSizesStr);
 freeMem(chromStartsStr);
 
 /* Append custom field values */
 for (cf = el->customFields; cf != NULL; cf = cf->next)
     sqlDyStringPrintf(update, ",'%s'", (char *)cf->val);
 
 sqlDyStringPrintf(update, ")");
 sqlUpdate(conn, update->string);
 dyStringFree(&update);
 
 if (isEmpty(el->name))
     {
     unsigned int newId = sqlLastAutoId(conn);
     struct dyString *nameUpdate = sqlDyStringCreate(
@@ -149,31 +182,33 @@
 safecpy(ret->strand, sizeof(ret->strand), row[6]);
 ret->thickStart = sqlUnsigned(row[7]);
 ret->thickEnd = sqlUnsigned(row[8]);
 ret->itemRgb = sqlUnsigned(row[9]);
 ret->blockCount = sqlUnsigned(row[10]);
 sqlSignedDynamicArray(row[11], &ret->blockSizes, &sizeOne);
 assert(sizeOne == ret->blockCount);
 sqlSignedDynamicArray(row[12], &ret->chromStarts, &sizeOne);
 assert(sizeOne == ret->blockCount);
 ret->description = cloneString(row[13]);
 ret->db = cloneString(row[14]);
 ret->ref = cloneString(row[15]);
 ret->alt = cloneString(row[16]);
 ret->project = cloneString(row[17]);
 ret->mouseover = cloneString(row[18]);
-ret->id = sqlUnsigned(row[19]);
+ret->itemType = cloneString(row[19]);
+ret->cnvType = cloneString(row[20]);
+ret->id = sqlUnsigned(row[21]);
 return ret;
 }
 
 struct myVariants *myVariantsLoadAll(char *fileName)
 /* Load all myVariants from a whitespace-separated file. Dispose of this with myVariantsFreeList(). */
 {
 struct myVariants *list = NULL, *el;
 struct lineFile *lf = lineFileOpen(fileName, TRUE);
 char *row[MYVARIANTS_NUM_COLS];
 while (lineFileRow(lf, row))
     {
     el = myVariantsLoad(row);
     slAddHead(&list, el);
     }
 lineFileClose(&lf);
@@ -228,50 +263,54 @@
     int i;
     s = sqlEatChar(s, '{');
     if (ret->blockCount > 0)
         AllocArray(ret->chromStarts, ret->blockCount);
     for (i=0; i<ret->blockCount; ++i)
         ret->chromStarts[i] = sqlSignedComma(&s);
     s = sqlEatChar(s, '}');
     s = sqlEatChar(s, ',');
     }
 ret->description = sqlStringComma(&s);
 ret->db = sqlStringComma(&s);
 ret->ref = sqlStringComma(&s);
 ret->alt = sqlStringComma(&s);
 ret->project = sqlStringComma(&s);
 ret->mouseover = sqlStringComma(&s);
+ret->itemType = sqlStringComma(&s);
+ret->cnvType = sqlStringComma(&s);
 ret->id = sqlUnsignedComma(&s);
 *pS = s;
 return ret;
 }
 
 void myVariantsFree(struct myVariants **pEl)
 /* Free a single dynamically allocated myVariants such as created with myVariantsLoad(). */
 {
 struct myVariants *el;
 if ((el = *pEl) == NULL) return;
 freeMem(el->chrom);
 freeMem(el->name);
 freeMem(el->blockSizes);
 freeMem(el->chromStarts);
 freeMem(el->description);
 freeMem(el->db);
 freeMem(el->ref);
 freeMem(el->alt);
 freeMem(el->project);
 freeMem(el->mouseover);
+freeMem(el->itemType);
+freeMem(el->cnvType);
 slPairFreeValsAndList(&el->customFields);
 freez(pEl);
 }
 
 void myVariantsFreeList(struct myVariants **pList)
 /* Free a list of dynamically allocated myVariants's */
 {
 struct myVariants *el, *next;
 for (el = *pList; el != NULL; el = next)
     {
     next = el->next;
     myVariantsFree(&el);
     }
 *pList = NULL;
 }
@@ -341,30 +380,38 @@
 fprintf(f, "%s", el->ref);
 if (sep == ',') fputc('"',f);
 fputc(sep,f);
 if (sep == ',') fputc('"',f);
 fprintf(f, "%s", el->alt);
 if (sep == ',') fputc('"',f);
 fputc(sep,f);
 if (sep == ',') fputc('"',f);
 fprintf(f, "%s", el->project);
 if (sep == ',') fputc('"',f);
 fputc(sep,f);
 if (sep == ',') fputc('"',f);
 fprintf(f, "%s", el->mouseover);
 if (sep == ',') fputc('"',f);
 fputc(sep,f);
+if (sep == ',') fputc('"',f);
+fprintf(f, "%s", el->itemType ? el->itemType : "");
+if (sep == ',') fputc('"',f);
+fputc(sep,f);
+if (sep == ',') fputc('"',f);
+fprintf(f, "%s", el->cnvType ? el->cnvType : "");
+if (sep == ',') fputc('"',f);
+fputc(sep,f);
 fprintf(f, "%u", el->id);
 fputc(lastSep,f);
 }
 
 static char *myVariantsAutoSqlString =
 "table myVariants\n"
 "\"An item in a myVariants type track.\"\n"
 "    (\n"
 "    uint bin;         \"Bin for range index\"\n"
 "    string chrom;     \"Reference sequence chromosome or scaffold\"\n"
 "    uint   chromStart;\"Start position in chromosome\"\n"
 "    uint   chromEnd;  \"End position in chromosome\"\n"
 "    string name;      \"Name of item - up to 16 chars\"\n"
 "    uint  score;      \"0-1000.  Higher numbers are darker.\"\n"
 "    char[1] strand;   \"+ or - for strand\"\n"
@@ -646,30 +693,32 @@
             "    name varchar(255) not null,\n"
             "    score int unsigned not null,\n"
             "    strand char(1) not null,\n"
             "    thickStart int unsigned not null,\n"
             "    thickEnd int unsigned not null,\n"
             "    itemRgb int unsigned not null,\n"
             "    blockCount int unsigned not null,\n"
             "    blockSizes longblob not null,\n"
             "    chromStarts longblob not null,\n"
             "    description longblob not null,\n"
             "    db varchar(255) not null,\n"
             "    ref varchar(255) not null,\n"
             "    alt varchar(255) not null,\n"
             "    project varchar(255) not null,\n"
             "    mouseover varchar(255) not null,\n"
+            "    itemType varchar(16) not null default 'snv',\n"
+            "    cnvType varchar(32) not null default '',\n"
             "    id int auto_increment,\n"
             "    PRIMARY KEY(id),\n"
             "    INDEX(chrom(16),bin),\n"
             "    INDEX(db),\n"
             "    INDEX(project)\n"
             ") ENGINE=InnoDB;", db, tableName);
     sqlUpdate(conn, dyStringCannibalize(&createTable));
     return myVariantsGetDbTable(userName);
     }
 } 
 
 
 static void readLabelsFromCtFile(char *path, char *trackName,
                                  char **retShort, char **retLong)
 /* Parse the matching track line in an existing ctfile and pull shortLabel
@@ -1014,31 +1063,31 @@
 sqlSafef(query, sizeof(query),
     "SELECT DISTINCT project FROM %s WHERE project IS NOT NULL AND project != '' ORDER BY project",
     dbTable);
 projects = sqlQuickList(conn, query);
 hFreeConn(&conn);
 return projects;
 }
 
 /* Built-in column names from myVariants.as - any column NOT in this list is a
  * user-added custom column.  Filtering by name (rather than index) is robust
  * against column reordering or future schema changes. */
 static const char *builtInColumns[] = {
     "bin", "chrom", "chromStart", "chromEnd", "name", "score", "strand",
     "thickStart", "thickEnd", "itemRgb", "blockCount", "blockSizes",
     "chromStarts", "description", "db", "ref", "alt", "project",
-    "mouseover", "id",
+    "mouseover", "itemType", "cnvType", "id",
 };
 
 static boolean isBuiltInColumn(char *name)
 {
 int i;
 for (i = 0;  i < ArraySize(builtInColumns);  i++)
     if (sameString(name, builtInColumns[i]))
         return TRUE;
 return FALSE;
 }
 
 struct slName *myVariantsGetCustomFields(char *userName)
 /* Return list of user-added custom column names for this user's myVariants table.
  * Excludes built-in columns and _hidden_ prefixed columns.
  * Caller must slFreeList the result. Returns NULL if no custom fields or table doesn't exist. */