# 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 are compiled alongside your own code. The word "incremental" means the generator tracks which inputs changed between builds and only re-runs the parts that are affected — making repeated builds fast even in large solutions. No code runs at application startup; everything the generator produces is plain C# that is 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]`, etc.), as well as C# nullable annotations (`string?`, `int?`, etc.).
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.0/advanced/procedure-mapping)).

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

While it reads your tables and `.sql` files, the generator also validates them and reports problems — unsupported attribute combinations, missing primary keys, malformed procedure headers, unresolvable schema placeholders, and more — as standard `SCGDB###` build diagnostics. See [Generator diagnostics](/database/0.3.0/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 time it is called it:

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 object? Value      { get; }   // current property value (after convertor)
    public Action<object?> SetValue { get; } // writes back to the property (used by RETURNING)
    public bool IsJson        { get; }   // column is stored as JSON
    public bool IsPrimaryKey  { get; }   // column is part of the primary key
    public bool HasDbDefault  { get; }   // column has a [Default] attribute or C# initializer
}
```

`SetValue` is used by `WithValuePropagation()` to write DB-generated values (e.g. 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 are converted 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 that only classes whose declaration or attribute metadata changed are re-processed. In practice this means:

- 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 in the file system:

```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 (e.g. `user.Insert()`) navigates directly to the generated file. This works without any extra IDE plugin.

---

## Multiple database engines

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

In other words: **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 is `Postgresql`-prefixed.

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 right now — 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 that is fully compatible with .NET Native AOT. Nothing at runtime uses reflection:

- `GetColumns()` is a hand-written dictionary-building method — no `PropertyInfo` scanning.
- WHERE expression trees in `Query(x => ...)` are compiled to parameterized SQL by the query builder at build time, not 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.
