Trilogy: a MySQL client library in C with three layers of API
Trilogy is a client library for MySQL-compatible database servers, designed for performance, flexibility, and ease of embedding.
At a glance
- What is it?
- Trilogy splits the MySQL wire protocol into an IO-decoupled core, a non-blocking client and a blocking wrapper, so you can drive it from an event loop or from ordinary blocking code. It has no dependencies outside POSIX, the C standard library and OpenSSL, and it is in production use on github.com.
- Who is it for?
- Adopt Trilogy if you are writing C and need to talk to a MySQL-compatible server without linking libmysqlclient or a heavy connector library, especially if your program already has its own event loop. Do not adopt it if you need the full MySQL command surface, since the library supports only the parts of the text protocol that are in common use, and it has no support for LOAD DATA INFILE on local files.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A parser with no parser in it
Trilogy is a client library for MySQL-compatible database servers, designed for performance, flexibility and ease of embedding, and it is currently in production use on github.com. The unusual thing is what the library is not. There is no object model, no connection handle with methods on it, no result set abstraction and no string formatting helper for dates and numbers. What there is instead is a set of layers that a caller assembles. The lowest level parses and builds packets with no knowledge of sockets, the next layer speaks the MySQL protocol on top of that, and the top layer is a set of convenience functions. The README calls the low-level protocol API completely decoupled from IO, and the headers list separates the same idea into files: protocol.h for the IO-decoupled functions to parse and build packets, packet_parser.h for a streaming packet frame parser, builder.h for a MySQL-compatible packet builder API, and reader.h for a bounds-checked packet reader API.
Three API layers, and you pick the one that fits your loop
The layering is the design, and the feature list names it in order. There is a low-level protocol API completely decoupled from IO. Above it a non-blocking client API wrapping the protocol API, where every command is split into a _send and a _recv function allowing callers to wait for IO readiness externally to Trilogy. Above that a blocking client API wrapping the non-blocking API. The headers document the same split. blocking.h holds the blocking client API, described as a set of convenient wrapper functions around the non-blocking client API in client.h. client.h holds the non-blocking API with the _send and _recv split. That middle layer is the one that matters for embedding, because it is where you decide whether the socket wait belongs to your event loop or to the library. error.h holds the error table, and every Trilogy function returning an int uses the error codes defined there, which is a plain convention rather than a set of errno values.
Protocol coverage is deliberately narrow
The features list says the library supports the most frequently used parts of the text protocol, and the limitations section repeats the point. Only supports the parts of the text protocol that are in common use. The supported list is the handshake, password authentication, and the query, ping and quit commands. On top of the text protocol there is support for prepared statements via the binary protocol, which is the part that matters for anyone who wants parameterised queries without the escaping risk. The rest of the stated limitations are worth reading before you commit. There is no support for LOAD DATA INFILE on local files, and trilogy_escape assumes an ASCII-compatible connection encoding, so a connection in a different encoding will produce wrong escapes rather than an error. charset.h holds the character set and encoding tables, so the tables are there, but the escape function's assumption is a real constraint if you run a utf8mb4 connection with a non-ASCII client encoding.
Building it is one make target and a static archive
The build section is short. You run make, that is it, and this will build a static libtrilogy.a. Trilogy should build out of the box on most UNIX systems which have OpenSSL installed. The Makefile in the repository shows the compiler settings, and they are strict in a way that says something about the project's standards. The default CFLAGS are -O1 -ggdb3 with -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 and -fstack-protector, and the warning set is -Wall -Werror -Wextra -pedantic -Wsign-conversion -Wno-missing-field-initializers with the standard as gnu99 and the inc directory on the include path. The link flags add -pie -Wl,-z,relro,-z,now on non-Darwin platforms, and on Darwin the Makefile points at the brew prefix for openssl@3 instead. Beyond the default target there are an examples target, a debug target that switches to -O0, an analyze target that runs the compiler's static analyzer over the sources, and a fuzz target.
The test suite and the CI matrix are the interesting part
The Makefile shows how the tests are built and run:
test/test: $(TEST_SOURCES) libtrilogy.a
$(CC) $(CFLAGS) $(LDFLAGS) -Wno-strict-prototypes -o test/test $(TEST_SOURCES) -L. -ltrilogy $(OPENSSL)The test binary is linked against the static archive and the OpenSSL libraries, and the make test target simply runs it. There is also an update_greatest target that fetches the greatest.h test framework from silentbicycle's repository, so the test harness is a vendored single-header framework refreshed on demand. The CI side is in the Docker files. The compose file defines a db service on the linux/x86_64 platform using the ci-${DB_VENDOR:-mysql}:${DB_VERSION:-8.0}-debian image built from test/${DB_VENDOR:-mysql}/Dockerfile with MYSQL_VERSION and MARIADB_VERSION build arguments, and an app service built from the ci-app image. The database is reachable at db.local with an alias of wildcard.db.local, which is how the test suite exercises host name matching behaviour. Both services mount certificate directories and the test and initdb directories, so the CI runs against real MySQL and MariaDB servers rather than a mock.
A Ruby binding in the same tree, and a low allocation footprint
The project maintains a Ruby binding in contrib/ruby inside this repository, and the README states that this is currently stable and production-ready. That is worth a moment, because it says something about the library's API design. A C client library that a Ruby binding can wrap cleanly is exposing a surface that maps onto a higher-level language without impedance, which is the opposite of what a tightly coupled protocol implementation tends to look like. The other feature worth naming is minimal dynamic allocation, listed alongside no dependencies outside of POSIX, the C standard library and OpenSSL. The combination of those two is the whole embedding argument. A library you link into a long-running daemon should not fragment the heap, and a library you vendor should not drag a transitive dependency tree into your build. The repository also carries a Dockerfile and an example/trilogy_query.c, plus an inc/ directory for the headers and src/ for the implementation, with script/ and test/ directories for the test entry point.
Where the narrow protocol support bites
Two limits decide whether Trilogy fits your project. The first is command coverage. The library implements the handshake, password authentication, query, ping and quit, plus prepared statements, and that is the list. Anything outside it, including LOAD DATA INFILE on local files, is not there. If your application needs a richer command surface, the choice is between accepting that limitation or using a fuller connector. The second is the connection encoding assumption in trilogy_escape, which requires an ASCII-compatible connection encoding, so a client using a non-ASCII encoding and relying on that helper to build queries is in territory the library does not cover. Neither limit is a defect. They are the direct consequence of a design that keeps the protocol layer small and the dependency list short, and they are stated in the README rather than discovered later. The security and maintenance side is also visible, with a SECURITY.md, a CODE_OF_CONDUCT.md, a CHANGELOG.md and a CONTRIBUTING.md at the root, and a v2 line released regularly with v2.13.1 on 2026-10-02 after v2.13.0 in August and v2.12.5 in May.
Editorial conclusion
Adopt Trilogy if you are writing C and need to talk to a MySQL-compatible server without linking libmysqlclient or a heavy connector library, especially if your program already has its own event loop. Do not adopt it if you need the full MySQL command surface, since the library supports only the parts of the text protocol that are in common use, and it has no support for LOAD DATA INFILE on local files. Before you commit, check that your server's authentication method is one the handshake supports, and remember that trilogy_escape assumes an ASCII-compatible connection encoding.
Frequently asked questions
What is the Trilogy C library?
It is a client library for MySQL-compatible database servers, designed for performance, flexibility and ease of embedding, with no dependencies outside of POSIX, the C standard library and OpenSSL. It is MIT licensed and is currently in production use on github.com.
How do I build Trilogy?
Run make, which builds a static libtrilogy.a. It should build out of the box on most UNIX systems which have OpenSSL installed. The Makefile also has examples, debug, analyze, fuzz and test targets, and the compiler flags are strict, with -Wall -Werror -Wextra -pedantic and -Wsign-conversion.
Which MySQL commands does Trilogy support?
The most frequently used parts of the text protocol, namely the handshake, password authentication, and the query, ping and quit commands, plus prepared statements over the binary protocol. It explicitly does not support LOAD DATA INFILE on local files.
What are the API layers in Trilogy?
There is a low-level protocol API that is completely decoupled from IO, a non-blocking client API wrapping it where each command is split into _send and _recv functions so callers can wait for IO readiness themselves, and a blocking client API wrapping the non-blocking one. The headers are protocol.h, client.h and blocking.h, with builder.h, reader.h, packet_parser.h, charset.h and error.h for the supporting pieces.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/trilogy-libraries-trilogy)Was this useful?