/// domagi --- DuckDB-powered pangenome Swiss Army knife /// Copyright © 2026 Arun Isaac /// /// This file is part of domagi. /// /// domagi is free software: you can redistribute it and/or modify it under the /// terms of the GNU General Public License as published by the Free Software /// Foundation, either version 3 of the License, or (at your option) any later /// version. /// /// domagi is distributed in the hope that it will be useful, but WITHOUT ANY /// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS /// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more /// details. /// /// You should have received a copy of the GNU General Public License along with /// domagi. If not, see . #include #include #include #include #include #define NAME str_int_map #define KEY_TY const char* #define VAL_TY int #include void die(const char *message) { fprintf(stderr, "%s\n", message); exit(EXIT_FAILURE); } static int ceildiv (int x, int y) { return (x / y) + (x % y ? 1 : 0); } static int hashtable_get (str_int_map *table, const char *key) { // Look up key in hash table and return the corresponding key. The program is // aborted if the key is not found. This is ok for our limited purposes since // all keys we look up are guaranteed to be present. str_int_map_itr itr = vt_get(table, key); assert(!vt_is_end(itr)); return itr.data->val; } static uint8_t orientation2int (char c) { return c == '+' ? 0 : 1; } static char invert_orientation (char c) { return c == '+' ? '-' : '+'; } static void pass1_handle_s_line (char *line, str_int_map *segment_id_table, duckdb_appender *appender) { static int segment_id = 0; // Split S line. strsep(&line, "\t"); char *segment_name = strsep(&line, "\t"); char *sequence = strsep(&line, "\t\n"); // Map segment name to ID in table. vt_insert(segment_id_table, strdup(segment_name), segment_id); // Append to database. duckdb_append_int32(*appender, segment_id); duckdb_append_varchar(*appender, segment_name); duckdb_append_varchar(*appender, sequence); duckdb_appender_end_row(*appender); // Increment segment ID for next segment. segment_id++; } static void pass1_handle_p_line (char *line, duckdb_appender *appender) { static int path_id = 0; // Split P line. strsep(&line, "\t"); char *path_name = strsep(&line, "\t"); // Append to database. There are usually relatively few P lines. So, we're // content with a simple row-wise appender. duckdb_append_int32(*appender, path_id); duckdb_append_varchar(*appender, path_name); duckdb_appender_end_row(*appender); // Increment path ID for next path. path_id++; } #define LINK_COLUMN_COUNT 4 static void process_l_lines (char **lines, size_t line_count, str_int_map *segment_id_table, duckdb_database *db) { idx_t vector_size = duckdb_vector_size(); duckdb_logical_type int_type = duckdb_create_logical_type(DUCKDB_TYPE_INTEGER); duckdb_logical_type orientation_type = duckdb_create_enum_type((const char *[]){"+", "-"}, 2); duckdb_logical_type types[LINK_COLUMN_COUNT] = {int_type, orientation_type, int_type, orientation_type}; #pragma omp parallel { duckdb_connection con; if (duckdb_connect(*db, &con) == DuckDBError) die("Unable to connect to DuckDB database"); duckdb_appender appender; if (duckdb_appender_create(con, NULL, "link", &appender) == DuckDBError) die("Unable to create appender for link table"); duckdb_data_chunk chunk = duckdb_create_data_chunk(types, LINK_COLUMN_COUNT); duckdb_data_chunk_set_size(chunk, vector_size); int32_t *from_segment_data = duckdb_vector_get_data(duckdb_data_chunk_get_vector(chunk, 0)); uint8_t *from_orientation_data = duckdb_vector_get_data(duckdb_data_chunk_get_vector(chunk, 1)); int32_t *to_segment_data = duckdb_vector_get_data(duckdb_data_chunk_get_vector(chunk, 2)); uint8_t *to_orientation_data = duckdb_vector_get_data(duckdb_data_chunk_get_vector(chunk, 3)); #pragma omp for for (size_t slice_index=0; slice_index 0) { switch (line[0]) { case 'S': pass1_handle_s_line(line, &segment_id_table, &segment_appender); break; case 'P': pass1_handle_p_line(line, &path_appender); break; case 'L': l_line_count++; break; default: break; } } if (duckdb_appender_destroy(&segment_appender) == DuckDBError) die("Unable to destroy segment appender"); if (duckdb_appender_destroy(&path_appender) == DuckDBError) die("Unable to destroy path appender"); } rewind(gfa_fp); // Pass 2 { char **l_lines = malloc(l_line_count*sizeof(char*)); size_t l_line_index = 0, path_id = 0; while ((line_length = getline(&line, &n, gfa_fp)) > 0) { switch (line[0]) { case 'L': l_lines[l_line_index] = strdup(line); l_line_index++; break; case 'P': process_p_line(line, line_length, &segment_id_table, path_id, &db); path_id++; break; default: break; } } process_l_lines(l_lines, l_line_count, &segment_id_table, &db); free(l_lines); } free(line); fclose(gfa_fp); duckdb_disconnect(&con); duckdb_close(&db); for (str_int_map_itr itr=vt_first(&segment_id_table); !vt_is_end(itr); itr=vt_next(itr)) free((char*)itr.data->key); vt_cleanup(&segment_id_table); return 0; } int main (int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(EXIT_FAILURE); } return import_gfa(argv[1], argv[2]); }