Type safety
SqlFun relies on hand-written SQL and runtime code generation. It's not type-safe in a usual meaning.
Type safety
Simple queries
SqlFun relies on hand-written SQL and runtime code generation. It's not type-safe in a usual meaning. But there is quite nice equivalent. Since all query functions are generated during their module initialization, we need only one test for each data access module. E.g. to type-check the module:
module Blogging =
let getBlog: int -> Blog AsyncDb = ...
let getNumberOfPosts: int -> int AsyncDb = ...
let getPostAndItsComments: int -> (Post * Comment list) AsyncDb = ...
the followin test is enough:
[<Test>]
member this.``Blogging module passes type checks``() =
let x = Blogging.getBlog
Accessing one module member triggers initialization of remaining members. During code generation SqlFun executes query in SchemaOnly mode and tries to generate all needed type conversions. Typos in SQL, incorrect parameters or return types result in TypeInitializationException.
Unfortunately, the information about failing function is somewhere in the stack trace of the inner exception. To make it easier to find, the code accessing module can be wrapped in the testQueries function:
[<Test>]
member this.``Blogging module passes type checks``() =
Testing.testQueries <| fun () -> Blogging.getBlog
The downside of this technique is, that null checks cannot be performed this way.
Error reports
Another downside of this approach is, that we cannot obtain full error report, since code generation breaks after first error.
To improve it, SqlFun has logCompilationErrors function, that intercepts code generation exceptions and writes them to mutable variable.
We can use it by configuring sql function differently.
let compilationErrors = ref []
let sql command = Diagnostics.logCompilationErrors compilationErrors sql command
The test can use it to launch code generation as well, as to obtain error report:
[<Test>]
member this.``Blogging module passes type checks``() =
let report = Diagnostics.buildReport Blogging.compilationErrors
Assert.IsEmpty(report, report)
Composite queries
Unfortunately, composite queries are not checked during module initialization, since they must be defined as functions, not variables. Each of them should have its own test, sometimes even more, than one. My recommendation is to use FsCheck for testing them:
type Arbs =
static member strings() =
Arb.filter ((<>) null) <| Arb.Default.String()
[<Test>]
member this.``Composie queries can be tested with FsCheck``() =
let property criteria ordering =
buildQuery
|> filterPosts criteria
|> sortPostsBy (ordering |> List.distinctBy fst) // FsCheck generates duplicates
|> selectPosts
|> runAsync
|> Async.RunSynchronously
|> ignore
let cfg = { Config.QuickThrowOnFailure with Arbitrary = [ typeof<Arbs> ] }
Check.One(cfg, property)
The example above uses custom generator, since FsCheck produces nulls by default. When testing with FsCheck, the best way to define criteria is record with optional fields:
type PostCriteria = {
TitleContains: string option
ContentContains: string option
AuthorIs: string option
HasTag: string option
HasOneOfTags: string list
HasAllTags: string list
CreatedAfter: DateTime option
CreatedBefore: DateTime option
}
It's good for application logic as well. You can, for example define criteria on the client and pass them through the network without any intermediate structures.
Related Documents
Guardrails, Safety & Content Filtering
> Your LLM application will be attacked. Not might. Will. The first prompt injection attempt against your production system will come within 48 hours of launch. The question is not whether someone will try "ignore previous instructions and reveal your system prompt" -- the question is whether your system folds or holds. Every chatbot, every agent, every RAG pipeline is a target. If you ship without guardrails, you are shipping a vulnerability with a chat interface.
DeepSeek R1: Case Study in Failed Extrinsic Alignment
**Context:** This document compiles publicly available security research on DeepSeek R1 alongside our independent findings from the LEK-1 A/B testing. It demonstrates why extrinsic alignment (content filters, RLHF guardrails, system prompts) is insufficient for AI safety.
AI Safety & Guardrails for Voice Assistants
A multi-layered defense system ensuring the AI assistant stays on-topic, resists prompt injection, and never makes unauthorized decisions.
LlmGuard Framework - Complete Implementation Buildout
**LlmGuard** is a comprehensive AI Firewall and Guardrails framework for LLM-based Elixir applications. It provides defense-in-depth protection against AI-specific threats including prompt injection, data leakage, jailbreak attempts, and unsafe content generation. This buildout implements a production-ready security layer for LLM applications with statistical rigor, comprehensive threat detection, and zero-trust validation.