Home

Awesome

ndbapi: Common Lisp bindings to the C++ NDB API of RonDB

This library allows to use the NDB API of RonDB in Common Lisp programs and thus write client applications that talk to RonDB. RonDb is a key-value store providing low latency, high throughput, and high availability (actually, Class 6 Availability or "six nines").

Parallel to the key-value stores capabilities RonDB also provides SQL capabilities. As RonDB is an open source distribution of NDB Cluster, which is the distributed database system underlying MySQL Cluster, these SQL capabilities are actually provided by running MySQL Servers on top of RonDB.

As a consequence, you can use SQL to setup new databases, tables, indexes and so forth, and also to load or export data, while you can use the NDB API to efficiently retrieve data, for example, by making use of specialized indexes. Look at the instruction on how to test this library for an example for this.

NOTE: This is an early release and the library still evolving as of 2022-10-25. MGR

Description of the library and the interfaces it provides

The library bases on a FFI binding code that was generated using SWIG 3.0.12. It consists of a complete low-level and a nicer high-level interface.

The high-level interface makes just those parts of the NDB API available that I need to implement a new database backend based on RonDB for our graph store but in a much nicer fashion, with scope-based macros such as ndbapi:with-ndb, ndbapi:with-ndb-transaction et cetera, but you can also explicitly free the objects with ndbapi:ndb-free-object or just let the the GC take care of them, as the constructed C++ objects are hooked up into SBCL's garbage collector with thin Lisp wrapping classes so that they get properly freed by the respective destructors if they are not referenced anywhere anymore.

The low-level interface makes the full NDB API available. But is is hard to use as it includes no class hierarchy, and thus exhibits long names, and implements no overloading. That is, the overloaded method myNdb.startTransaction(), for example, is available as the ten separate Lisp functions from _wrap_Ndb_startTransaction__SWIG_0 to _wrap_Ndb_startTransaction__SWIG_9 with varying arguments.

Care has been taken that NDB is not deinitialized by NDB_END if there is still a cluster connection, or a cluster connection is not freed as long there are NDB objects that were created using it. Also double freeing is avoided, so you can combine the explicit and automatic approaches and still be sure that eventually the GC will free the resources when you have missed one.

The exported interface is defined as the package :ndbapi in file src/interface.lisp. The implementation is in the subdirectory src/, the examples in example/, and the ASDF system definitions in the top level directory in the files ndbapi.asd and ndbapi-examples.asd.

An example application is available in file examples/ndbapi-simple-scan.lisp. For comparison a C++ version of the same example is implemented in file examples/ndbapi_simple_scan_in_cpp/ndbapi_simple_scan.cpp. It is based on other examples of ndb cluster but does not use a MySQL node. The example uses a secondary index as a scan index to iterate over columns in an example database observing bounds. See both files for details.

A second example application is in file examples/ndbapi-scan-count.lisp. It gives an estimated count of the rows matching a bound of lower and/or upper limits. This example uses a number of more advanced features compared to the first example to make the count efficient, for example, interpreted programs to limit the row access to just one per partition (or fragment), accessing special values (or pseudo columns), and masking of columns. Please read the long comment at the beginning of the file examples/ndbapi-scan-count.lisp for a more detailed explanation.

Installation

The installation instructions are in the separate file INSTALL.md.

Details on the generation of the FFI bindings

This library bases on a FFI binding code that was generated using SWIG. As CFFI support was disabled in SWIG 4.0.0 and finally completely removed in SWIG 4.1.0, SWIG 3.0.12 was used, which is part of Ubuntu 20.04.2 LTS.

NDB API is a rather substantial C++ library and uses many things not possible in C while the CFFI module of SWIG has quite a few limitations and cannot handle some C++ constructs. Still, SWIG successfully generates a complete C wrapping library (using extern "C") named src/ndbapi_wrap.cxx and a lisp library making all C definitions available to Lisp via CFFI constructs in src/ndbapi.lisp. Also a higher level CLOS interface gets generated as src/ndbapi-clos.lisp, which contains generated classes and generic methods.

The C wrapping library exhibits a rather low-level interface as all hierarchical class names are rolled out with prefixes and all overloaded methods are exported as separate functions. That is, the overloaded method myNdb.startTransaction(), for examples, becomes available as the ten separate Lisp functions from _wrap_Ndb_startTransaction__SWIG_0 to _wrap_Ndb_startTransaction__SWIG_9 with varying arguments. Most of the time the methods only vary in their arity, which would allow for a simple dispatch by arity. But there are also some methods that have the same number of arguments but of different types, as, for example, NdbDictionary::getValuePtr(), the constructor of the Column class, or Table::getColumn().

SWIG 3.0.1 just created CFFI bindings with the same lisp name for a family of overloaded methods, which conflicted, of course, while the CLOS interface just tries to call these function, ignoring the fact that there is overloading supposed to be involved.

In the end, after exploring other versions of SWIG (see section "Exploration of other SWIG versions ..." for details), the CFFI bindings generated by SWIG 3.0.1 were used, completely ignoring the CLOS interface in ndbapi-clos.lisp. This simple CFFI interface could be generated for the full NDB API interface and seemed complete. Afterwards, many translation problems or errors were manually fixed.

I have change the conflicting Lisp names in an automated fashion to also contain the SWIG-* postfix, to have, for example, the C function _wrap_new_Ndb__SWIG_1 available as new_Ndb/SWIG-1 in Lisp. Simple dispatch arity is implemented in file src/overloading.lisp by the macro ndbapi.ffi.overloading::overload-function-by-arity and used for some methods.

I just focused on having the parts available that I need to implement a new database backend based on RonDB for our graph store rather then exporting everything. And to have this core available in a safe fashion so that the C/C++ NDB objects are properly constructed and always destructed.

Exploration of other SWIG versions for a more complete generated interface

An attempt was made to use the enhanced variant 'swig-cpp-to-cffi-for-common-lisp-enhancements` of SWIG which was authored by Jason E. Aten in 2011, who spent quite some time to make the CFFI module for SWIG work for "a large codebase of C and C+". Sadly, this version choked on some parts of the NDB API, as, for example, structs with constructors, and it just exited with a SEGFAULT. It was possible generate a version for a smaller subset of the NDB API, after taking out problematic parts of it that were determined by having a version of SWIG with debug logging activated and running it from within GDB.

This version of SWIG actually contained something that Aten describes as "a synthesized dispatcher that allows natural calling of overloaded constructors by keyword specification is provided". Meaning that for each overloaded method first a function is generated which implemented dispatch by arity and then for each variant generic methods are generated that dispatch on the types. But the methods that actually would use it, as the above mentioned NdbDictionary::getValuePtr(), seem to be just missing from the CLOS interface.

In the end, this attempt was aborted, as the generated interface seemed to be incomplete even when only parts of the NDB API were translated.

It is noted that Nikolai Matiushev attempted to ("Revive CFFI Common Lisp support") in 2021](https://github.com/swig/swig/pull/1966) but this seems to "lack an C++ support". Still, if that work is continued it might lead to a nicer automatically generated high-level interface.

License

Copyright (c) 2022 Max-Gerd Retzlaff mgr@matroid.org, Datagraph GmbH.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

A copy of the license is also reproduced in the file LICENSE in the top level directory of this repository.