Blog
Sorry, your browser does not support inline SVG.

What’s New in the Go API v2

Berwyn Hoyt

It’s a wrap! The end of a long journey, and YDBGo v2 is finally here with its pristine finish and a V8 rumble that’s champing at the bit to get some traction in your code.

YDBGo v2 is more concise, easier to read, and more “Go-like”. While v1 had two command sets called “SimpleAPI” and “EasyAPI”, v2 is simpler and easier than both (see the syntax comparison below). Version 2 is also faster than both, and better protects you from inadvertent bugs. These features are presented below along with other functional additions, risk reductions, internal improvements, and a final section on migration from v1 to v2 which includes a table of examples of syntax changes.

Speed Improvements

YDBGo v2 is faster than v2:

  • For basic operations like setting a node (benchmarked here):
    • 1-4% faster than the v1 SimpleAPI
    • 4 to 7 times as fast as the v1 EasyAPI
  • For a fully-loaded multi-process application (per this 3n+1 sequence benchmark):
    • 1-3% faster than the v1 SimpleAPI
    • v1 EasyAPI not reported as it was too slow to be worth benchmarking

How We Did It

Most of the speed gains were achieved in v2 by reducing memory allocations — database accesses use database objects (actually, the Go equivalent of objects: type instances). Specifically, v2 requires database access through a connection instance *Conn and node instance *Node.

Each Conn instance allocates buffers for API transfer of error strings, values, and, where necessary, M call-in parameters. A Node instance holds a database variable name and subscript names in API-call format so that rapid operations on that database subtree may be performed many times.

Although Child() nodes may be created from any Node instance, for fast iteration, Node instances may be Index()‘d to access sub-nodes without re-allocating a new Node instance for each subnode. Index() achieves this internally by yielding a ‘mutable’ node instance which is re-used by changing (mutating) the stored subscripts. Iterators are provided to traverse the database that automatically use this mechanism by yielding mutable nodes as the loop variable.

Reduced memory allocations and mutating node objects both result in less garbage collection than the EasyAPI.

Calling M, the language embedded in YottaDB, is also faster with more intuitive syntax by allowing Go’s binary types (int, float, etc) to be passed as native parameter types to the API. This avoids needless conversion to and from strings with relatively slow conversion functions like strconv.Atoi(), and avoids associated error checking.

There are other minor speed gains. Database access via Node instances reduces the number of parameters stacked by each function call and also reduces the error checking needed by each function. Errors checks are now performed once when the Node instance is created rather than at each API call on that node, yielding a slight speed increase. The same mechanism allows fewer function calls to be made than with the SimpleAPI, achieving better readability and a small speed gain.

More specific details of speed improvements are documented in this Issue.

Functional Improvements

Since Go does not support C’s atexit() functionality, the API now provides an explicit handler function, ShutdownOnPanic(), which application code should defer at the start of every goroutine. This ensures that the database will be cleanly run down before the process exits, ensuring database structural integrity without an application needing to write its own panic handler. As for the main goroutine, the API Init() function now returns a handle that must be passed to defer Shutdown(): an intuitive cue to the programmer that the main goroutine also needs to be shutdown.

Version 2 correctly handles panics that occur in Go callback functions that traverse the CGo boundary: specifically, panics inside transactions or inside signal goroutines. Previously such panics would un-stack the CGo call directly, which is unsafe in Go. Now panics are caught inside the callback with recover, the error is returned through the CGo boundary, and re-panicked once back in Go. The re-panic error message also includes the traceback of the original panic so that errors inside transactions and signals may still be easily located. This paragraph can be fleshed out with examples in a future blog message. Let us know if you’re interested in such an article.

Documentation and Source Improvements

For v2, the official location of the API documentation has moved to the Go Packages website, where Go developers look for package documentation. The YottaDB Multi-Language Programmers Guide points to the Go Packages site for the Go v2 API. Documentation for each function now includes a leading purpose statement and numerous executable examples have been added.

