using System; using System.Collections.Generic; namespace GB5Shared.ListQuery { // ───────────────────────────────────────────────────────────────────── // QueryContext – carries the alias map and optional schema map for // one specific query. // // Problem it solves: // Legacy SqlClauses methods hardcoded table aliases as magic strings // ("a.", "b.", "e."), so changing an alias broke every clause method. // QueryContext makes aliases declarative: each query builder registers // its own alias map, and all clause methods use ctx.Col() to qualify // columns — no "a." floating in the clause library. // // Schema support: // Existing tables are unqualified (dbo, default schema). New tables // in module-specific schemas (e.g. "mm") are registered with a schema. // ctx.Table() emits "mm.tmtrrecord" for schema-qualified tables and // just "tallocation" for unqualified ones — zero impact on existing QB files. // // Usage: // var ctx = new QueryContext(dialect) // .Register("allocation", "a") // unqualified table // .Register("mtrrecord", "r", schema:"mm"); // schema-qualified // // ctx.Col("allocation", "ALLOCATIONID") → "a.ALLOCATIONID" // ctx.Table("mtrrecord", "tmtrrecord") → "mm.tmtrrecord" // ctx.Table("allocation", "tallocation") → "tallocation" // ───────────────────────────────────────────────────────────────────── public sealed class QueryContext { private readonly Dictionary _aliases = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _schemas = new(StringComparer.OrdinalIgnoreCase); /// The SQL dialect active for this query. public ISqlDialect Dialect { get; } public QueryContext(ISqlDialect dialect) => Dialect = dialect; // ── Registration ───────────────────────────────────────────────── /// /// Registers a logical table name with its SQL alias and optional schema. /// /// Logical name used in builder code (e.g. "allocation"). /// SQL alias used in the query (e.g. "a"). /// /// Optional DB schema (e.g. "mm"). When empty the table is referenced without /// a schema prefix — matching the existing GB5 convention for dbo tables. /// public QueryContext Register(string logicalName, string alias, string schema = "") { _aliases[logicalName] = alias; if (!string.IsNullOrEmpty(schema)) _schemas[logicalName] = schema; return this; } // ── Column qualification ────────────────────────────────────────── /// /// Returns "alias.column" for the given logical table and column name. /// Example: ctx.Col("allocation", "ALLOCATIONID") → "a.ALLOCATIONID" /// public string Col(string logicalTable, string column) => $"{Alias(logicalTable)}.{column}"; /// /// Returns just the alias for the logical table name. /// Falls back to the logical name itself if not registered (safe for simple queries). /// public string Alias(string logicalName) => _aliases.TryGetValue(logicalName, out var alias) ? alias : logicalName; // ── Table name (with optional schema) ───────────────────────────── /// /// Returns the fully qualified table reference for use in FROM/JOIN clauses. /// /// When schema is registered: returns "schema.tableName" (e.g. "mm.tmtrrecord") /// When no schema: returns just "tableName" (e.g. "tallocation") /// /// The alias is NOT appended here — callers write: /// $"FROM {ctx.Table("mtrrecord", "tmtrrecord")} r" /// /// Logical name used when registering (e.g. "mtrrecord"). /// Physical table name (e.g. "tmtrrecord"). public string Table(string logicalName, string tableName) { if (_schemas.TryGetValue(logicalName, out var schema) && !string.IsNullOrEmpty(schema)) return $"{schema}.{tableName}"; return tableName; } } }