# How it works

A deep dive into the Roslyn incremental source generator: what it reads, what it emits, and how to inspect the output.

## What a Roslyn incremental source generator is

A Roslyn incremental source generator is a .NET SDK feature that plugs into the C# compiler pipeline. It receives syntax trees and semantic models for every file in your project, runs arbitrary analysis, and writes additional `.cs` files that compile alongside your own code. "Incremental" means the generator tracks which inputs changed between builds and re-runs only the affected parts, so repeated builds stay fast even in large solutions. Nothing runs at application startup. Everything the generator produces is plain C# compiled at build time.

## What the generator does at build time

When you run `dotnet build`, the generator:

1. Scans every class in the compilation that carries a `[Table]` attribute.
2. Reads the attribute arguments (table name, schema) and inspects each property's attributes (`[PrimaryKey]`, `[Default]`, `[Column]`, `[ValueConvertor]`, `[Json]`, and so on), plus C# nullable annotations (`string?`, `int?`).
3. Emits one companion `.g.cs` file per annotated class into `obj/{Configuration}/net{version}/generated/Socigy.OpenSource.DB.SourceGenerator/Socigy.OpenSource.DB.SourceGenerator.Program/`.
4. Separately processes `<AdditionalFiles>` `.sql` files to emit procedure wrapper methods (see [Procedure mapping](/database/0.3.2/advanced/procedure-mapping)).

The compiler sees the generated files as ordinary source. No reflection, no dynamic proxy, no IL weaving.

> **NOTE** The table attributes and `DbDefaults` live in `Socigy.OpenSource.DB.Attributes`. The one exception is `[Nullable]`, which lives in `Socigy.OpenSource.DB.Core.Attributes`.

While it reads your tables and `.sql` files, the generator validates them and reports problems as standard `SCGDB###` build diagnostics: unsupported attribute combinations, missing primary keys, malformed procedure headers, unresolvable schema placeholders, and more. See [Generator diagnostics](/database/0.3.2/advanced/generator-diagnostics) for the full catalog.

## What each generated file contains

### IDbTable implementation

Every generated file makes the class implement `IDbTable`:

```csharp
public interface IDbTable
{
    string GetTableName();
    Dictionary<string, ColumnInfo> GetColumns();
    Dictionary<string, ColumnInfo> GetPrimaryColumns();
    (string Name, ColumnInfo Info)? GetColumn(string name);
    string? GetDbColumnName(string memberName);
}
```

| Method              | What it returns                                                        |
|---------------------|------------------------------------------------------------------------|
| `GetTableName()`    | The SQL table name from `[Table("...")]`                               |
| `GetColumns()`      | A `Dictionary<string, ColumnInfo>` built at call time (see below)      |
| `GetPrimaryColumns()` | A `Dictionary<string, ColumnInfo>` containing only the `[PrimaryKey]` columns |
| `GetColumn(name)`   | A `(string Name, ColumnInfo Info)?` tuple for the given C# property name, or `null` |
| `GetDbColumnName(name)` | The snake_case DB column name for a given C# property name         |

### GetColumns(): the runtime column map

`GetColumns()` is the core of the query infrastructure. Each call:

1. Reads the current value of every mapped property on `this`.
2. Applies any `[ValueConvertor]` transformation to the value.
3. Packages the result into a `ColumnInfo` struct with metadata flags.

The returned `Dictionary<string, ColumnInfo>` is keyed by snake_case DB column name.

### ColumnInfo struct

```csharp
public struct ColumnInfo
{
    public Type Type                { get; set; }   // CLR type of the column
    public object? Value            { get; set; }   // current value from the row instance (after convertor)
    public bool IsPrimaryKey        { get; set; }   // part of the primary key
    public bool IsAutoIncrement     { get; set; }   // sequence-backed; excluded from INSERT by default
    public bool HasDbDefault        { get; set; }   // has a DB DEFAULT expression from [Default]
    public bool IsJson              { get; set; }   // stored as jsonb
    public bool IsEncrypted         { get; set; }   // [Encrypted]; stored as bytea (ciphertext)
    public Action<object?>? SetValue { get; set; }  // writes a value read back from the DB into the row

    public static T? ApplyDbValue<T>(object? dbValue); // converts a raw DB value to T
}
```