When you click a function name from Go Packages website to take a peek at the YDBGo source code, you’ll find that v2 is more readable and maintainable. Internally, it uses more idiomatic Go, such as the range keyword, iterators, and atomic variables instead of pseudo-C versions of the same. Extensive use of struct type instances cleans up internal as well as API function signatures. The codebase is now fully compliant with the Go lint tool staticcheck. Yoda conditions have been replaced with more familiar Go syntax.

All the original unit tests have been implemented against v2, and automated test coverage for code has improved from 75% to 96%.

Syntax Improvements

The use of type instances to access the database simplifies the v2 API:

  • API method names have been renamed according to Go policy, to be whole words, and the scope omitted now that it is clear from the parent type.
  • tptoken and errstr need no longer be passed to every API function as they are stored in the Conn instance. Each Node instance also references its Conn instance, so the latter doesn’t need to be passed to Node API functions.
  • The Conn and Node type instances harness Go’s garbage collector to automatically free its allocated buffers when each instance is no longer used. This means an application no longer has to call Alloc and Free functions, and prevents inadvertent memory leaks that were possible with the v1 SimpleAPI.
  • Calling M from Go is now simpler and more Go-like using the new Import() function.

See also: Examples of syntax changes.

Error Handling

Error handling has been standardized and simplified for readability:

  • All API errors (returned or panicked) may now be identified by an error Code (negative codes for YottaDB errors and positive codes are for YDBGo errors). This allows the programmer to identify and handle specific errors as desired, even for panicked errors captured by a Go recover statement.
  • Node methods now automatically handle expected YottaDB return codes that are not errors. For example:
    • Iterators terminate on NODEEND.
    • Get() automatically allocates more space and retries on INVSTRLEN.
    • Get() allows the user to supply a default value rather than returning GVUNDEF and LVUNDEF.
  • Node methods now panic on all programmer errors (per Go policy), and also on database system errors like out of memory.
    • Panicking on system errors greatly improves readability of most database operations, which no longer have to check for errors. It also means that robust applications should use Go recover to capture database system errors in order to handle them gracefully rather than panic.

Signals

As with v1, signals that are used by YottaDB require special treatment if an application also needs to use them. The v2 API has changed the signal handling as follows:

  • RegisterSignalNotify() has become SignalNotify() and now matches the function signature of Go’s built-in signal capture function signal.Notify().
  • In addition, a v2 API signal handler no longer has to specify whether to pass the signal on to YottaDB before, during, or after the user’s handler. Instead, user handlers must simply call NotifyYDB() at the point in their handler where they wish to notify YottaDB of the signal.

Migration from v1 to v2

Applications that use YDBGo v1 continue to operate without change, and YottaDB continues to support the v1 API. To aid migration of large applications from YDBGo v1 to v2, it is possible to use v1 and v2 APIs in the same application code. For details, see the migration section of the README.

  • Here is an example Go program migrated from YDBGo v1 EasyAPI to YDBGo v2 as a side-by-side diff.
  • Similarly, here is a diff of another Go program migrated from v1 SimpleAPI to v2.

Below is a table mapping v1 syntax to v2. For further information, refer to the official documentation.

Examples of Syntax Changes

