HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
fsprojects
Rezoom.SQL
F#2017–2026
fsprojects / Rezoom.SQL

Rezoom.SQL

Statically typechecks a common SQL dialect and translates it to various RDBMS backends

12Distinct people
19Mentions
0Explicit recommenders
681GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

Statically typechecks a common SQL dialect and translates it to various RDBMS backends

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 6:01:08 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year19 dated mentions

Observed mentions: 2017-07-30 18:31:34 UTC to 2026-10-19 19:10:32 UTC.

0123452017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 5 mentions; 2 submissions, 3 comments20172018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 4 mentions; 0 submissions, 4 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments20212022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 1 mentions; 1 submissions, 0 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 5 mentions; 0 submissions, 5 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC523
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC101
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC303
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC404
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC000
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC110
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC000
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC000
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC000
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC505

What Hacker News says

5 sources
I get what you are saying. If you write a "recognizer" with parser combinators (like a regex without capture groups) and then want to convert that recognizer to an actual AST parser, you have to change it more deeply than just choosing what pieces to capture. You have to carefully follow a trail of left/right capture/discards to figure out what actually ends up in a function input. It's tree-shaped not linear-list-of-captures. Some parser combinator environments are better than others. Haskell's applicative style is pretty good: (\x y z -> ...) <$> parser1 <*> parser2 <*> parser3 F#'s FParsec has pairwise operators `.>>`, `>>.`, and `.>>.` for capture-left-ignore-right, capture-right-ignore-left, and capture-both-and-tuple. These rapidly get unwieldy: parser1 .>>. parser2 .>>. parser3 |>> (fun ((x, y), z) -> ...) // ugly nested t
Read the rest
uples! So they have functions like `pipe3` instead: pipe3 parser1 parser2 parser3 (fun x y z -> ...) Still, what a pain when you mix in some "ignored" parts in between the "captured" parts. Ugly and awkward to follow. pipe3 (whitespace >>. parser1) (char '(' >>. parser2 .>> char ')') (whitespace >>. parser3 .>> whitespace) (fun x y z -> ...) I wrote a library some years ago to alleviate my issues with that F# flavor of this problem. My solution is very similar to yours in that it also asks the user to think of it as like regex "capture groups"! https://rspeele.github.io/FParsec-Pipes/Intro.html My approach is more symbolic-operator flavored than yours though. In mine you always start a chain of parsers with `%%`, separate with `--` and end with `-|> function`. %% parser1 -- parser2 -- parser3 -|> fun () -> ... If you want to capture any part of the chain you use the unary prefix operator `+.` to mark it captured and it will get passed through to the consuming function. %% +.parser1 -- parser2 -- +.parser3 -|> fun p1 p3 -> ... I used it extensively in a SQL parser: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez...
In Parser Combinators
I'd love to take a principled stance on this because I do consider code an art or at least a craft and I appreciate a program written in a unique and clever way. In retrospect, SO MANY of my personal projects[0][1][2][3] over the years were DSLs that affected how you write something and how pretty or type-safe the code ends up being. They are libraries that can only be appreciated by another programmer, they mean nothing to an end user. It appears that whole genre of library will mean nothing to anybody in a few years. Shoulder of Orion, Tannhauser Gate, etc. etc. [0] https://github.com/rspeele/infix.el [1] https://github.com/rspeele/LicenseToCIL [2] https://github.com/fsprojects/Rezoom.SQL [3] https://rspeele.github.io/FParsec-Pipes/Intro.html Yet, it's utterly hopeless to try to take the same stance visual artists are nobly holding. Of
Read the rest
the programs I use daily, how many have I ever even glanced at the source code? I could count them on my fingers. And I'm in the target audience: a programmer, enthusiastic about the trade, one who has contributed to open source... Conclusion: within a rounding error of NOBODY cares about the code. Of the weavers who were displaced by the automated looms, or the furniture-makers displaced by mass produced pressboard, some of the most skilled could still ply their trade for the rich who wanted to flex their bespoke handmade goods. Programmers writing code by hand will not even have that luxury market. It makes me depressed but I don't see any way around it. My hope is that I can use my knowledge, willingness to spend long hours fighting with a computer, and industry experience to at least make better software with agents than the average vibecoder. I sadly don't think I'll get anywhere trying to play John Henry vs. the Steam Drill, trying to beat it with my hand-typed code. Maybe somebody who's 10x the dev I am can do that, and Godspeed to them, but I thought I was pretty decent and I don't have a chance. Hell, it's not even possible to prove any code was written by hand, other than by having uploaded it pre-2024ish. For this reason I really feel bad for those coming up right now, who have the same enthusiasm and fascination for Real Programming that I did, but no way to prove themselves and no motivation to in a world that values that skill less all the time.
In Programming Isn't Special
It is just a coding mistake, except that fixing that mistake leaves you with clunkier abstractions. If you have Foos, and users have permissions that control what they can do to a Foo, you'd like to have a function `GetPermissions : (UserId, FooId) -> Async<Permissions>`. If users can frob Foos you'd like to have a `FrobFoo : (FooId) -> Async<void>` function. But as soon as you let users select multiple Foos, or god forbid, an entire folder containing Foos, and bulk-frob them now you have to write `FrobFoos : (List<FooId>) -> Async<void>`. And to avoid the implementation of that causing another 1+N checking permissions, you also need `GetPermissionsBulk : (UserId, List<FooId> -> Async<Dictionary<FooId, Permissions>>`. The singular forms of those functions, to avoid duplication, now become wrappers over the bulk forms. The logic becomes ha
Read the rest
rder to trace in the rewritten, bulk forms of the functions, but they are efficient. Next the customer hits you with a request like "let's have a smart-frob function that works on all the selected foos. For foos that are red, it frobs them, if they are blue, it fizzles them". Now you have to bulk-load to select the redness or blueness of all your Foos, build two separate lists, red and blue, then call your bulk-frob and bulk-fizzle functions accordingly on the two lists. Again the machinery to turn the requirement into a batch-shaped thing is not a lot, but it does kind of obscure the original business requirement. At various times in the life of the project you will have a feature that starts as a "always done on one Foo" thing because it's triggered by a button on the detail screen. Then somebody will possibly come along and want to do it in bulk later and you have to rewrite the implementation. Unless you have very strict code review that everything MUST be written in batch-style taking a list of IDs up to the API layer. I wrote a library[1] many years ago to solve this problem and allow the straightforward, non-batch versions of the functions to be automatically batchable. The idea is kind of like what React did for frontend dev: React was not faster than mutating the page with jQuery soup, but it was much faster than replacing the entire DOM on every render, and it let you write your code as if that was what you were doing. That was a very simple mental model and much less buggy than jQuery soup. The idea of my library was basically borrowed from other functional languages with a resumption monad, meaning that instead of an opaque async task to go do a thing, you have a "plan" which could either be a. done or b. waiting on some errand that requires firing off a query. If you have a list of plans like from a loop, you could step all of them to the next errand they are waiting on, then fire those off in a batch. So plans could be composed linearly or "batch-style" depending on your preference[2]. What makes it very powerful is the combination with an F# type provider that could analyze your SQL and automatically determine a caching profile for each query. It knows what tables the query reads from, what tables it writes to, whether it uses any impure functions like random(), etc. So within one transaction, it wouldn't re-run the same pure query again, it would pull the results from a local cache -- except if another command issued in that transaction updates those tables, the cache is automatically invalidated. This solves the other code smell that starts to accumulate as you try to write efficient database code in a complex app -- keeping materialized objects loaded in memory and passing them around to other functions so they don't have to re-query for them. Anyway, it was a little too weird to catch on, and I was a little too burnt out to maintain it. [1]https://github.com/fsprojects/Rezoom.SQL [2]https://fsprojects.github.io/Rezoom.SQL/doc/Rezoom/README.ht...
In Solving the 1+N Query Problem
You take advantage of the property that verifying / sanity-checking an answer is usually much easier and faster than figuring out the answer in the first place. It's similar to how Google has been useful for the past few decades despite the internet being full of junk info. Except Google could only provide semi-reliable information about stuff that's public knowledge with lots of people talking about it, whereas Claude can provide semi-reliable information about your own code that nobody else has ever laid eyes on. A few months ago I used this to re-acquaint myself with code I myself had written back in 2017-2018[1]. I wanted to add some new features, and I did NOT want AI to write them, because the existing codebase was my own sweat and tears and running it through the slop extruder would cheapen that original work. I wanted to write all
Read the rest
the code myself with full understanding of what was going on. Now, it had been many years and the codebase did some pretty esoteric stuff. I was the only person in the world who ever knew what that code did, and I didn't know it like the back of my hand anymore. Particularly the stuff with runtime CIL generation for result set processing. I did a lot of reading my own code and a lot of thinking about it to get that understanding back, but it was a very helpful tool to be able to bounce my questions off an agent during the process and then investigate its answers for accuracy. And greater than 90% of the time its answers turned out to be correct. [1]https://github.com/fsprojects/Rezoom.SQL
In What if useful AI is a fantasy?
Object-oriented polymorphism (interfaces, inheritance) is for when you have a fixed set of methods to implement but an unbounded set of types that may want to implement them. As a consumer, you cannot change the methods, but you can add a subtype. When you subtype an abstract class or an interface, the compiler does not let you proceed until you have implemented all the methods. Discriminated unions are for the exact opposite situation, when you have a fixed set of subtypes, but unbounded set of methods to implement on them. As a consumer, you cannot add a subtype, but you can add a new method. When you write a new method, the compiler does not let you proceed until you have handled all the subtypes. Good languages should support both! The best example is abstract syntax trees, the data types that represent expressions and statements in a
Read the rest
programming language. "Expression" breaks down into cases: integer literal, string literal, variable name, binary operations like add(expr1,expr2), unary operations like negate(expr), function call(functionName, exprs), etc. Clearly all of these expression subtypes should belong to a base type `Expression`. But what methods do you put on `Expression`? If you're writing a compiler, you have to walk this syntax tree many times for very different purposes. First you might do a pass on it where you "de-sugar" syntax, then another pass where you type-check it and resolve names in the code, then another pass where you generate assembly code from it. Perhaps your compiler even supports different backends so you have a code-gen path for x86, another for ARM, etc. You'll likely want a pretty-printer so you can do automatic reformatting, maybe you want linting support, etc. If you look at all those concerns and say that each subtype of `Expression` must implement methods for each one, then you end up with untenable code organization. Every expression subtype now has a huge stack of methods to implement all in one file, dealing with stuff from totally different layers of the compiler. It's a mess. It's much cleaner to have the "shape" of the expression defined in one place without all that clutter, and then in each of those areas of the code you can write methods that consume expressions however they need, so each of those separate concerns lives in its own silo. ------------------------------------------------ Some real code (but it's F# not C#) to look at. AST for my SQL dialect: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez... Typechecker code: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez... Backend code that outputs MS TSQL from it: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez... ------------------------------------------------ If you're an old hand at OO you may be familiar with its actual answer to this problem, the "Visitor" pattern. See System.Linq.Expressions.ExpressionVisitor. However, once you've used a language with good union and pattern matching support, this feels like a clunky hack. Basically the mirror image of a language without real object orientation imitating it by passing around closures and structs-of-closures. ------------------------------------------------ It doesn't just have to be compiler stuff. A business app data model can use this too. Instead of having: public class DbUser { public EmailAddress Email { get; set; } public PasswordHash? Password { get; set; } // null if they use SSO public SamlEntityProviderId? SamlProvider { get; set; } // null if they use password auth } You could have: type UserAuth = | PasswordAuth of PasswordHash | SSOAuth of SamlIdentityProviderId The implementation details of those different auth methods, the UI for them, etc. don't have to be part of the data model. We do have to model what "shapes" of data are acceptable, but "doing stuff" based on those shapes is another layer's problem.
In .NET (OK, C#) finally gets union types

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.