Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain


src/db/entrypoint.sh eol=lf
.githooks/pre-commit eol=lf
43 changes: 43 additions & 0 deletions .github/chat/code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Here is a set of instructions for generating C# code that adheres to our team's conventions.

#### **General Principles**

* **Language and Framework:** All code should be written in the latest stable version of C# for the .NET platform.
* **Clarity and Simplicity:** Prioritize writing code that is clear, simple, and easy to understand. Avoid overly complex or "clever" solutions when a more straightforward one exists.
* **Modern C# Features:** Utilize modern C# features and syntax where appropriate, such as file-scoped namespaces, `using` declarations, and pattern matching.

#### **Naming and Style Conventions**

* **No Underscores:** Do not use underscores (`_`) in method names, variable names, or class names. The only exception is for private backing fields, which should be prefixed with an underscore (e.g., `_fieldName`).
* **Casing:** Follow standard C# casing conventions:
* **PascalCase** for class names, method names, property names, and record names.
* **camelCase** for local variables and method parameters.
* **Implicit vs. Explicit Typing:** Use `var` for local variable declarations when the type is obvious from the right-hand side of the assignment. Otherwise, use the explicit type name for clarity.
* **File Organization:** Each class, interface, struct, or record should be in its own file. The filename should match the type name (e.g., `MyClass.cs`).

#### **Commenting and Documentation**

* **Code Comments:**
* Add comments to explain the "why" behind a piece of code, not the "what." Focus on complex logic, business rules, or non-obvious workarounds.
* Avoid excessive or redundant comments that simply restate what the code does. A clean, well-named method often requires no comments.
* Use `//` for single-line comments.

* **XML Documentation Comments:**
* Generate XML documentation (`///`) for all public and protected members (classes, methods, properties).
* The `<summary>` tag should provide a clear and concise description of the member's purpose.
* Use `<param>` tags to describe each parameter for a method.
* Use `<returns>` tags to describe the method's return value.
* Keep documentation concise and to the point. Avoid overly verbose explanations.

**Example of Good Documentation:**
```csharp
/// <summary>
/// Calculates the total price for a given order.
/// </summary>
/// <param name="orderId">The unique identifier of the order.</param>
/// <returns>The calculated total price of the order.</returns>
public decimal CalculateTotalPrice(int orderId)
{
// Implementation details...
}
```
39 changes: 39 additions & 0 deletions .github/chat/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
When reviewing the selected C# code, please analyze it based on the following priorities, providing feedback in a clear and constructive manner.

#### **Priority 1: Correctness and Functionality**

This is the most critical aspect. Before all else, the code must be correct.

* **Logical Errors:** Scrutinize the code for any logical flaws, off-by-one errors, incorrect assumptions, or potential bugs.
* **Runtime Safety:** Identify potential runtime errors. Pay close attention to possible `NullReferenceException`s, race conditions in concurrent code, and improper resource management (e.g., un-disposed `IDisposable` objects).
* **Compilation:** While the IDE often catches this, double-check that the code is syntactically correct and should compile without errors.

#### **Priority 2: Purpose and Intent**

Once correctness is established, verify that the code is doing the *right* thing.

* **Fulfillment of Purpose:** Based on the method/class names and surrounding code, assess whether the selected code appears to achieve its intended goal. Does it correctly implement the feature or solve the problem it is meant to address?
* **Edge Cases:** Consider if the code handles common edge cases, invalid inputs, or empty collections gracefully.

#### **Priority 3: C# Best Practices and Style**

If the code is correct and fulfills its purpose, focus on its quality, readability, and adherence to our conventions.

* **Naming Conventions:**
* Ensure all identifiers (class, method, property, variable names) are descriptive and clear.
* Verify adherence to standard C# casing: **PascalCase** for public members and types, and **camelCase** for local variables and parameters.
* Confirm that no underscores (`_`) are used, except for private backing fields (e.g., `_fieldName`).

* **Documentation:**
* Check for the presence of XML documentation comments (`///`) on all public and protected members.
* The `<summary>` should clearly explain the member's purpose.
* Ensure methods have `<param>` and `<returns>` tags where appropriate.

* **Immutability and Data Types:**
* Review how data is being modeled. Suggest the use of immutable types like `record` or `readonly struct` where appropriate to prevent unintended side effects and make state easier to reason about.

