How to Create and Open Persistent SCID5 Databases
This guide explains how to create, persist, flush, and reopen native SCID5 database collections on disk using the libscid C ABI.
1. Overview of Symbols
scid_database_create_scid5: Creates a new persistent SCID5 database file triad on disk.scid_database_open_scid5: Opens an existing SCID5 database collection.scid_database_save: Flushes uncommitted in-memory index updates, name records, and game bodies to disk storage.scid_database_game_add: Appends games into the persistent database.scid_database_free: Synchronises remaining updates and closes file handles.
2. Complete Recipe
#include <scid/scid.h>
#include <stdio.h>
#include <string.h>
static int
check(
scid_error error,
const char* call)
{
if (error == SCID_OK)
{
return 1;
}
fprintf(stderr, "%s failed with scid_error %hu\n", call, error);
return 0;
}
static int
text_equals(
const char* text,
size_t text_size,
const char* expected)
{
return text_size == strlen(expected) && strncmp(text, expected, text_size) == 0;
}
static void
remove_scid5_database(const char* path)
{
char filename[256];
snprintf(filename, sizeof(filename), "%s.si5", path);
remove(filename);
snprintf(filename, sizeof(filename), "%s.sg5", path);
remove(filename);
snprintf(filename, sizeof(filename), "%s.sn5", path);
remove(filename);
}
static int
parse_pgn(
const char* pgn,
scid_game** out_game)
{
const char* start_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
scid_position* position = NULL;
char diagnostic[1024];
size_t diagnostic_size = 0;
if (!check(
scid_position_create_from_fen(start_fen, &position), "scid_position_create_from_fen") ||
!check(
scid_game_create(
position, pgn, strlen(pgn), out_game, diagnostic, sizeof(diagnostic),
&diagnostic_size),
"scid_game_create"))
{
fprintf(stderr, "%.*s\n", (int)diagnostic_size, diagnostic);
scid_position_free(position);
return 0;
}
scid_position_free(position);
return 1;
}
int
main(void)
{
const char* path = "libscid-example-persistent-database";
const char* pgn = "[Event \"Persistent\"]\n"
"[Site \"Toronto\"]\n"
"[Date \"2026.06.14\"]\n"
"[White \"Alpha\"]\n"
"[Black \"Beta\"]\n"
"[Result \"1-0\"]\n"
"\n"
"1. e4 e5 2. Nf3 1-0\n";
scid_database* created = NULL;
scid_database* reopened = NULL;
scid_game* game = NULL;
char event[128];
char filename[256];
char description[128];
char metadata_key[32];
char metadata_value[128];
char min_date[32];
char max_date[32];
char result[16];
char type[16];
char flags[22];
size_t count = 0;
size_t description_size = 0;
size_t event_size = 0;
size_t filename_size = 0;
size_t result_size = 0;
size_t metadata_count = 0;
size_t metadata_key_size = 0;
size_t metadata_value_size = 0;
size_t min_date_size = 0;
size_t max_date_size = 0;
size_t result_count = 0;
size_t type_size = 0;
size_t flags_size = 0;
int is_open = 0;
int read_only = 0;
remove_scid5_database(path);
if (!parse_pgn(pgn, &game) ||
!check(scid_database_create_scid5(path, &created), "scid_database_create_scid5") ||
!check(scid_database_is_open(created, &is_open), "scid_database_is_open") || !is_open ||
!check(scid_database_game_add(created, game, "M"), "scid_database_game_add") ||
!check(scid_database_game_count_get(created, &count), "scid_database_game_count_get") ||
count != 1 ||
!check(
scid_database_metadata_set(created, "description", "Example persistent database"),
"scid_database_metadata_set") ||
!check(scid_database_save(created), "scid_database_save") ||
!check(scid_database_close(created), "scid_database_close") ||
!check(scid_database_is_open(created, &is_open), "scid_database_is_open") || is_open)
{
scid_game_free(game);
scid_database_free(created);
remove_scid5_database(path);
return 1;
}
scid_database_free(created);
created = NULL;
if (!check(
scid_database_open_scid5_read_only(path, NULL, NULL, NULL, NULL, &reopened),
"scid_database_open_scid5_read_only") ||
!check(
scid_database_type_get(reopened, type, sizeof(type), &type_size),
"scid_database_type_get") ||
!text_equals(type, type_size, "scid5") ||
!check(
scid_database_filename_get(reopened, filename, sizeof(filename), &filename_size),
"scid_database_filename_get") ||
!text_equals(filename, filename_size, "libscid-example-persistent-database.si5") ||
!check(scid_database_read_only_get(reopened, &read_only), "scid_database_read_only_get") ||
!read_only ||
!check(
scid_database_metadata_get(
reopened, "description", description, sizeof(description), &description_size),
"scid_database_metadata_get") ||
!text_equals(description, description_size, "Example persistent database") ||
!check(
scid_database_metadata_count_get(reopened, &metadata_count),
"scid_database_metadata_count_get") ||
metadata_count != 9 ||
!check(
scid_database_metadata_at_get(
reopened, 1, metadata_key, sizeof(metadata_key), &metadata_key_size, metadata_value,
sizeof(metadata_value), &metadata_value_size),
"scid_database_metadata_at_get") ||
!text_equals(metadata_key, metadata_key_size, "description") ||
!text_equals(metadata_value, metadata_value_size, "Example persistent database") ||
!check(
scid_database_stats_date_range_get(
reopened, min_date, sizeof(min_date), &min_date_size, max_date, sizeof(max_date),
&max_date_size),
"scid_database_stats_date_range_get") ||
!text_equals(min_date, min_date_size, "2026.06.14") ||
!text_equals(max_date, max_date_size, "2026.06.14") ||
!check(
scid_database_stats_result_count_get(reopened, "1-0", &result_count),
"scid_database_stats_result_count_get") ||
result_count != 1 ||
!check(scid_database_game_count_get(reopened, &count), "scid_database_game_count_get") ||
count != 1 ||
!check(
scid_database_game_tag_get(reopened, 0, "Event", event, sizeof(event), &event_size),
"scid_database_game_tag_get") ||
!text_equals(event, event_size, "Persistent") ||
!check(
scid_database_game_result_get(reopened, 0, result, sizeof(result), &result_size),
"scid_database_game_result_get") ||
!text_equals(result, result_size, "1-0") ||
!check(
scid_database_game_get(reopened, 0, &game, flags, sizeof(flags), &flags_size),
"scid_database_game_get") ||
!text_equals(flags, flags_size, "M"))
{
scid_game_free(game);
scid_database_free(reopened);
remove_scid5_database(path);
return 1;
}
printf(
"reopened %.*s %.*s: %.*s %.*s flags=%.*s description=%.*s\n", (int)type_size, type,
(int)filename_size, filename, (int)event_size, event, (int)result_size, result,
(int)flags_size, flags, (int)description_size, description);
scid_game_free(game);
scid_database_close(reopened);
scid_database_free(reopened);
remove_scid5_database(path);
return 0;
}
3. Key Concepts and Patterns
The SCID5 File Triad
SCID5 databases consist of three complementary binary files sharing a base path prefix:
.si5: Game index storing compact metadata, offsets, and header flags..sg5: Compressed binary move representations and game body data..sn5: Centralised namebase containing player names, event titles, and sites.
/* Create a new SCID5 database at ./my_games */
scid_database* db = NULL;
scid_database_create_scid5("./my_games", &db);
/* Add games */
scid_database_game_add(db, game, "W");
/* Flush to disk */
scid_database_save(db);
scid_database_free(db);
/* Reopen existing database */
scid_database_open_scid5("./my_games", &db);
/* ... read records ... */
scid_database_free(db);