fix(historian): #683 review — a refused tag ADD INDEX only warns; document index trade-offs

- Bootstrapper: an ALTER ... ADD INDEX refused up front (e.g. no DDL rights)
  logs a Warning and lets the bootstrap succeed (the index is optional); a
  WAL-apply failure remains unobserved, like the TTL ALTER — documented.
  Integration test (VARCHAR tag => refused ALTER) fails with the catch removed.
- Historian.md: a dominant tag can read ~20% slower on long windows (single-
  threaded index scan vs parallel JIT filter); O3 writes rewrite index files;
  sample_modified.tag / event.source deliberately not indexed, and why.
- CLAUDE.md: the concurrent-HistoryUpdate-delete caveat on the range-first order.
This commit is contained in:
Joseph Doherty
2026-10-12 10:15:37 -04:00
parent 549d2d68b3
commit cc0cc8cd89
4 changed files with 54 additions and 7 deletions
@@ -83,6 +83,26 @@ public sealed class TagIndexTests(QuestDbFixture fx)
(await b.TagIndexedAsync()).ShouldBeTrue();
}
[Fact]
public async Task A_refused_ADD_INDEX_does_not_fail_the_bootstrap()
{
fx.SkipIfUnavailable();
// A sample table whose tag column cannot be indexed (VARCHAR, not SYMBOL): the ALTER is refused up front, which
// stands in for any refusal (e.g. no DDL rights). The index is optional, so the bootstrap must still succeed.
var p = fx.UniquePrefix();
var o = fx.Options(p);
var conn = new QuestDbConnection(o);
await using (var c = await conn.OpenAsync(Ct))
{
var ddl = QuestDbTables.CreateSample(p, null).Replace("tag SYMBOL INDEX", "tag VARCHAR", StringComparison.Ordinal);
await using var cmd = conn.CreateCommand(c, ddl);
await cmd.ExecuteNonQueryAsync(Ct);
await using var alter = conn.CreateCommand(c, QuestDbTables.AddTagIndex(conn.SampleTable));
await Should.ThrowAsync<Exception>(() => alter.ExecuteNonQueryAsync(Ct)); // precondition: really refused
}
await new QuestDbSchemaBootstrapper(conn, o).EnsureAsync(Ct);
}
[Fact]
public async Task The_data_range_seeks_the_tag_index()
{