I once had to work with a project which used MariaDB connector for Node.js, and for some reasons it was necessary to add Knex query builder to it. Honestly, I am not a big fan of query builders, because I know SQL pretty well, and usually it is faster for me to write a query in SQL than use a query builder. But sometimes you have to obey someone else’s decisions 🙂
The main issue was that Knex does not support MariaDB connector out of the box: it supports only a few drivers, and MariaDB is unfortunately not one of them (you can use either mysql or mysql2).
However, because the project used mariadb, I did not want to switch to mysql or mysql2, and decided to write a MariaDB client for Knex.
It turned out that there had been no simple way to extend Knex: even the list of supported clients was immutable:
const SUPPORTED_CLIENTS = Object.freeze(
[
'mssql',
'mysql',
'mysql2',
'oracledb',
'postgres',
'redshift',
'sqlite3',
].concat(keys(CLIENT_ALIASES))
);
And if you try to pass anything else as client, you get an error: “Error: knex: Unknown configuration option ‘client’ value XXX. Note that it is case-sensitive, check documentation for supported values.”
The official documentation was unhelpful there, so I had to dig into the code.
It turned out that:
- If no parameters are passed to Knex, it uses a generic client (whatever it means).
- If
config.clientis a function, and is derived fromClient, the function will be used as a client. - Otherwise, client name is taken from
config.clientorconfig.dialectand is expected to be a string. If the name is not inSUPPORTED_CLIENTS, Knex throws an exception, otherwise it loads the client fromdialects/<client>/index.js.
To create a client (or dialect), I had to use the second option. Fortunately, mariadb connector can be compatible with mysql and mysql2: it provides a callback API for compatibility reasons. This means that I don’t have to write everything from scratch, and it should be enough to rewrite the existing mysql2 dialect.
Basically, I had to re-implement only two things: _driver() and validateConnection() methods. The first one returns the driver (or connector) — mariadb in my case, and the second one checks whether the connection is valid (mariadb does that differently).
And here is the final code:
const Client_MySQL = require('knex/src/dialects/mysql/index');
class Client_MariaDB extends Client_MySQL {
driverName = 'mariadb';
_driver () {
return require('mariadb/callback');
}
validateConnection (connection) {
return connection.isValid();
}
};
module.exports = Client_MariaDB;
Thanks for explaining the reasoning and logic of the connector you have created. It was helpful.
You may also add a link to project README.
thanks for this detailed solution. Appreciate your sharing of repo too. https://github.com/sjinks/knex-mariadb/tree/master