v2 Syntax v1 Syntax
import "lang.yottadb.com/go/yottadb/v2" import "lang.yottadb.com/go/yottadb"
db := yottadb.MustInit()
conn := NewCon()
yottadb.Shutdown(db)
err := yottadb.init()
handle(err)
yottadb.exit()
Node (type) KeyT (type)
Simple API
n := conn.Node(“varname”, “sub1”, “sub2”, …) var n yottadb.KeyT
err1 := n.Alloc(varSize, numSubs, subSiz)
defer n.Free()
err2 := n.Varnm.SetValStr(tptoken, &err, “varname”)
err3 := n.SubAry.SetValStr(tptoken, &err, “sub1”)
err4 := n.SubAry.SetValStr(tptoken, &err, “sub2”)
<handle errors>
Easy API
n := Node(var, subs…)
n.Set(value)
x := n.Get()
err1 := SetValE(tptoken, &err, value, var, subs)
x, err2 := ValE(tptoken, &err, var, subs)
<handle errors>
x := n.Incr(3) x, err2 := IncrE(tptoken, &err, “3”, var, subs)
hasValue := n.HasValue()
[cf. HasTree, HasValueOnly, etc.]
data, err := DataE(tptoken, &err, varname, subs)
hasValue := (data == 1)
<handle errors>
n.Kill() err := DeleteE(tptoken, &err, 1, varname, subsarray)
<handle errors>
n.Clear() err := DeleteE(tptoken, &err, 2, varname, subsarray)
<handle errors>
conn.KillLocalsExcept(localNames, var1, var2) err := DeleteExclE(tptoken, &err, []string{var1, var2)
Locking
locked := n.Lock(timeout) err := LockIncrE(tptoken, &err, timeout, var, subsarray)
locked := (err != LOCKTIMEOUT)
<handle errors>
n.Unlock() err := LockDecrE(tptoken, &err, timeout, var, subsarray)
<handle errors>
locked := conn.Lock(timeout, node1, node2, …) err := LockE(tptoken, &err, timeout, var, subs, …)
locked := (err != LOCKTIMEOUT)
<handle errors>
Iteration
for node := range n.Children() {
println(node)
}
No iterator. Complicated using SubNextE()
for node := range n.Tree() {
println(node)
}
No iterator. Very complicated using NodeNextE()
Transactions
success := conn.Transaction(“BATCH”, nil, func() {
n.Set(value)
}()
err := TpE(tptoken, &err, func(tptoken uint64, errptr *yottadb.BufferT) int32 {
SetValE(tptoken, &err, value, var, subsarray)
}(“BATCH”, nil)
success := (err != YDB_TP_ROLLBACK)
<handle errors>
Calling M
table := conn.MustImport("calltab.ci")
retval := table.Call("mfunc", "param1", 2, 3)
table, err := CallMTableOpenT(tptoken, &err, "calltab.ci")
<handle errors>
_, err = table.CallMTableSwitchT(tptoken, &err)
<handle errors>
var routine yottadb.CallMDesc
routine.SetRtnName("mfunc")
retval, err := routine.CallMDescT(tptoken, &err, 64, "parm1", "2", "3")
<handle errors>
Handling Signals
ch := make(chan os.Signal, 1)
SignalNotify(ch, syscall.SIGINT)
go func(ch chan os.Signal) {
defer yottadb.ShutdownOnPanic()
for {
sig := <-ch
NotifyYDB(sig)
fmt.Printf("\nSignal %s\n", sig)
}
}(ch)
ch := make(chan bool, 1)
ack := make(chan bool, 1)
RegisterSignalNotify(syscall.SIGINT, ch, ack, NotifyAfterYDBSigHandler)
go func(ch, ack chan bool) {
defer usersOwnPanicSafety()
for {
<-ch
sigAck <- true
fmt.Printf("\nSignal SIGINT\n")
}
}(ch, ack)
Debug Output
println(n) =>
varname("sub1","sub2")
println(n.Dump()) =>
BufferT.Dump(): cbuftptr: 0x12341234, buf_addr: 12341234, len_alloc: 12341234, len_used: 24
BufferTArray.Dump(): cbuftary: 0x12341234, elemAlloc: 3, elemUsed: 3, elemLenAlloc: 20
0: sub1
1: sub2
n.Set(1)
n.Child(“age”).Set(42)
println(n.Dump()) =>
varname("sub1", "sub2")=1
varname(&quot&sub1", "sub2", "age")=42
No equivalent

 

There you have it, folks. Let us know how you like it.


Credits

  • Images generated by Google Gemini in response to prompting by K.S. Bhaskar.

Published on March 12, 2026