* **Modern C# Usage:**
* Identify opportunities to simplify the code using modern C# features, such as LINQ, pattern matching, expression-bodied members, or file-scoped namespaces, but only where it improves clarity.

* **Simplicity and Readability:**
* Flag any code that is overly complex or difficult to understand. Suggest simpler, more straightforward alternatives if they exist.
32 changes: 32 additions & 0 deletions .github/chat/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Here is a set of instructions for generating C# tests using xUnit.

#### **General Principles**

* **Framework:** All tests will be written using the xUnit testing framework.
* **Assertions:** Use the standard assertion library provided by xUnit (`Xunit.Assert`). Do not use any third-party assertion libraries like FluentAssertions.
* **Test Types:** Tests should be clearly identified as either "Unit" or "Integration" tests. Unit tests must be self-contained and not require any external resources to run.

#### **Test Naming Convention**

* Test method names should be descriptive and clearly state the scenario being tested and the expected outcome.
* Use a dot-separated naming convention for test methods (e.g., `MethodName.Scenario.ExpectedBehavior`).
* Do not use underscores in test method names.
* Test class names should be the name of the class under test, suffixed with "Tests". A common convention is to name the test project after the project it is testing, with a `.Tests` suffix.

#### **Test Structure**

* Tests must follow the **Arrange, Act, Assert** pattern.
* Use comments (`// Arrange`, `// Act`, `// Assert`) to clearly separate the three parts of the test.

#### **Test Categorization**

To distinguish between unit and integration tests, use the `[Trait]` attribute from xUnit.

* For unit tests, which are self-contained and can run at any time, add the following attribute to the test method or class:
```csharp
[Trait("Category", "Unit")]
```
* For integration tests that rely on external resources (e.g., databases, file systems, network services), use the following attribute:
```csharp
[Trait("Category", "Integration")]
```
91 changes: 91 additions & 0 deletions .github/instructions/sql-gen.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
applyTo: '**'
---

### **Comprehensive Instruction File for GitHub Copilot: C# SQL Code Generator**

#### **Part 1: Vision and Architectural Principles**

**1.1. The Guiding Philosophy: "Convention over Configuration"**

The primary goal is to create a tool that is immediately useful with zero configuration but allows for complete control when needed. The generator must be able to parse a directory of SQL DDL files and produce fully functional C# entity and data access classes based on sensible conventions. The `generator.config.json` file is to be treated as an **override and enhancement layer**, not a required definition file. We are explicitly avoiding the pitfalls of a previous system where C# code generation was tightly coupled to physical database structures like indexes.

**1.2. The Core Architecture: A Multi-Phase Pipeline**

The generator's internal logic must be structured as a distinct, sequential, and testable pipeline. This separation of concerns is critical for maintainability.

1. **Phase 1: Ingestion & Raw Model Creation:**
* **Action:** Parse all `.sql` files.
* **Output:** An in-memory object graph representing the "raw" database schema. This includes tables, views, columns with their inferred SQL types and nullability, primary key constraints, and all indexes.

2. **Phase 2: Schema Model Refinement:**
* **Action:** Load `generator.config.json`. Scan for `columnOverrides` that specify `sqlType` or `isNullable`.
* **Purpose:** This phase specifically exists to **solve the "SQL View Problem."** Where the parser cannot determine a view column's type or nullability, these config values are used to "patch" and complete the in-memory schema model.
* **Output:** A complete and accurate in-memory representation of the database schema, ready for transformation.

