Skip to content

F# Type Provider for LQL with Compile-Time Validation#15

Merged
MelbourneDeveloper merged 12 commits intomainfrom
TypeProviderF#
Jan 5, 2026
Merged

F# Type Provider for LQL with Compile-Time Validation#15
MelbourneDeveloper merged 12 commits intomainfrom
TypeProviderF#

Conversation

@MelbourneDeveloper
Copy link
Copy Markdown
Collaborator

F# Type Provider for LQL with Compile-Time Validation

TLDR;

Adds an F# type provider that validates LQL queries at compile time, converting them to SQL. Invalid LQL syntax causes compilation errors rather than runtime failures.

Brief Details

  • LqlTypeProvider: New F# type provider (Lql.LqlCommand<"query">) that parses LQL at compile time, validates syntax, and generates SQL
  • Compile-time errors: Invalid LQL queries fail the build with descriptive error messages including line/column positions
  • Generated properties: Each validated query type exposes Query (original LQL) and Sql (generated SQL) static properties
  • E2E test suite: 30+ tests covering selects, filters, joins, aggregations, ordering, limits, and arithmetic expressions
  • Test data seeder: C# helper using DataProvider-generated extensions to seed test databases (no raw SQL inserts)
  • CI integration: New GitHub Actions job lql-fsharp-typeprovider-tests runs F# type provider tests on LQL changes
  • F# example project: Demo app showing type provider usage with .lql query files

How Do The Tests Prove This Works?

  • TypeProviderCompileTimeValidationTests: Verify Query and Sql properties are generated correctly
  • TypeProviderFilterTests: Confirm filter expressions generate proper WHERE clauses with AND/OR
  • TypeProviderJoinTests: Validate JOIN, LEFT JOIN, and multi-table joins produce correct SQL
  • TypeProviderAggregationTests: Test GROUP BY, SUM, AVG, COUNT, and HAVING clauses
  • TypeProviderE2EExecutionTests: Execute generated SQL against real SQLite database, verify row counts and data
  • TypeProviderRealWorldScenarioTests: End-to-end scenarios querying customers, filtering users, joining orders

@MelbourneDeveloper MelbourneDeveloper enabled auto-merge (squash) January 4, 2026 08:52
@MelbourneDeveloper MelbourneDeveloper merged commit b3a7fdd into main Jan 5, 2026
18 checks passed
@MelbourneDeveloper MelbourneDeveloper deleted the TypeProviderF# branch January 5, 2026 07:16
MelbourneDeveloper added a commit that referenced this pull request Apr 7, 2026
#12 op method names use the literal table.Name (e.g.
   'Insertgk_userAsync') instead of PascalCasing it. The SQLite.Cli
   emits the literal name and consumer call sites depend on it.

#13 query method parameters use the SQL @param name verbatim
   ('resource_id') instead of camelCasing it. Consumer call sites
   that use named arguments (CheckResourceGrantAsync(resource_id: ...))
   broke when the codegen renamed them.

#14 byte[] (Postgres bytea) columns are now always emitted as
   'byte[]?' even when the schema marks the column NOT NULL.
   Postgres bytea metadata is unreliable about nullability and the
   reader can return null at runtime, causing CS8604 warnings on
   non-nullable byte[] receivers.

#15 query record types are non-positional now:

       public record Foo {
           public string id { get; init; }
           public string? name { get; init; }
       }

   instead of the positional 'public sealed record Foo(string id, ...)'.
   Per-property nullability comes from the column metadata. The
   ReadFoo() reader uses object-initializer syntax 'new() { ... }'
   to match the new shape. Restores parity with the old SQLite.Cli
   output.

Bumps Postgres.Cli to 0.2.4-beta.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant