From b95c37dba6d10b996ea08293adc8a3988a735093 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sun, 19 Oct 2025 15:42:07 +0200 Subject: [PATCH 01/12] The base commit --- docs/README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/README.md b/docs/README.md index 4a72812..12c6299 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,21 +2,21 @@ The table below includes the information about all SQL functions exposed by Infera. -| # | Function | Return Type | Description | -|----|:-------------------------------------------------------------|:-----------------|:--------------------------------------------------------------------------------------------------------------------------------------------| -| 1 | `infera_load_model(name VARCHAR, path_or_url VARCHAR)` | `BOOLEAN` | Loads an ONNX model from a local file path or a remote URL and assigns it a unique name. Returns `true` on success. | -| 2 | `infera_unload_model(name VARCHAR)` | `BOOLEAN` | Unloads a model, freeing its associated resources. Returns `true` on success. | -| 3 | `infera_set_autoload_dir(path VARCHAR)` | `VARCHAR (JSON)` | Scans a directory for `.onnx` files, loads them automatically, and returns a JSON report of loaded models and any errors. | -| 4 | `infera_get_loaded_models()` | `VARCHAR (JSON)` | Returns a JSON array containing the names of all currently loaded models. | +| # | Function | Return Type | Description | +|----|:-------------------------------------------------------------|:-----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1 | `infera_load_model(name VARCHAR, path_or_url VARCHAR)` | `BOOLEAN` | Loads an ONNX model from a local file path or a remote URL and assigns it a unique name. Returns `true` on success. | +| 2 | `infera_unload_model(name VARCHAR)` | `BOOLEAN` | Unloads a model, freeing its associated resources. Returns `true` on success. | +| 3 | `infera_set_autoload_dir(path VARCHAR)` | `VARCHAR (JSON)` | Scans a directory for `.onnx` files, loads them automatically, and returns a JSON report of loaded models and any errors. | +| 4 | `infera_get_loaded_models()` | `VARCHAR (JSON)` | Returns a JSON array containing the names of all currently loaded models. | | 5 | `infera_get_model_info(name VARCHAR)` | `VARCHAR (JSON)` | Returns a JSON object with metadata about a specific loaded model (name, input/output shapes). If the model is not loaded, this function raises an error. | -| 6 | `infera_predict(name VARCHAR, features... FLOAT)` | `FLOAT` | Performs inference on a batch of data, returning a single float value for each input row. | -| 7 | `infera_predict_multi(name VARCHAR, features... FLOAT)` | `VARCHAR (JSON)` | Performs inference and returns all outputs as a JSON-encoded array. This is useful for models that produce multiple predictions per sample. | -| 8 | `infera_predict_multi_list(name VARCHAR, features... FLOAT)` | `LIST[FLOAT]` | Performs inference and returns all outputs as a typed list of floats. Useful for multi-output models without JSON parsing. | -| 9 | `infera_predict_from_blob(name VARCHAR, data BLOB)` | `LIST[FLOAT]` | Performs inference on raw `BLOB` data (for example, used for an image tensor), returning the result as a list of floats. | -| 10 | `infera_is_model_loaded(name VARCHAR)` | `BOOLEAN` | Returns `true` if the given model is currently loaded, otherwise `false`. | -| 11 | `infera_get_version()` | `VARCHAR (JSON)` | Returns a JSON object with version and build information for the Infera extension. | -| 12 | `infera_clear_cache()` | `BOOLEAN` | Clears the entire model cache directory, freeing up disk space. Returns `true` on success. | -| 13 | `infera_get_cache_info()` | `VARCHAR (JSON)` | Returns cache statistics including directory path, total size in bytes, file count, and configured size limit. | +| 6 | `infera_predict(name VARCHAR, features... FLOAT)` | `FLOAT` | Performs inference on a batch of data, returning a single float value for each input row. | +| 7 | `infera_predict_multi(name VARCHAR, features... FLOAT)` | `VARCHAR (JSON)` | Performs inference and returns all outputs as a JSON-encoded array. This is useful for models that produce multiple predictions per sample. | +| 8 | `infera_predict_multi_list(name VARCHAR, features... FLOAT)` | `LIST[FLOAT]` | Performs inference and returns all outputs as a typed list of floats. Useful for multi-output models without JSON parsing. | +| 9 | `infera_predict_from_blob(name VARCHAR, data BLOB)` | `LIST[FLOAT]` | Performs inference on raw `BLOB` data (for example, used for an image tensor), returning the result as a list of floats. | +| 10 | `infera_is_model_loaded(name VARCHAR)` | `BOOLEAN` | Returns `true` if the given model is currently loaded, otherwise `false`. | +| 11 | `infera_get_version()` | `VARCHAR (JSON)` | Returns a JSON object with version and build information for the Infera extension. | +| 12 | `infera_clear_cache()` | `BOOLEAN` | Clears the entire model cache directory, freeing up disk space. Returns `true` on success. | +| 13 | `infera_get_cache_info()` | `VARCHAR (JSON)` | Returns cache statistics including directory path, total size in bytes, file count, and configured size limit. | > [!NOTE] > The `features...` arguments accept `FLOAT` as well as values from `DOUBLE`, `INTEGER`, `BIGINT`, and `DECIMAL` From 004971e09c41689d033faeefe5ba1e32cd31053d Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Mon, 20 Oct 2025 08:18:46 +0200 Subject: [PATCH 02/12] Improve the issue template --- .github/ISSUE_TEMPLATE/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0086358..39ad9e9 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ -blank_issues_enabled: true +blank_issues_enabled: false +contact_links: + - name: Discussions + url: https://github.com/CogitatorTech/infera/discussions + about: Please ask and answer general questions here From 515956feeb29eadd520be5e3f62fc489b942f778 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Mon, 20 Oct 2025 23:26:34 +0200 Subject: [PATCH 03/12] Make Rust linter checks more strict --- Makefile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a556da8..d8dcebf 100644 --- a/Makefile +++ b/Makefile @@ -61,12 +61,17 @@ rust-coverage: ## Generate code coverage report for Infera crate .PHONY: rust-lint rust-lint: rust-format ## Run linter checks on Rust files @echo "Linting Rust files..." - @cargo clippy --manifest-path infera/Cargo.toml --features "tract" -- -D warnings + @cargo clippy --manifest-path infera/Cargo.toml --features "tract" -- -D warnings -D clippy::unwrap_used -D clippy::expect_used .PHONY: rust-fix-lint rust-fix-lint: ## Fix Rust linter warnings @echo "Fixing linter warnings..." - @cargo clippy --fix --allow-dirty --allow-staged --manifest-path infera/Cargo.toml --features "tract" -- -D warnings + @cargo clippy --fix --allow-dirty --allow-staged --manifest-path infera/Cargo.toml --features "tract" -- -D warnings -D clippy::unwrap_used -D clippy::expect_used + +.PHONY: rust-careful +careful: ## Run security checks on Rust code + @echo "Running security checks..." + @cargo careful .PHONY: rust-clean rust-clean: ## Clean Rust build artifacts @@ -110,7 +115,7 @@ install-deps: ## Set up development environment (for Debian-based systems) @echo "Setting up development environment..." @sudo apt-get install -y cmake clang-format snap python3-pip @sudo snap install rustup --classic - @cargo install cargo-tarpaulin cbindgen cargo-edit cargo-audit cargo-outdated + @cargo install cargo-tarpaulin cbindgen cargo-edit cargo-audit cargo-outdated cargo-careful @cd infera && cargo check --features "tract" @git submodule update --init --recursive @pip install --user --upgrade pip uv From 53137ff7b474e12c7b08bfe8c7eb452b0bbd75b4 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 14:30:54 +0200 Subject: [PATCH 04/12] Fix the failing linter checks --- .github/workflows/dist_pipeline.yml | 3 +++ infera/src/engine.rs | 9 ++++++++- infera/src/lib.rs | 11 ++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dist_pipeline.yml b/.github/workflows/dist_pipeline.yml index 91c0070..61c5068 100644 --- a/.github/workflows/dist_pipeline.yml +++ b/.github/workflows/dist_pipeline.yml @@ -1,6 +1,9 @@ name: Build Extension Binaries on: workflow_dispatch: + pull_request: + branches: + - main push: tags: - 'v*' diff --git a/infera/src/engine.rs b/infera/src/engine.rs index b16cd44..85946ef 100644 --- a/infera/src/engine.rs +++ b/infera/src/engine.rs @@ -213,7 +213,14 @@ pub(crate) fn run_inference_blob_impl( let blob_bytes = unsafe { std::slice::from_raw_parts(blob_data, blob_len) }; let float_vec: Vec = blob_bytes .chunks_exact(4) - .map(|chunk| f32::from_ne_bytes(chunk.try_into().unwrap())) + .map(|chunk| { + // SAFETY: chunks_exact(4) guarantees exactly 4 bytes, so this conversion cannot fail + let array: [u8; 4] = match chunk.try_into() { + Ok(arr) => arr, + Err(_) => [0u8; 4], // This branch is unreachable but satisfies clippy + }; + f32::from_ne_bytes(array) + }) .collect(); let expected_elements: usize = model .input_shape diff --git a/infera/src/lib.rs b/infera/src/lib.rs index 06233a7..ed0d2e2 100644 --- a/infera/src/lib.rs +++ b/infera/src/lib.rs @@ -248,7 +248,16 @@ pub extern "C" fn infera_get_loaded_models() -> *mut c_char { let models = model::MODELS.read(); let list: Vec = models.keys().cloned().collect(); let joined = serde_json::to_string(&list).unwrap_or_else(|_| "[]".to_string()); - CString::new(joined).unwrap().into_raw() + match CString::new(joined) { + Ok(cstr) => cstr.into_raw(), + Err(_) => { + // Fallback to empty JSON array if string contains null bytes + match CString::new("[]") { + Ok(cstr) => cstr.into_raw(), + Err(_) => std::ptr::null_mut(), // This should never happen with "[]" + } + } + } } /// Returns a JSON string with version and build information about the Infera library. From fd7a1d13c666849d4ace2232fe4a1da941ed2285 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 14:40:15 +0200 Subject: [PATCH 05/12] Increase the version to `0.3.0` --- infera/Cargo.toml | 2 +- infera/bindings/infera_extension.cpp | 2 +- vcpkg.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infera/Cargo.toml b/infera/Cargo.toml index e6b9a59..d7d89f1 100644 --- a/infera/Cargo.toml +++ b/infera/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "infera" -version = "0.2.0" +version = "0.3.0" edition = "2021" publish = false diff --git a/infera/bindings/infera_extension.cpp b/infera/bindings/infera_extension.cpp index 94f97ad..08018d1 100644 --- a/infera/bindings/infera_extension.cpp +++ b/infera/bindings/infera_extension.cpp @@ -527,7 +527,7 @@ static void LoadInternal(ExtensionLoader &loader) { void InferaExtension::Load(ExtensionLoader &loader) { LoadInternal(loader); } std::string InferaExtension::Name() { return "infera"; } -std::string InferaExtension::Version() const { return "v0.2.0"; } +std::string InferaExtension::Version() const { return "v0.3.0"; } } // namespace duckdb diff --git a/vcpkg.json b/vcpkg.json index 26c42b1..78923d4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "infera", - "version": "0.2.0", + "version": "0.3.0", "description": "Infera DuckDB Extension", "dependencies": [ { From c54950c0ba9cf6f86b60becb7926a9f214e30ab2 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 14:48:18 +0200 Subject: [PATCH 06/12] Add the instructions to install Infera from extensions' repo --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9de9846..da1aac1 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,21 @@ See the [ROADMAP.md](ROADMAP.md) for the list of implemented and planned feature ### Quickstart +#### Install from Community Extensions Repository + +You can install and load Infera from +the [DuckDB community extensions](https://duckdb.org/community_extensions/list_of_extensions) repository using the +following SQL commands in the DuckDB shell: + +```sql +install infera from community; +load infera; +``` + +#### Build from Source + +Alternatively, you can build Infera from source and use it with DuckDB by following these steps: + 1. Clone the repository and build the Infera extension from source: ```bash @@ -93,7 +108,7 @@ select infera_unload_model('linear_model'); -- 4. Check the Infera version select infera_get_version(); -```` +``` [![Simple Demo 1](https://asciinema.org/a/745806.svg)](https://asciinema.org/a/745806) @@ -101,12 +116,8 @@ select infera_get_version(); > After building from source, the Infera binary will be `build/release/extension/infera/infera.duckdb_extension`. > You can load it using the `load 'build/release/extension/infera/infera.duckdb_extension';` in the DuckDB shell. > Note that the extension binary will only work with the DuckDB version that it was built against. -> At the moment, Infera is not available as -> a [DuckDB community extension](https://duckdb.org/community_extensions/list_of_extensions). -> Nevertheless, you can still use Infera by building it from source yourself, or downloading pre-built binaries from -> the [releases page](https://github.com/CogitatorTech/infera/releases) for your platform. -> Please check the [this page](https://duckdb.org/docs/stable/extensions/installing_extensions.html) for more details on -> how to use extensions in DuckDB. +> You can download the pre-built binaries from the [releases page](https://github.com/CogitatorTech/infera/releases) for +> your platform. --- From 23a3ce34fbd6feb5196b7abe046f7f5905cbce91 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 14:51:06 +0200 Subject: [PATCH 07/12] WIP --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index da1aac1..2cc23de 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ See the [ROADMAP.md](ROADMAP.md) for the list of implemented and planned feature #### Install from Community Extensions Repository You can install and load Infera from -the [DuckDB community extensions](https://duckdb.org/community_extensions/list_of_extensions) repository using the +the [DuckDB community extensions](https://duckdb.org/community_extensions/list_of_extensions) repository by running the following SQL commands in the DuckDB shell: ```sql @@ -86,7 +86,17 @@ make release ./build/release/duckdb ``` -3. Run the following SQL commands in the shell to try Infera out: +> [!NOTE] +> After building from source, the Infera binary will be `build/release/extension/infera/infera.duckdb_extension`. +> You can load it using the `load 'build/release/extension/infera/infera.duckdb_extension';` in the DuckDB shell. +> Note that the extension binary will only work with the DuckDB version that it was built against. +> You can download the pre-built binaries from the [releases page](https://github.com/CogitatorTech/infera/releases) for +> your platform. + + +#### Trying Infera + +You can now try Infera out by running the following SQL commands in the DuckDB shell: ```sql -- Normally, we need to load the extension first, @@ -112,13 +122,6 @@ select infera_get_version(); [![Simple Demo 1](https://asciinema.org/a/745806.svg)](https://asciinema.org/a/745806) -> [!NOTE] -> After building from source, the Infera binary will be `build/release/extension/infera/infera.duckdb_extension`. -> You can load it using the `load 'build/release/extension/infera/infera.duckdb_extension';` in the DuckDB shell. -> Note that the extension binary will only work with the DuckDB version that it was built against. -> You can download the pre-built binaries from the [releases page](https://github.com/CogitatorTech/infera/releases) for -> your platform. - --- ### Documentation From a4d84c757f345880268ddf96e412ad42667c29fd Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 14:53:32 +0200 Subject: [PATCH 08/12] WIP --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 2cc23de..26d7230 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ load infera; #### Build from Source -Alternatively, you can build Infera from source and use it with DuckDB by following these steps: +Alternatively, you can build Infera from source and use it by following these steps: 1. Clone the repository and build the Infera extension from source: @@ -96,8 +96,6 @@ make release #### Trying Infera -You can now try Infera out by running the following SQL commands in the DuckDB shell: - ```sql -- Normally, we need to load the extension first, -- but the `duckdb` binary that we built in the previous step From a0d6f0346215e666d97a72f5f97acf1109ef6af3 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 15:10:17 +0200 Subject: [PATCH 09/12] WIP --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 26d7230..35de042 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ See the [ROADMAP.md](ROADMAP.md) for the list of implemented and planned feature #### Install from Community Extensions Repository You can install and load Infera from -the [DuckDB community extensions](https://duckdb.org/community_extensions/list_of_extensions) repository by running the +the [DuckDB community extensions](https://duckdb.org/community_extensions/extensions/infera) repository by running the following SQL commands in the DuckDB shell: ```sql @@ -97,10 +97,9 @@ make release #### Trying Infera ```sql --- Normally, we need to load the extension first, --- but the `duckdb` binary that we built in the previous step --- already has Infera statically linked to it. --- So, we don't need to load the extension explicitly. +-- 0. Install and load Infera (skip this step if you built from source and ran `./build/release/duckdb`) +install infera from community; +load infera; -- 1. Load a simple linear model from a remote URL select infera_load_model('linear_model', From 45b9c9ef43a46c982b38a4587f7037b7ceadd24b Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 18:11:40 +0200 Subject: [PATCH 10/12] WIP --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35de042..a65cc02 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,8 @@ make release #### Trying Infera ```sql --- 0. Install and load Infera (skip this step if you built from source and ran `./build/release/duckdb`) +-- 0. Install and load Infera +-- Skip this step if you built from source and ran `./build/release/duckdb` install infera from community; load infera; From b67d0aa25362f802b865707d79de07c02ba214be Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 18:14:59 +0200 Subject: [PATCH 11/12] WIP --- infera/src/config.rs | 1 - infera/src/engine.rs | 1 - infera/src/error.rs | 1 - infera/src/ffi_utils.rs | 1 - infera/src/http.rs | 1 - infera/src/lib.rs | 1 - infera/src/model.rs | 1 - 7 files changed, 7 deletions(-) diff --git a/infera/src/config.rs b/infera/src/config.rs index 28ef98e..b336e53 100644 --- a/infera/src/config.rs +++ b/infera/src/config.rs @@ -1,4 +1,3 @@ -// src/config.rs // Centralized configuration management for Infera use once_cell::sync::Lazy; diff --git a/infera/src/engine.rs b/infera/src/engine.rs index 85946ef..564cf4f 100644 --- a/infera/src/engine.rs +++ b/infera/src/engine.rs @@ -1,4 +1,3 @@ -// src/engine.rs // Contains the core ONNX inference logic using the Tract library. use crate::error::InferaError; diff --git a/infera/src/error.rs b/infera/src/error.rs index 84a0832..9225424 100644 --- a/infera/src/error.rs +++ b/infera/src/error.rs @@ -1,4 +1,3 @@ -// src/error.rs // Contains the InferaError enum and thread-local error handling logic. use std::cell::RefCell; diff --git a/infera/src/ffi_utils.rs b/infera/src/ffi_utils.rs index 8ee00b9..6479cf3 100644 --- a/infera/src/ffi_utils.rs +++ b/infera/src/ffi_utils.rs @@ -1,4 +1,3 @@ -// src/ffi_utils.rs // Contains C-compatible structs and memory management functions for the FFI boundary. use std::ffi::{c_char, CString}; diff --git a/infera/src/http.rs b/infera/src/http.rs index 98a93c5..d508551 100644 --- a/infera/src/http.rs +++ b/infera/src/http.rs @@ -1,4 +1,3 @@ -// src/http.rs // Handles downloading and caching of remote models. use crate::config::{LogLevel, CONFIG}; diff --git a/infera/src/lib.rs b/infera/src/lib.rs index ed0d2e2..aca7de8 100644 --- a/infera/src/lib.rs +++ b/infera/src/lib.rs @@ -1,4 +1,3 @@ -// src/lib.rs // The public C API layer and module declarations. use serde_json::json; diff --git a/infera/src/model.rs b/infera/src/model.rs index 49f6180..956454e 100644 --- a/infera/src/model.rs +++ b/infera/src/model.rs @@ -1,4 +1,3 @@ -// src/model.rs // Defines the internal representation of a model and the global model store. use once_cell::sync::Lazy; From b21a9aca0deb1c5b92f3ecd2f3f27922bc952bd3 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 23 Oct 2025 18:23:04 +0200 Subject: [PATCH 12/12] WIP --- infera/src/engine.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/infera/src/engine.rs b/infera/src/engine.rs index 564cf4f..2236a66 100644 --- a/infera/src/engine.rs +++ b/infera/src/engine.rs @@ -214,10 +214,7 @@ pub(crate) fn run_inference_blob_impl( .chunks_exact(4) .map(|chunk| { // SAFETY: chunks_exact(4) guarantees exactly 4 bytes, so this conversion cannot fail - let array: [u8; 4] = match chunk.try_into() { - Ok(arr) => arr, - Err(_) => [0u8; 4], // This branch is unreachable but satisfies clippy - }; + let array: [u8; 4] = chunk.try_into().unwrap_or_default(); f32::from_ne_bytes(array) }) .collect();