Skip to content

Commit

Permalink
adding some moar
Browse files Browse the repository at this point in the history
  • Loading branch information
samansmink committed Nov 12, 2024
1 parent db4569a commit 4891496
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 46 deletions.
18 changes: 11 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
cmake_minimum_required(VERSION 3.5...3.29)
cmake_minimum_required(VERSION 2.8)

set(CMAKE_CXX_STANDARD "11" CACHE STRING "C++ standard to enforce")

###
# Configuration
###
if(NOT DEFINED MINIMUM_DUCKDB_VERSION_MAJOR OR
NOT DEFINED MINIMUM_DUCKDB_VERSION_MINOR OR
NOT DEFINED MINIMUM_DUCKDB_VERSION_PATCH)
message(FATAL_ERROR "DuckDB minimum version is required")

Check failure on line 11 in CMakeLists.txt

View workflow job for this annotation

GitHub Actions / Build extension binaries / Windows (windows_amd64, x64-windows-static-md)

DuckDB minimum version is required

Check failure on line 11 in CMakeLists.txt

View workflow job for this annotation

GitHub Actions / Build extension binaries / MacOS (osx_arm64, arm64, arm64-osx)

DuckDB minimum version is required
endif()

if(NOT DEFINED EXTENSION_NAME)
message(FATAL_ERROR "DuckDB extension name is required")
endif()

project(${EXTENSION_NAME})

#message(FATAL_ERROR "-DDUCKDB_EXTENSION_API_VERSION_MAJOR=${MINIMUM_DUCKDB_VERSION_MINOR}")
add_definitions(-DDUCKDB_EXTENSION_API_VERSION_MAJOR=${MINIMUM_DUCKDB_VERSION_MAJOR})
add_definitions(-DDUCKDB_EXTENSION_API_VERSION_MINOR=${MINIMUM_DUCKDB_VERSION_MINOR})
add_definitions(-DDUCKDB_EXTENSION_API_VERSION_PATCH=${MINIMUM_DUCKDB_VERSION_PATCH})
add_definitions(-DDUCKDB_EXTENSION_NAME=${EXTENSION_NAME})

###
# Build
###
project(${EXTENSION_NAME})

set(EXTENSION_SOURCES
src/add_numbers.cpp
src/capi_quack.cpp
)

add_library(${EXTENSION_NAME} SHARED ${EXTENSION_SOURCES})
target_include_directories(${EXTENSION_NAME} PRIVATE src/include)
target_include_directories(${EXTENSION_NAME} PRIVATE src/include)
target_include_directories(${EXTENSION_NAME} PRIVATE duckdb_capi)
33 changes: 3 additions & 30 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,18 @@

PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

# Main extension configuration
EXTENSION_NAME=capi_quack
MINIMUM_DUCKDB_VERSION_MAJOR=0
MINIMUM_DUCKDB_VERSION_MINOR=0
MINIMUM_DUCKDB_VERSION_PATCH=1

MINIMUM_DUCKDB_VERSION=v$(MINIMUM_DUCKDB_VERSION_MAJOR).$(MINIMUM_DUCKDB_VERSION_MINOR).$(MINIMUM_DUCKDB_VERSION_PATCH)

CMAKE_VERSION_PARAMS = -DEXTENSION_NAME=$(EXTENSION_NAME)
CMAKE_VERSION_PARAMS += -DMINIMUM_DUCKDB_VERSION_MAJOR=$(MINIMUM_DUCKDB_VERSION_MAJOR)
CMAKE_VERSION_PARAMS += -DMINIMUM_DUCKDB_VERSION_MINOR=$(MINIMUM_DUCKDB_VERSION_MINOR)
CMAKE_VERSION_PARAMS += -DMINIMUM_DUCKDB_VERSION_PATCH=$(MINIMUM_DUCKDB_VERSION_PATCH)

all: configure debug

# Include makefiles from DuckDB
include extension-ci-tools/makefiles/duckdb_extension_c_api.Makefile

############################################### TODO MOVE TO MAKEFILE ###############################################