3. **Phase 3: C# Model Transformation:**
* **Action:** Determine the final C# type for every single column. This is where the complex type mapping logic lives.
* **Process:** Apply C# type rules in a strict order of precedence (detailed in Part 3).
* **Output:** A final, "C#-ready" model containing all information needed for code generation (final class names, property names, C# types, method definitions, etc.).

4. **Phase 4: Code Generation:**
* **Action:** Iterate through the final C#-ready model.
* **Logic:** This phase should be as "dumb" as possible. It simply translates the rich model from Phase 3 into C# code strings and writes them to `.cs` files. All complex decisions have already been made.

#### **Part 2: Baseline Functionality (Convention-Based Generation)**

This section defines the generator's behavior when **no `generator.config.json` file is present.**

* **Entity Class Generation:**
* A table `dbo.Products` generates a class named `Products`.
* Each column becomes a public property (e.g., `ProductName` -> `public string ProductName { get; set; }`).
* SQL types are mapped to default C# types (`int` -> `int`, `nvarchar` -> `string`, `bit` -> `bool`, `datetime2` -> `DateTime`).
* Nullability is respected (`int NULL` -> `int?`, `varchar(50) NOT NULL` -> `string`).

* **Data Access Method Generation:**
* **Read (by PK):** A method to fetch a single record is created using the columns from the `PRIMARY KEY` constraint as parameters.
* **Read (by Index):** For every other `INDEX` on the table, a method to fetch a list of records is created using the indexed columns as parameters. This ensures all indexed lookups are supported by default.
* **Update:** An `Update` method is created. Its `SET` clause includes all columns **except** the members of the primary key.
* **Create/Delete:** Standard `Create` and `Delete` methods are generated, with the `Delete` method using the primary key for record identification.

#### **Part 3: Advanced Functionality (Configuration-Driven Overrides)**

This section details the features enabled by the `generator.config.json` file, following the schema we have defined.

**3.1. Global Type Mappings (`globalTypeMappings`)**

* **Purpose:** To define broad, reusable rules for mapping columns to custom or specific C# types. This prevents repeating the same logic for many tables.
* **Implementation:**
* Each entry is an object with `match` and `apply` properties.
* `match`: Defines conditions. It can contain `columnNameRegex`, `tableNameRegex`, `schemaNameRegex`, and `sqlType`. *All* conditions must be met for a rule to match.
* `apply`: Defines the outcome, primarily setting the `csharpType`.
* **`priority` - The Rule for Conflict Resolution:** This is critical. If a column matches multiple global rules, the rule with the **highest integer `priority` wins**. This allows for creating broad, low-priority default rules (e.g., "any column ending in 'Amount' is a `Money` type") and then overriding them with specific, high-priority exception rules (e.g., "any column of SQL type `int` is a C# `int`, even if its name ends in 'Amount'").

**3.2. Table-Specific Overrides (`tables`)**

* **Purpose:** To provide fine-grained control for a specific table or view. The key is the schema-qualified name (e.g., `"dbo.PurchaseOrder"`).
* **Features:**
* **`csharpClassName`**: Overrides the generated C# class name (e.g., `dbo.PurchaseOrder` -> `Order`).
* **`primaryKeyOverride`**: An array of column names to be treated as the primary key. **This decouples the generator's logic from the physical DDL.** It directly dictates the parameters for the `Read(key)`, `Update`, and `Delete` methods.
* **`updateConfig.ignoreColumns`**: An array of column names to exclude from the `UPDATE` statement's `SET` clause. This is for capturing business logic, such as ensuring columns like `CreatedOn` or `TenantId` are immutable.
* **`readMethods`**: An array for defining custom data access methods. This is a key feature for **separating the application's API from the database's physical structure.** A developer can define a `GetByStatusAndDate` method here without needing a corresponding index to exist in the database.

**3.3. Column-Specific Overrides (`columnOverrides`)**

* **Purpose:** To target a single column within a specific table. This is the highest level of precedence and serves two distinct purposes based on the generator phase.
* **Features for Phase 2 (Schema Refinement):**
* **`sqlType`**: Provides the SQL data type (e.g., `"nvarchar(100)"`) when the parser cannot infer it, primarily for view columns.
* **`isNullable`**: A boolean to explicitly set nullability when it cannot be inferred from a view.
* **Feature for Phase 3 (C# Model Transformation):**
* **`csharpType`**: Defines the final C# type for the property. This is the **ultimate override**. If this value is present, it is used unconditionally, ignoring any global mapping rules or default conventions for that specific column.

#### **Part 4: The Role of `generator.schema.json`**

* **Purpose:** This file is **not used by the generator at runtime**. Its purpose is purely for the **developer experience**.
* **Benefits:**
1. **Validation:** It prevents typos and structural errors in the `generator.config.json` file.
2. **IntelliSense:** When referenced via the `$schema` key, IDEs like VS Code will provide autocompletion for property names and values.
3. **Self-Documentation:** It serves as the formal contract for what constitutes a valid configuration file.


schemas/sql-config.schema.json
Loading