Thank you for your interest in making FerretDB better!
We are interested in all contributions, big or small, in code or documentation. But unless you are fixing a very small issue like a typo, we kindly ask you first to create an issue, to leave a comment on an existing issue if you want to work on it, or to join our Slack chat and leave a message for us there. This way, you will get help from us and avoid wasted efforts if something can't be worked on right now or someone is already working on it.
You can find a list of good issues for first-time contributors there.
The supported way of contributing to FerretDB is to modify and run it on the host (Linux, macOS, or Windows)
with PostgreSQL and other dependencies running inside Docker containers via Docker Compose.
On Linux, docker (with docker compose subcommand a.k.a. Compose V2,
not old docker-compose tool) should be installed on the host.
On macOS and Windows, Docker Desktop should be used.
On Windows, it should be configured to use WSL 2 without any distro;
all commands should be run on the host.
You will need Go 1.19 or later on the host. If your package manager doesn't provide it yet, please install it from go.dev.
You will also need git installed; the version provided by your package manager should do.
On Windows, the simplest way to install it might be https://gitforwindows.org.
Finally, you will also need git-lfs installed and configured (git lfs install).
Fork the FerretDB repository on GitHub.
To have all the tags in the repository and what they point to, copy all branches by removing checkmark for copy the main branch only before forking.
After forking FerretDB on GitHub, you can clone the repository:
git clone git@github.com:<YOUR_GITHUB_USERNAME>/FerretDB.git
cd FerretDB
git remote add upstream https://github.com/FerretDB/FerretDB.gitTo run development commands, you should first install the task tool.
You can do this by changing the directory to tools (cd tools) and running go generate -x.
That will install task into the bin directory (bin/task on Linux and macOS, bin\task.exe on Windows).
You can then add ./bin to $PATH either manually (export PATH=./bin:$PATH in bash)
or using something like direnv (.envrc files),
or replace every invocation of task with explicit bin/task.
You can also install task globally,
but that might lead to the version skew.
With task installed,
you should install development tools with task init
and download required Docker images with task env-pull.
If something does not work correctly,
you can reset the environment with task env-reset.
You can see all available task tasks with task -l.
To build a release binary, run task build-release.
The result will be saved as bin/ferretdb.
With task installed (see above), you may do the following:
- Start the development environment with
task env-up. - Run all tests in another terminal window with
task test. - Start FerretDB with
task run. This will start it in a development mode where all requests are handled by FerretDB, but also routed to MongoDB. The differences in response are then logged and the FerretDB response is sent back to the client. - Fill
valuescollection intestdatabase with data for experiments withtask env-data. - Run
mongoshwithtask mongosh. This allows you to run commands against FerretDB. For example, you can see what data was inserted by the previous command withdb.values.find().
FerretDB can run in multiple operation modes.
Operation mode specify how FerretDB handles incoming requests.
FerretDB supports four operation modes: normal, proxy, diff-normal, diff-proxy,
see Operation modes documentation page for more details.
By running task run FerretDB starts on diff-normal mode, which routes all of
the sent requests both to the FerretDB and MongoDB.
While running, it logs a difference between both returned responses,
but sends the one from FerretDB to the client.
If you want to get the MongoDB response, you can run task run-proxy to start FerretDB in diff-proxy mode.
The directory cmd provides commands implementation.
Its subdirectory ferretdb is the main FerretDB binary; others are tools for development.
The package tools uses "tools.go" approach to fix tools versions.
They are installed into bin/ by cd tools; go generate -x.
The internal subpackages contain most of the FerretDB code:
typespackage provides Go types matching BSON types that don't have built-in Go equivalents: we useint32for BSON's int32, buttypes.ObjectIDfor BSON's ObjectId.types/fjsonprovides converters from/to FJSON for built-in andtypestypes. FJSON adds some extensions to JSON for keeping object keys in order, preserving BSON type information in the values themselves, etc. It is used for logging of BSON values and wire protocol messages.bsonpackage provides converters from/to BSON for built-in andtypestypes.wirepackage provides wire protocol implementation.clientconnpackage provides client connection implementation. It accepts client connections, readswire/bsonprotocol messages, and passes them tohandlers. Responses are then converted towire/bsonmessages and sent back to the client.handlerscontains a common interface for backend handlers that they should implement. Handlers usetypesandwirepackages, butbsonpackage details are hidden.handlers/commoncontains code shared by different handlers.handlers/dummycontains a stub implementation of that interface that could be copied into a new package as a starting point for the new handlers.handlers/pgcontains the implementation of the PostgreSQL handler.handlers/pg/pjsonprovides converters from/to PJSON for built-in andtypestypes. PJSON adds some extensions to JSON for keeping object keys in order, preserving BSON type information in the values themselves, etc. It is used bypghandler.handlers/tigriscontains the implementation of the Tigris handler.handlers/tigris/tjsonprovides converters from/to TJSON with JSON Schema for built-in andtypestypes. BSON type information is preserved either in the schema (where possible) or in the values themselves. It is used bytigrishandler.
Those packages are tested by "unit" tests that are placed inside those packages.
Some of them are truly hermetic and test only the package that contains them;
you can run those "short" tests with go test -short or task test-unit-short,
but that's typically not required.
Other unit tests use real databases;
you can run those with task test-unit after starting the environment as described above.
We also have a set of "integration" tests in the integration directory.
They use the Go MongoDB driver like a regular user application.
They could test target any MongoDB-compatible database (such as FerretDB or MongoDB itself) via a regular TCP port.
They also could test target in-process FerretDB instances
(meaning that integration tests start and stop them themselves) with a given handler.
Some tests (so-called compatibility or "compat" tests) connect to two systems ("target" and "compat") at the same time,
send the same queries to both, and compare results.
You can run them with:
task test-integration-pgfor in-process FerretDB withpghandler and MongoDB on port 37017 (as in our development environment);task test-integration-tigrisfor in-process FerretDB withtigrishandler and MongoDB on port 37017;task test-integration-mongodbfor MongoDB running on port 37017 only;- or
task test-integrationto run all in parallel.
You may run all tests in parallel with task test.
If tests fail and the output is too confusing, try running them sequentially by using the commands above.
You can also run task -C 1 to limit the number of concurrent tasks, which is useful for debugging.
Finally, since all tests just run go test with various arguments and flags under the hood,
you may also use all standard go tool facilities,
including GOFLAGS environment variable.
For example:
- to run a single test case for
pghandler with all subtests running sequentially, you may useenv GOFLAGS='-run=TestName/TestCaseName -parallel=1' task test-integration-pg; - to run all tests for
tigrishandler with Go execution tracer enabled, you may useenv GOFLAGS='-trace=trace.out' task test-integration-tigris.
In general, we prefer integration tests over unit tests, tests using real databases over short tests and real objects over mocks.
(You might disagree with our terminology for "unit" and "integration" tests; let's not fight over it.)
We have an additional integration testing system in another repository: https://github.com/FerretDB/dance.
Above everything else, we value consistency in the source code. If you see some code that doesn't follow some best practice but is consistent, please keep it that way; but please also tell us about it, so we can improve all of it. If, on the other hand, you see code that is inconsistent without apparent reason (or comment), please improve it as you work on it.
Our code most of the standard Go conventions, documented on CodeReviewComments wiki page. Some of our idiosyncrasies:
- We use type switches over BSON types in many places in our code.
The order of
cases follows this order: https://pkg.go.dev/github.com/FerretDB/FerretDB/internal/types#hdr-Mapping It may seem random, but it is only pseudo-random and follows BSON spec: https://bsonspec.org/spec.html
Before submitting a pull request, please make sure that:
- Tests are added for new functionality or fixed bugs.
Typical test cases include:
- happy paths;
- dot notation for existing and non-existent paths.
- Comments are added or updated for all new or changed code. Please add missing comments for all (both exported and unexported) new and changed top-level declarations (functions, types, etc).
- Comments are rendered correctly in the
task godocsoutput. task allpasses.
With task installed (see above), you may do the following:
- Format and lint documentation with
task docs-fmt. - Start Docusaurus development server with
task docs-dev. - Build Docusaurus website with
task docs.
Before submitting a pull request, please make sure that:
- Documentation is formatted, linted, and built with
task docs. - Documentation is written according to our writing guide