build_extension_library_debug: check_configure
mkdir -p ./cmake_build/debug && \
cd cmake_build/debug && \
cmake $(CMAKE_VERSION_PARAMS) -DCMAKE_BUILD_TYPE=Debug ../.. && \
cmake --build . --config Debug
$(PYTHON_VENV_BIN) -c "from pathlib import Path;Path('./build/debug/extension/$(EXTENSION_NAME)').mkdir(parents=True, exist_ok=True)"
$(PYTHON_VENV_BIN) -c "import shutil;shutil.copyfile('cmake_build/debug/$(EXTENSION_LIB_FILENAME)', 'build/debug/$(EXTENSION_LIB_FILENAME)')"

build_extension_library_release: check_configure
mkdir -p ./cmake_build/release && \
cd cmake_build/release && \
cmake $(CMAKE_VERSION_PARAMS) -DCMAKE_BUILD_TYPE=Release ../.. && \
cmake --build . --config Release
$(PYTHON_VENV_BIN) -c "from pathlib import Path;Path('./build/release/extension/$(EXTENSION_NAME)').mkdir(parents=True, exist_ok=True)"
$(PYTHON_VENV_BIN) -c "import shutil;shutil.copyfile('cmake_build/release/$(EXTENSION_LIB_FILENAME)', 'build/release/$(EXTENSION_LIB_FILENAME)')"

clean_c:
rm -rf cmake_build

###############################################
include ./duckdb_extension_c.Makefile

configure: venv platform extension_version

Expand All @@ -51,5 +24,5 @@ test: test_debug
test_debug: test_extension_debug
test_release: test_extension_release

clean: clean_build clean_c
clean: clean_build clean_cmake
clean_all: clean_configure clean
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# DuckDB C/C++ extension template
This is an **experimental** template for C or C++ based extensions based on the C Extension API of DuckDB. Note that this
is a different template from

Features:
- No DuckDB build required
- CI/CD chain preconfigured
- (Coming soon) Works with community extensions

## Cloning
Clone the repo with submodules

```shell
git clone --recurse-submodules <repo>
```

## Dependencies
In principle, compiling this template only requires a C/C++ toolchain. However, this template relies on some additional
tooling to make life a little easier and to be able to share CI/CD infrastructure with extension templates for other languages:

