Go / Informix / LLM-assisted review

From LLM review to the wrong protocol: how I tested a Go driver for Informix

≈ 7 min read

Go driver connected to an Informix database before hitting a protocol mismatch
Pic. 1. A working test connection hid the protocol mismatch that blocked production use.

At work, I needed to rewrite a piece of legacy code from Perl to Go. The code itself was well established, and there were no real problems with it. The problem was a dependency: the main database was still Informix.

Let us skip my explanation of why it had to be Informix. “Historical reasons,” as one of my old team leads used to say.

Informix is a database with a long history. It was once a major player, but today it is a fairly niche technology. It is still developed and used in production, but finding a modern Go library for it is much harder than for PostgreSQL or MySQL.

A year earlier, I had already looked for a suitable adapter. I found IfxGo [1] , an old fork made specifically for Informix, and its base project, the general-purpose ODBC driver [2] . I could not get them to work properly back then, so I had to put the refactoring idea aside.

This time I found go_ibm_db [3] . At first glance, it looked like a ready-made solution: the project had been alive since 2018, the repository had about 280 commits, it had a Bun [4] integration, and it had more than a hundred stars on GitHub. That was no guarantee of quality, of course, but it still looked better than an abandoned fork with no releases.

I started Informix in a container, installed IBM CLI Driver, connected to it, and read some data. Everything worked on the first try.

I was delighted. The refactoring I had postponed for about a year suddenly stopped looking like a problem. All I had to do was add the library and write the code.

Just in case, I asked GLM 5.3 in agent mode to look through the codebase. It was mostly out of curiosity: maybe it would find something interesting.

Note: I understand that the text already makes it clear that the library might have problems. However, the past few years of programming in Go have spoiled me. Almost every library does what I need without much tinkering, so I let my guard down.)

The first real bug

I became suspicious when GLM 5.3 pointed out several potentially problematic places. The first thing I tackled was SQL_ATTR_PARAMSET_SIZE. It was not reset for scalar executions after an array insert. We checked it on Informix: insert [1, 2], then insert 3 using the same statement -> SQLSTATE 22003, while the table ended up containing [1, 2, 3, 3].

This was worse than a normal error. The application saw a failed query even though the data had already been written, including an unwanted duplicate. If the application automatically retried the operation after the error, it would create even more duplicates.

I wrote a regression test, added the attribute reset, and submitted PR #290 [5] . The fix was accepted quickly.

That was a good sign. The maintainers responded quickly and to the point, so I continued the review.

From review to reproducible tests

One LLM response is not enough to call a suspicious piece of code a bug. A model is good at mapping out places worth checking, but every hypothesis still needs a test.

I added CI, ran the tests on different platforms, enabled linters, and started working through the findings one by one. Then I gave the changes to another model for a cross-review. This produced three more fixes.

The first was a panic on an empty parameter array. The code accessed the first slice element immediately. For []int64{} or a typed nil, this caused an index out of range panic instead of returning a normal error. The fix and 14 regression cases went into PR #291 [6] .

The second was premature truncation in SQLGetInfo. The driver allocated a buffer of 128 UTF-16 elements, or 256 bytes, but told ODBC that the buffer size was 128 bytes. Half of the allocated memory was therefore unavailable for writing. The problem was easy to reproduce with a long SQL_KEYWORDS value, while under load it appeared as an occasional empty SQL_DBMS_NAME. The fix went into PR #292 [7] .

The third was a statement handle leak during query cancellation. If the context was canceled after query execution and column binding but before Rows was created, the statement was already considered to be owned by the result. But the Rows object was never created, so nothing could release the handle. After the fix, the cursor was closed, the statement could be reused, and the handle was released correctly. This became PR #293 [8] .

The maintainers accepted all four fixes into master.

The review gradually expanded:

  • static review helped find suspicious code;
  • regression tests separated real bugs from assumptions;
  • -race and load tests caught lifetime and concurrency problems;
  • repeated runs against a live Informix instance checked that a fix worked outside mocks as well.

At some point, the driver looked much more reliable than it had at the start. The tests passed, the connection worked, and the fixes were upstream. It looked like the main work was done.

It does not matter how deep you dig if you are digging in the wrong place

Something still bothered me. I went back to the very first LLM response that had started the whole investigation:

> I need an adapter for Go and Informix.

< Driver                  Notes
< ibmdb/go_ibm_db         IBM driver, CGO + CLI Driver. Connects through DRDA.
< alexbrainman/odbc       General-purpose ODBC driver. Works through Informix Client SDK.
< OpenInformix/IfxGo      Old ODBC driver fork for Informix.

Wait. What does “connects through DRDA” mean?

Informix supports different network protocols. Our configuration used onsoctcp, the regular native Informix protocol over TCP/IP. IBM Data Server Client connects to Informix through DRDA, which requires a separate listener of type drsoctcp on the server. These are not two names for the same connection. They are different server interfaces.

I checked the configuration with my colleagues:

> Does Informix use onsoctcp or drsoctcp?

< onsoctcp
< Nobody has tested DR because we do not need it

The test container allowed me to enable the required interface, so go_ibm_db connected and executed queries without any problems. But production only had onsoctcp. Using the chosen driver would require changing the Informix configuration, opening another port, and separately testing how DRDA behaved in production.

This was no longer a matter of replacing a library in a Go application. It was an infrastructure change around a critical database. Making that change only for the convenience of one driver made no sense.

The official Informix documentation [9] confirmed exactly that: onsoctcp is used for native TCP/IP connections, while drsoctcp is used for IBM Data Server Client connections over DRDA.

The result was almost funny. I had found several real bugs, written tests, run the driver under load, and submitted four fixes, all of which made it into master. But the driver still did not fit the original task.

Was the work useless?

Not quite.) For one thing, the fixes remained in the open source project and might help its users. I also built a working approach for reviewing CGO drivers: start with a static review, turn every suspicion into a minimal test, then add race detection, load testing, and checks against a real database.

But the main lesson was simpler than the technical details: compatibility does not start with a successful SELECT 1. First, you need to check the environment constraints: the protocol, the client library, the server version, and which infrastructure changes are acceptable. Only then does it make sense to dig deep into the implementation.

I went back to the combination I had put aside a year earlier: alexbrainman/odbc and IfxGo. This time, I already had a test environment, a set of scenarios, and a much better idea of where to look for problems.

That was where a completely different story began: checkptr, runtime.Pinner, fuzzing, 30-minute runs, and the point where Go tools were no longer enough and I had to start looking at Valgrind and C ThreadSanitizer.

But that is a story for the next article.

References

[1] OpenInformix/IfxGo: an Informix database driver for Go. GitHub repository.

[2] alexbrainman/odbc: an ODBC database driver for Go. GitHub repository.

[3] ibmdb/go_ibm_db: a Go driver for IBM databases. GitHub repository.

[4] uptrace/bun: a SQL-first database client for Go. GitHub repository.

[5] ibmdb/go_ibm_db, pull request #290: reset SQL_ATTR_PARAMSET_SIZE after array binding.

[6] ibmdb/go_ibm_db, pull request #291: handle empty parameter arrays without panicking.

[7] ibmdb/go_ibm_db, pull request #292: correct the SQLGetInfo buffer length.

[8] ibmdb/go_ibm_db, pull request #293: release statement handles when queries are canceled.

[9] IBM Informix documentation: SQLHOSTS connectivity information.