`SetValue` is used by `WithValuePropagation()` to write DB-generated values (auto-UUID, server timestamp) back into the C# object after an INSERT.

### Instance builder methods

The builder methods live on the entity instance. Call them on an object you already have:

```csharp
user.Insert()                  // PostgresqlInsertCommandBuilder<User>
user.Update()                  // PostgresqlUserUpdateCommandBuilder<User>
user.Delete()                  // PostgresqlUserDeleteCommandBuilder<User>
```

### Static query and delete methods

```csharp
static TableQueryBuilder User.Query();
static TableQueryBuilder User.Query(Expression<Func<User, bool>> predicate);
static PostgresqlUserDeleteCommandBuilder<User> User.DeleteNonInstance(); // filtered delete, no instance needed
```

### Async static shorthands

One-liner shortcuts for the common case, no builder needed:

```csharp
static Task<bool> User.InsertAsync(User instance, DbConnection connection);
static Task<int>  User.UpdateAsync(User instance, DbConnection connection);
```

### Column name constants

The generator emits a `{PropertyName}ColumnName` constant directly on the class for every mapped property, using the snake_case database column name:

```csharp
User.IdColumnName         // => "id"
User.UsernameColumnName   // => "username"
User.CreatedAtColumnName  // => "created_at"
```

Property names convert to snake_case automatically. These constants are useful when building dynamic SQL fragments or constructing ORDER BY / GROUP BY clauses without hard-coded strings.

## Incremental builds

The generator participates in Roslyn's incremental pipeline. It registers syntax predicates and semantic transforms, so only classes whose declaration or attribute metadata changed get re-processed. In practice:

- Changing `Product.cs` does not re-generate `User.generated.cs`.
- Adding a new `[Table]` class triggers generation only for that class.
- Cold builds regenerate everything; warm builds regenerate only diffs.

## Inspecting generated code

Generated files are readable C# placed under the intermediate output path. Add `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>` to your `.csproj` to make them visible on disk:

```plaintext
obj/{Configuration}/net{version}/generated/Socigy.OpenSource.DB.SourceGenerator/Socigy.OpenSource.DB.SourceGenerator.Program/
```

Opening these files is the most direct way to diagnose unexpected query behavior. You can see exactly which columns are included, how the WHERE clause is built, and which parameters are parameterized.

> **TIP** In Visual Studio and JetBrains Rider, pressing F12 (Go to Definition) on a generated method such as `user.Insert()` navigates straight to the generated file. No extra IDE plugin needed.

## Multiple database engines

The library is built around an engine-agnostic core. Everything the generator reads is independent of any single database: the `[Table]` and column attributes, the `IDbTable` contract, the `ColumnInfo` model, the WHERE expression-tree analysis, and the query/command builder abstractions. The PostgreSQL-specific pieces (the `Postgresql*CommandBuilder<T>` types, the DDL dialect, the type mapping) sit in a thin layer on top.

The core is ready for multiple engines today. The per-engine implementations simply haven't been written yet. PostgreSQL is currently the only engine with a complete implementation, which is why every generated builder you see carries the `Postgresql` prefix.

This is a deliberate design goal, not a limitation baked into the architecture. Adding another engine (MySQL, SQLite, SQL Server) means implementing that engine's builders and dialect against the existing core abstractions. It does not require touching the source generator or the core model.

> **NOTE** Multi-engine support is on the roadmap, and the groundwork is already in place. If you need an engine that isn't here yet, nothing blocks you from contributing it: the core is intentionally open for exactly this. Contributions are welcome on [GitHub](https://github.com/WailedParsley36/Socigy.OpenSource.DB).

## AOT compatibility

The generator emits code fully compatible with .NET Native AOT. Nothing at runtime uses reflection:

- `GetColumns()` is a hand-written dictionary-building method, with no `PropertyInfo` scanning.
- WHERE expression trees in `Query(x => ...)` compile to parameterized SQL in the query builder, never reflected over at runtime.
- JSON columns serialize through a `JsonSerializerContext` you provide, which is also AOT-safe.

This makes `Socigy.OpenSource.DB` suitable for publishing with `PublishAot=true` without trimmer warnings from the DB layer.
</content>
</invoke>