- Python3
- Python3-venv
- [Make](https://www.gnu.org/software/make)
- CMake
- Git

Installing these dependencies will vary per platform:
- For Linux, these come generally pre-installed or are available through the distro-specific package manager.
- For MacOS, [homebrew](https://formulae.brew.sh/).
- For Windows, [chocolatey](https://community.chocolatey.org/).

## Building
After installing the dependencies, building is a two-step process. Firstly run:
```shell
make configure
```
This will ensure a Python venv is set up with DuckDB and DuckDB's test runner installed. Additionally, depending on configuration,
DuckDB will be used to determine the correct platform for which you are compiling.

Then, to build the extension run:
```shell
make debug
```
This delegates the build process to cargo, which will produce a shared library in `target/debug/<shared_lib_name>`. After this step,
a script is run to transform the shared library into a loadable extension by appending a binary footer. The resulting extension is written
to the `build/debug` directory.

To create optimized release binaries, simply run `make release` instead.

## Testing
This extension uses the DuckDB Python client for testing. This should be automatically installed in the `make configure` step.
The tests themselves are written in the SQLLogicTest format, just like most of DuckDB's tests. A sample test can be found in
`test/sql/<extension_name>.test`. To run the tests using the *debug* build:

```shell
make test_debug
```

or for the *release* build:
```shell
make test_release
```

### Version switching
Testing with different DuckDB versions is really simple:

First, run
```
make clean_all
```
to ensure the previous `make configure` step is deleted.

Then, run
```
DUCKDB_TEST_VERSION=v1.1.2 make configure
```
to select a different duckdb version to test with

Finally, build and test with
```
make debug
make test_debug
```

### Known issues
This is a bit of a footgun, but the extensions produced by this template may (or may not) be broken on windows on python3.11
with the following error on extension load:
```shell
IO Error: Extension '<name>.duckdb_extension' could not be loaded: The specified module could not be found
```
This was resolved by using python 3.12
13 changes: 13 additions & 0 deletions duckdb_capi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# DuckDB C API Headers
This directory contains the C API headers of DuckDB. These headers should be of the same
DuckDB version as is specified in `Makefile`. Note that these headers can be updated automatically
to match the `MINIMUM_DUCKDB_VERSION` makefile variable by running (assuming the default makefile setup):

```shell
make update_duckdb_headers
```

Of course manually updating the headers is also fine, just make sure that the headers are always from the same
DuckDB version and that they are not from a later release of DuckDB than is specified in the `MINIMUM_DUCKDB_VERSION`
build variable. Using headers from an older version than `MINIMUM_DUCKDB_VERSION` is allowed, but you probably don't want
that.
17 changes: 17 additions & 0 deletions src/include/duckdb.h → duckdb_capi/duckdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -2258,6 +2258,23 @@ Creates a value representing a NULL value.
*/
DUCKDB_API duckdb_value duckdb_create_null_value();

/*!
Returns the number of elements in a LIST value.
* @param value The LIST value.
* @return The number of elements in the list.
*/
DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value);

/*!
Returns the LIST child at index as a duckdb_value.
* @param value The LIST value.
* @param index The index of the child.
* @return The child as a duckdb_value.
*/
DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index);

//===--------------------------------------------------------------------===//
// Logical Type Interface
//===--------------------------------------------------------------------===//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ typedef struct {
duckdb_logical_type (*duckdb_param_logical_type)(duckdb_prepared_statement prepared_statement, idx_t param_idx);
bool (*duckdb_is_null_value)(duckdb_value value);
duckdb_value (*duckdb_create_null_value)();
idx_t (*duckdb_get_list_size)(duckdb_value value);
duckdb_value (*duckdb_get_list_child)(duckdb_value value, idx_t index);
#endif

} duckdb_ext_api_v0;
Expand Down Expand Up @@ -883,6 +885,8 @@ typedef struct {
#define duckdb_param_logical_type duckdb_ext_api.duckdb_param_logical_type
#define duckdb_is_null_value duckdb_ext_api.duckdb_is_null_value
#define duckdb_create_null_value duckdb_ext_api.duckdb_create_null_value
#define duckdb_get_list_size duckdb_ext_api.duckdb_get_list_size
#define duckdb_get_list_child duckdb_ext_api.duckdb_get_list_child
#define duckdb_appender_create_ext duckdb_ext_api.duckdb_appender_create_ext
#define duckdb_table_description_create_ext duckdb_ext_api.duckdb_table_description_create_ext
#define duckdb_table_description_get_column_name duckdb_ext_api.duckdb_table_description_get_column_name
Expand Down
52 changes: 52 additions & 0 deletions duckdb_extension_c.Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Reusable makefile for the C/C++ extensions targeting the C extension API
#
# Inputs
# EXTENSION_NAME : name of the extension (lower case)
# EXTENSION_LIB_FILENAME : the library name that is produced by the build
# MINIMUM_DUCKDB_VERSION : full version tag (including v)
# MINIMUM_DUCKDB_VERSION_MAJOR : major version
# MINIMUM_DUCKDB_VERSION_MINOR : minor version
# MINIMUM_DUCKDB_VERSION_PATCH : patch version
# CMAKE_EXTRA_BUILD_FLAGS : additional CMake flags to pass


.PHONY: build_extension_library_debug build_extension_library_release update_duckdb_headers

# Create build params to pass name and version
CMAKE_VERSION_PARAMS = -DEXTENSION_NAME=$(EXTENSION_NAME)
CMAKE_VERSION_PARAMS += -DMINIMUM_DUCKDB_VERSION=$(MINIMUM_DUCKDB_VERSION_MAJOR)
CMAKE_VERSION_PARAMS += -DMINIMUM_DUCKDB_VERSION_MINOR=$(MINIMUM_DUCKDB_VERSION_MINOR)
CMAKE_VERSION_PARAMS += -DMINIMUM_DUCKDB_VERSION_PATCH=$(MINIMUM_DUCKDB_VERSION_PATCH)

CMAKE_BUILD_FLAGS = $(CMAKE_VERSION_PARAMS) $(CMAKE_EXTRA_BUILD_FLAGS)

#############################################
### Rust Build targets
#############################################

build_extension_library_debug: check_configure
cmake $(CMAKE_BUILD_FLAGS) -DCMAKE_BUILD_TYPE=Debug -S $(PROJ_DIR) -B cmake_build/debug
cmake --build cmake_build/debug --config Debug
$(PYTHON_VENV_BIN) -c "from pathlib import Path;Path('./build/debug/extension/$(EXTENSION_NAME)').mkdir(parents=True, exist_ok=True)"
$(PYTHON_VENV_BIN) -c "import shutil;shutil.copyfile('cmake_build/debug/$(EXTENSION_LIB_FILENAME)', 'build/debug/$(EXTENSION_LIB_FILENAME)')"

build_extension_library_release: check_configure
cmake $(CMAKE_BUILD_FLAGS) -DCMAKE_BUILD_TYPE=Release -S $(PROJ_DIR) -B cmake_build/release
cmake --build cmake_build/release --config Release
$(PYTHON_VENV_BIN) -c "from pathlib import Path;Path('./build/release/extension/$(EXTENSION_NAME)').mkdir(parents=True, exist_ok=True)"
$(PYTHON_VENV_BIN) -c "import shutil;shutil.copyfile('cmake_build/release/$(EXTENSION_LIB_FILENAME)', 'build/release/$(EXTENSION_LIB_FILENAME)')"

#############################################
### Misc
#############################################
# TODO: switch this to use the $(MINIMUM_DUCKDB_VERSION) after v1.2.0 is released
BASE_HEADER_URL=https://raw.githubusercontent.com/duckdb/duckdb/refs/heads/main/src/include
DUCKDB_C_HEADER_URL=$(BASE_HEADER_URL)/duckdb.h
DUCKDB_C_EXTENSION_HEADER_URL=$(BASE_HEADER_URL)/duckdb_extension.h

update_duckdb_headers:
$(PYTHON_VENV_BIN) -c "import urllib.request;urllib.request.urlretrieve('$(DUCKDB_C_HEADER_URL)', 'duckdb_capi/duckdb.h')"
$(PYTHON_VENV_BIN) -c "import urllib.request;urllib.request.urlretrieve('$(DUCKDB_C_EXTENSION_HEADER_URL)', 'duckdb_capi/duckdb_extension.h')"

clean_cmake:
rm -rf cmake_build
4 changes: 2 additions & 2 deletions src/add_numbers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static void AddNumbersTogether(duckdb_function_info info, duckdb_data_chunk inpu
} else {
// no NULL values - iterate and do the operation directly
for (idx_t row = 0; row < input_size; row++) {
result_data[row] = a_data[row] + b_data[row];
result_data[row] = a_data[row] * b_data[row];
}
}
}
Expand All @@ -43,7 +43,7 @@ static void AddNumbersTogether(duckdb_function_info info, duckdb_data_chunk inpu
void RegisterAddNumbersFunction(duckdb_connection connection) {
// create a scalar function
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, "add_numbers_together");
duckdb_scalar_function_set_name(function, "multiply_numbers_together");

// add a two bigint parameters
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
Expand Down
12 changes: 5 additions & 7 deletions test/sql/capi_quack.test
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# name: test/sql/capi_quack.test
# description: test capi_quack extension
# group: [quack]
# group: [capi_quack]

# Before we load the extension, this will fail
statement error
SELECT capi_quack('Sam');
SELECT multiply_numbers_together(1,2);
----
Catalog Error: Scalar Function with name capi_quack does not exist!
Catalog Error: Scalar Function with name multiply_numbers_together does not exist!

# Require statement will ensure the extension is loaded from now on
require capi_quack

require icu

# Confirm the extension works
query I
SELECT * from capi_quack('Sam');
SELECT multiply_numbers_together(5,10)
----
Capi Quack Sam 🐥
50

0 comments on commit 4891496

Please sign in to comment.