Skip to content

Commit e2e18a6

Browse files
Fixes
1 parent c2c73de commit e2e18a6

File tree

20 files changed

+496
-23
lines changed

20 files changed

+496
-23
lines changed

Agents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
## Coding Rules
1010

11-
- **NEVER THROW** - Return `Result<T>`. Wrap failures in try/catch
11+
- **NEVER THROW** - Return `Result<T,E>`. Wrap failures in try/catch
1212
- **No casting/!** - Pattern match on type only
1313
- **NO GIT** - Source control is illegal
1414
- **No suppressing warnings** - Illegal

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
## Coding Rules
1010

11-
- **NEVER THROW** - Return `Result<T>`. Wrap failures in try/catch
11+
- **NEVER THROW** - Return `Result<T,E>`. Wrap failures in try/catch
1212
- **No casting/!** - Pattern match on type only
1313
- **NO GIT** - Source control is illegal
1414
- **No suppressing warnings** - Illegal

DataProvider/DataProvider/DataProviderConfig.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ namespace DataProvider;
33
/// <summary>
44
/// Configuration for DataProvider code generation
55
/// </summary>
6+
/// <example>
7+
/// <code>
8+
/// // Configure tables for code generation
9+
/// var config = new DataProviderConfig
10+
/// {
11+
/// ConnectionString = "Data Source=app.db",
12+
/// Tables = new List&lt;TableConfig&gt;
13+
/// {
14+
/// new TableConfig { Schema = "main", Name = "users", GenerateDelete = true },
15+
/// new TableConfig { Schema = "main", Name = "orders" }
16+
/// }.AsReadOnly()
17+
/// };
18+
/// </code>
19+
/// </example>
620
public sealed record DataProviderConfig
721
{
822
/// <summary>
@@ -19,6 +33,21 @@ public sealed record DataProviderConfig
1933
/// <summary>
2034
/// Configuration for a single table
2135
/// </summary>
36+
/// <example>
37+
/// <code>
38+
/// // Configure a table with custom settings
39+
/// var tableConfig = new TableConfig
40+
/// {
41+
/// Schema = "dbo",
42+
/// Name = "Patients",
43+
/// GenerateInsert = true,
44+
/// GenerateUpdate = true,
45+
/// GenerateDelete = false,
46+
/// ExcludeColumns = new List&lt;string&gt; { "computed_column" }.AsReadOnly(),
47+
/// PrimaryKeyColumns = new List&lt;string&gt; { "Id" }.AsReadOnly()
48+
/// };
49+
/// </code>
50+
/// </example>
2251
public sealed record TableConfig
2352
{
2453
/// <summary>

DataProvider/DataProvider/DbConnectionExtensions.cs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,52 @@
55
namespace DataProvider;
66

77
/// <summary>
8-
/// Static extension methods for IDbConnection following FP patterns
8+
/// Static extension methods for IDbConnection following FP patterns.
9+
/// All methods return Result types for explicit error handling.
910
/// </summary>
11+
/// <example>
12+
/// <code>
13+
/// using var connection = new SqliteConnection("Data Source=:memory:");
14+
/// connection.Open();
15+
///
16+
/// // Execute a query with mapping
17+
/// var result = connection.Query&lt;Customer&gt;(
18+
/// sql: "SELECT Id, Name FROM Customers WHERE Active = 1",
19+
/// mapper: reader => new Customer(
20+
/// Id: reader.GetInt32(0),
21+
/// Name: reader.GetString(1)
22+
/// )
23+
/// );
24+
///
25+
/// // Pattern match on the result
26+
/// var customers = result switch
27+
/// {
28+
/// Result&lt;IReadOnlyList&lt;Customer&gt;, SqlError&gt;.Ok&lt;IReadOnlyList&lt;Customer&gt;, SqlError&gt; ok => ok.Value,
29+
/// Result&lt;IReadOnlyList&lt;Customer&gt;, SqlError&gt;.Error&lt;IReadOnlyList&lt;Customer&gt;, SqlError&gt; err => throw new Exception(err.Value.Message),
30+
/// _ => throw new InvalidOperationException()
31+
/// };
32+
/// </code>
33+
/// </example>
1034
public static class DbConnectionExtensions
1135
{
1236
/// <summary>
13-
/// Execute a query and return results
37+
/// Execute a query and return results.
1438
/// </summary>
1539
/// <typeparam name="T">The result type</typeparam>
1640
/// <param name="connection">The database connection</param>
1741
/// <param name="sql">The SQL query</param>
1842
/// <param name="parameters">Optional parameters</param>
1943
/// <param name="mapper">Function to map from IDataReader to T</param>
2044
/// <returns>Result with list of T or error</returns>
45+
/// <example>
46+
/// <code>
47+
/// var result = connection.Query&lt;Product&gt;(
48+
/// sql: "SELECT * FROM Products WHERE Price > @minPrice",
49+
/// parameters: [new SqliteParameter("@minPrice", 10.00)],
50+
/// mapper: r => new Product(r.GetInt32(0), r.GetString(1), r.GetDecimal(2))
51+
/// );
52+
/// </code>
53+
/// </example>
2154
public static Result<IReadOnlyList<T>, SqlError> Query<T>(
2255
this IDbConnection connection,
2356
string sql,
@@ -72,12 +105,26 @@ public static Result<IReadOnlyList<T>, SqlError> Query<T>(
72105
}
73106

74107
/// <summary>
75-
/// Execute a non-query command
108+
/// Execute a non-query command (INSERT, UPDATE, DELETE).
76109
/// </summary>
77110
/// <param name="connection">The database connection</param>
78111
/// <param name="sql">The SQL command</param>
79112
/// <param name="parameters">Optional parameters</param>
80113
/// <returns>Result with rows affected or error</returns>
114+
/// <example>
115+
/// <code>
116+
/// var result = connection.Execute(
117+
/// sql: "UPDATE Products SET Price = @price WHERE Id = @id",
118+
/// parameters: [
119+
/// new SqliteParameter("@price", 19.99),
120+
/// new SqliteParameter("@id", 42)
121+
/// ]
122+
/// );
123+
///
124+
/// if (result is Result&lt;int, SqlError&gt;.Ok&lt;int, SqlError&gt; ok)
125+
/// Console.WriteLine($"Updated {ok.Value} rows");
126+
/// </code>
127+
/// </example>
81128
public static Result<int, SqlError> Execute(
82129
this IDbConnection connection,
83130
string sql,

DataProvider/DataProvider/DbTransact.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ namespace DataProvider;
77
/// Provides transactional helpers as extension methods for <see cref="DbConnection"/>.
88
/// Opens a transaction, executes the provided delegate, and commits or rolls back accordingly.
99
/// </summary>
10+
/// <example>
11+
/// <code>
12+
/// // Execute multiple operations in a transaction
13+
/// await connection.Transact(async tx =>
14+
/// {
15+
/// await tx.InsertUserAsync(new User { Id = Guid.NewGuid(), Name = "John" });
16+
/// await tx.InsertOrderAsync(new Order { UserId = userId, Total = 99.99m });
17+
/// });
18+
///
19+
/// // Execute with a return value
20+
/// var userId = await connection.Transact(async tx =>
21+
/// {
22+
/// var user = new User { Id = Guid.NewGuid(), Name = "Jane" };
23+
/// await tx.InsertUserAsync(user);
24+
/// return user.Id;
25+
/// });
26+
/// </code>
27+
/// </example>
1028
public static class DbTransact
1129
{
1230
/// <summary>

DataProvider/DataProvider/SchemaTypes.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ namespace DataProvider;
66
/// <summary>
77
/// Represents a database column with its metadata
88
/// </summary>
9+
/// <example>
10+
/// <code>
11+
/// // Create a column definition
12+
/// var column = new DatabaseColumn
13+
/// {
14+
/// Name = "Id",
15+
/// SqlType = "uniqueidentifier",
16+
/// CSharpType = "Guid",
17+
/// IsNullable = false,
18+
/// IsPrimaryKey = true,
19+
/// IsIdentity = false,
20+
/// IsComputed = false
21+
/// };
22+
///
23+
/// // Create a string column with max length
24+
/// var nameColumn = new DatabaseColumn
25+
/// {
26+
/// Name = "Name",
27+
/// SqlType = "nvarchar(100)",
28+
/// CSharpType = "string",
29+
/// IsNullable = false,
30+
/// MaxLength = 100
31+
/// };
32+
/// </code>
33+
/// </example>
934
public sealed record DatabaseColumn
1035
{
1136
/// <summary>
@@ -62,6 +87,27 @@ public sealed record DatabaseColumn
6287
/// <summary>
6388
/// Represents a database table with its columns
6489
/// </summary>
90+
/// <example>
91+
/// <code>
92+
/// // Create a table definition with columns
93+
/// var table = new DatabaseTable
94+
/// {
95+
/// Schema = "dbo",
96+
/// Name = "Patients",
97+
/// Columns = new List&lt;DatabaseColumn&gt;
98+
/// {
99+
/// new DatabaseColumn { Name = "Id", SqlType = "uniqueidentifier", IsPrimaryKey = true },
100+
/// new DatabaseColumn { Name = "Name", SqlType = "nvarchar(100)" },
101+
/// new DatabaseColumn { Name = "DateOfBirth", SqlType = "date", IsNullable = true }
102+
/// }.AsReadOnly()
103+
/// };
104+
///
105+
/// // Access computed column lists
106+
/// var pkColumns = table.PrimaryKeyColumns;
107+
/// var insertColumns = table.InsertableColumns;
108+
/// var updateColumns = table.UpdateableColumns;
109+
/// </code>
110+
/// </example>
65111
public sealed record DatabaseTable
66112
{
67113
/// <summary>

DataProvider/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ A .NET source generator that creates compile-time safe database extension method
77
- **Compile-Time Safety** - SQL queries are validated during compilation, catching errors before runtime
88
- **Auto-Generated Extensions** - Creates extension methods on `IDbConnection` and `IDbTransaction`
99
- **Schema Inspection** - Automatically inspects database schema to generate appropriate types
10-
- **Result Type Pattern** - All operations return `Result<T>` types for explicit error handling
10+
- **Result Type Pattern** - All operations return `Result<T,E>` types for explicit error handling
1111
- **Multi-Database Support** - Currently supports SQLite and SQL Server
1212
- **LQL Integration** - Seamlessly works with Lambda Query Language files
1313

@@ -244,7 +244,7 @@ For complex result sets with joins, configure grouping in a `.grouping.json` fil
244244
DataProvider follows functional programming principles:
245245

246246
- **No Classes** - Uses records and static extension methods
247-
- **No Exceptions** - Returns `Result<T>` types for all operations
247+
- **No Exceptions** - Returns `Result<T,E>` types for all operations
248248
- **Pure Functions** - Static methods with no side effects
249249
- **Expression-Based** - Prefers expressions over statements
250250

@@ -277,7 +277,7 @@ dotnet test DataProvider.Tests/DataProvider.Tests.csproj
277277

278278
## Error Handling
279279

280-
All methods return `Result<T>` types:
280+
All methods return `Result<T,E>` types:
281281

282282
```csharp
283283
var result = await connection.ExecuteQueryAsync();

Migration/Migration/SchemaBuilder.cs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,29 @@ namespace Migration;
33
/// <summary>
44
/// Fluent builder for schema definitions.
55
/// </summary>
6+
/// <example>
7+
/// <code>
8+
/// var schema = SchemaFactory.Define("MyDatabase")
9+
/// .Table("users", t => t
10+
/// .Column("id", PortableTypes.Uuid, c => c.PrimaryKey())
11+
/// .Column("email", PortableTypes.VarChar(255), c => c.NotNull())
12+
/// .Column("created_at", PortableTypes.DateTime, c => c.DefaultLql("now()"))
13+
/// .Index("idx_users_email", "email", unique: true))
14+
/// .Table("orders", t => t
15+
/// .Column("id", PortableTypes.Int, c => c.Identity().PrimaryKey())
16+
/// .Column("user_id", PortableTypes.Uuid, c => c.NotNull())
17+
/// .Column("total", PortableTypes.Decimal(10, 2))
18+
/// .ForeignKey("user_id", "users", "id", onDelete: ForeignKeyAction.Cascade))
19+
/// .Build();
20+
/// </code>
21+
/// </example>
622
public static class SchemaFactory
723
{
824
/// <summary>
925
/// Start defining a schema with the given name.
1026
/// </summary>
27+
/// <param name="name">The name of the schema/database.</param>
28+
/// <returns>A <see cref="SchemaBuilder"/> for fluent configuration.</returns>
1129
public static SchemaBuilder Define(string name) => new(name);
1230
}
1331

@@ -28,8 +46,20 @@ public static class Schema
2846
}
2947

3048
/// <summary>
31-
/// Builder for creating schema definitions.
49+
/// Builder for creating schema definitions with tables, columns, indexes, and constraints.
3250
/// </summary>
51+
/// <example>
52+
/// <code>
53+
/// var schema = Schema.Define("inventory")
54+
/// .Table("products", t => t
55+
/// .Column("id", PortableTypes.Int, c => c.Identity().PrimaryKey())
56+
/// .Column("name", PortableTypes.VarChar(100), c => c.NotNull())
57+
/// .Column("price", PortableTypes.Decimal(10, 2), c => c.NotNull().Check("price >= 0"))
58+
/// .Column("sku", PortableTypes.VarChar(50), c => c.NotNull())
59+
/// .Unique("uq_products_sku", "sku"))
60+
/// .Build();
61+
/// </code>
62+
/// </example>
3363
public sealed class SchemaBuilder
3464
{
3565
private readonly string _name;
@@ -66,8 +96,23 @@ public SchemaBuilder Table(string schema, string name, Action<TableBuilder> conf
6696
}
6797

6898
/// <summary>
69-
/// Builder for creating table definitions.
99+
/// Builder for creating table definitions with columns, indexes, foreign keys, and constraints.
70100
/// </summary>
101+
/// <example>
102+
/// <code>
103+
/// // Define a table with various column types and constraints
104+
/// .Table("employees", t => t
105+
/// .Column("id", PortableTypes.Uuid, c => c.PrimaryKey().DefaultLql("gen_uuid()"))
106+
/// .Column("name", PortableTypes.VarChar(100), c => c.NotNull())
107+
/// .Column("email", PortableTypes.VarChar(255), c => c.NotNull())
108+
/// .Column("department_id", PortableTypes.Int)
109+
/// .Column("salary", PortableTypes.Decimal(12, 2))
110+
/// .Column("hired_at", PortableTypes.DateTime, c => c.DefaultLql("now()"))
111+
/// .Index("idx_employees_email", "email", unique: true)
112+
/// .Index("idx_employees_dept", "department_id")
113+
/// .ForeignKey("department_id", "departments", "id"))
114+
/// </code>
115+
/// </example>
71116
public sealed class TableBuilder
72117
{
73118
private readonly string _name;
@@ -279,8 +324,26 @@ internal TableDefinition Build() =>
279324
}
280325

281326
/// <summary>
282-
/// Builder for creating column definitions.
327+
/// Builder for creating column definitions with type, nullability, defaults, and constraints.
283328
/// </summary>
329+
/// <example>
330+
/// <code>
331+
/// // UUID primary key with auto-generation
332+
/// .Column("id", PortableTypes.Uuid, c => c.PrimaryKey().DefaultLql("gen_uuid()"))
333+
///
334+
/// // Required string with max length
335+
/// .Column("name", PortableTypes.VarChar(100), c => c.NotNull())
336+
///
337+
/// // Auto-increment integer
338+
/// .Column("sequence", PortableTypes.Int, c => c.Identity())
339+
///
340+
/// // Decimal with precision and check constraint
341+
/// .Column("price", PortableTypes.Decimal(10, 2), c => c.NotNull().Check("price > 0"))
342+
///
343+
/// // DateTime with default to current timestamp
344+
/// .Column("created_at", PortableTypes.DateTime, c => c.DefaultLql("now()"))
345+
/// </code>
346+
/// </example>
284347
public sealed class ColumnBuilder
285348
{
286349
private readonly string _name;

Migration/Migration/SchemaDiff.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@ namespace Migration;
44
/// Calculates the difference between two schema definitions.
55
/// Produces a list of operations to transform current schema into desired schema.
66
/// </summary>
7+
/// <example>
8+
/// <code>
9+
/// // Compare current database schema against desired schema
10+
/// var currentSchema = await schemaInspector.InspectAsync(connection);
11+
/// var desiredSchema = Schema.Define("mydb")
12+
/// .Table("users", t => t
13+
/// .Column("id", PortableTypes.Uuid, c => c.PrimaryKey())
14+
/// .Column("email", PortableTypes.VarChar(255), c => c.NotNull())
15+
/// .Column("name", PortableTypes.VarChar(100))) // New column
16+
/// .Build();
17+
///
18+
/// // Calculate safe (additive-only) migration operations
19+
/// var result = SchemaDiff.Calculate(currentSchema, desiredSchema);
20+
/// if (result is OperationsResult.Ok&lt;IReadOnlyList&lt;SchemaOperation&gt;, MigrationError&gt; ok)
21+
/// {
22+
/// foreach (var op in ok.Value)
23+
/// {
24+
/// var ddl = ddlGenerator.Generate(op);
25+
/// await connection.ExecuteAsync(ddl);
26+
/// }
27+
/// }
28+
///
29+
/// // Or allow destructive changes (DROP operations)
30+
/// var destructiveResult = SchemaDiff.Calculate(
31+
/// currentSchema, desiredSchema, allowDestructive: true);
32+
/// </code>
33+
/// </example>
734
public static class SchemaDiff
835
{
936
/// <summary>

0 commit comments

Comments
 (0)