This document provides guidance to coding agents focusing on C++ deliverables when working in the DuckDB CityJSON Extension repository.
- The loadable extension itself is implemented in Rust and exposes DuckDB SQL functions.
- C++ work in this repository usually targets integration scenarios: embedding the extension in C++ applications, authoring DuckDB C++ tests or utilities, and validating the FFI boundary between the Rust extension and DuckDB's C++ API.
- CityJSON data layout mirrors the CityParquet schema exposed by the extension. Inspect the column set through
DESCRIBEstatements or the SQL tests undertest/sql/to map values in C++.
YOU SHOULD REFERENCE THE DESIGN_DOC.md FILE FOR THE ARCHITECTURAL OVERVIEW.
- Run
makeonce to prepare the DuckDB build environment. To make use of cache, try to useGEN=ninja makeinstead. - Build debug binaries with
make test_debug. The loadable module lands inbuild/debug/extension/cityjson/. To use DuckDB with loadable extension, exec./build/release/duckdb. - For release artifacts run
make. - You only need CMake or other C++ build tools when producing auxiliary C++ binaries/tests.
- Include DuckDB's header (
duckdb.hpp) from the DuckDB submodule or your system installation. - Load the extension dynamically at runtime; the module requires DuckDB 1.4.1 (matching the bundled submodule).
- Example snippet:
#include "duckdb.hpp"
int main() {
duckdb::DuckDB db;
duckdb::Connection conn(db);
conn.Query("LOAD './build/debug/extension/cityjson/cityjson.duckdb_extension';");
auto result = conn.Query("SELECT * FROM read_cityjson('example.city.json');");
if (result->HasError()) {
throw std::runtime_error(result->GetError());
}
// Access result->GetValue(row, column) to inspect rows.
return 0;
}- Remember to start DuckDB with unsigned extensions enabled (
allow_unsigned_extensions=true) when required.
- The Rust extension uses Arrow-style column buffers under the hood. When exchanging data with C++, prefer DuckDB logical types and
Valuehelpers instead of manual buffer manipulation. - Geometry coordinates are stored as
LIST<STRUCT<x DOUBLE, y DOUBLE, z DOUBLE>>columns. Use the logical type metadata returned by DuckDB (e.g., viaPRAGMA table_info) when reproducing decode logic in C++. - Metadata such as transforms and CRS live in JSON columns; use DuckDB's JSON functions from C++ queries rather than custom parsers where possible.
- Primary tests live under
test/sql/*.test. Run them withmake testormake test_debugafter C++ changes that impact observable behaviour. - For C++ unit tests, link against DuckDB's testing utilities (located in the DuckDB submodule). Keep them in
test/cpp/if you introduce new suites. - Always verify sample CityJSON files across
read_*andwrite_*functions to ensure encoding parity with the Rust implementation.
- Batch operations through SQL queries rather than row-by-row API calls; DuckDB's vectorised execution is far more efficient.
- When marshalling large geometries from C++, avoid unnecessary copies. Use
duckdb::Appenderfor bulk inserts into staging tables. - Be mindful of transform metadata; reapply the same scale/offset semantics as the Rust code when emitting raw coordinates.
- Mirror the Rust code style for documentation and naming when adding C++ bridging code. Use
clang-formatwith DuckDB's style if you add new.cc/.hhfiles. - Keep FFI boundaries minimal. Expose new Rust capabilities through SQL functions first; only add direct C/C++ hooks when unavoidable.
- Document any new SQL surface area in both
README.mdand the relevant.testfiles so Rust and C++ contributors share the same contract.
- DuckDB C++ API: https://duckdb.org/docs/stable/clients/c/api
- CityJSON specification: https://www.cityjson.org/specs/2.0.1/
- DuckDB headers:
duckdb/src/include/duckdb.hpp,duckdb/src/include/duckdb/common/types.hpp