Repository: duckdb/duckdb (language: python)

Issue:
Add support for registering custom types through c api
This PR adds support for registering custom types and setting type aliases through the C-API.
The following new functions are added

```c
// Set the alias of a type
void duckdb_logical_type_set_alias(duckdb_logical_type type, const char *alias)

// Create a "custom type" struct used for registering custom types
duckdb_custom_type duckdb_create_custom_type();
// Set the base type of a custom type
void duckdb_custom_type_set_base_type(duckdb_custom_type type, duckdb_logical_type base_type)
// Set the name of a custom type
void duckdb_custom_type_set_name(duckdb_custom_type type, const char *name);
// Register a custom type
duckdb_state duckdb_register_custom_type(duckdb_connection con, duckdb_custom_type type);
// Destroy a custom type struct
void duckdb_destroy_custom_type(duckdb_custom_type *type);
```

One thing that im not sure about: when registering a custom type, the name should _usually_ be the same as the alias given to the base logical type, but im not sure if we want to enforce/handle this automatically behind the scenes in the c-api or if we leave it up to the user. Although we could always add that later (without changing the public facing API).


Discussion / hints:

Fix patch:
diff --git a/scripts/generate_c_api.py b/scripts/generate_c_api.py
index 4af2efd6fd40..68da22dc9c8c 100644
--- a/scripts/generate_c_api.py
+++ b/scripts/generate_c_api.py
@@ -73,6 +73,7 @@
     'arrow_interface',
     'threading_information',
     'streaming_result_interface',
+    'cast_functions',
 ]
 
 # The file that forms the base for the header generation
diff --git a/src/include/duckdb.h b/src/include/duckdb.h
index dcb5b1b737cf..0eb5fb05e8cb 100644
--- a/src/include/duckdb.h
+++ b/src/include/duckdb.h
@@ -219,6 +219,8 @@ typedef enum duckdb_error_type {
 	DUCKDB_ERROR_SEQUENCE = 41,
 	DUCKDB_INVALID_CONFIGURATION = 42
 } duckdb_error_type;
+//! An enum over DuckDB's different cast modes.
+typedef enum duckdb_cast_mode { DUCKDB_CAST_NORMAL = 0, DUCKDB_CAST_TRY = 1 } duckdb_cast_mode;
 
 //===--------------------------------------------------------------------===//
 // General type definitions
@@ -444,6 +446,12 @@ typedef struct _duckdb_logical_type {
 	void *internal_ptr;
 } * duckdb_logical_type;
 
+//! Holds extra information used when registering a custom logical type.
+//! Reserved for future use.
+typedef struct _duckdb_create_type_info {
+	void *internal_ptr;
+} * duckdb_create_type_info;
+
 //! Contains a data chunk from a duckdb_result.
 //! Must be destroyed with `duckdb_destroy_data_chunk`.
 typedef struct _duckdb_data_chunk {
@@ -555,6 +563,18 @@ typedef void (*duckdb_table_function_init_t)(duckdb_init_info info);
 //! The main function of the table function.
 typedef void (*duckdb_table_function_t)(duckdb_function_info info, duckdb_data_chunk output);
 
+//===--------------------------------------------------------------------===//
+// Cast types
+//===--------------------------------------------------------------------===//
+
+//! A cast function. Must be destroyed with `duckdb_destroy_cast_function`.
+typedef struct _duckdb_cast_function {
+	void *internal_ptr;
+} * duckdb_cast_function;
+
+typedef bool (*duckdb_cast_function_t)(duckdb_function_info info, idx_t count, duckdb_vector input,
+                                       duckdb_vector output);
+
 //===--------------------------------------------------------------------===//
 // Replacement scan types
 //===--------------------------------------------------------------------===//
@@ -2201,6 +2221,14 @@ The result must be destroyed with `duckdb_free`.
 */
 DUCKDB_API char *duckdb_logical_type_get_alias(duckdb_logical_type type);
 
+/*!
+Sets the alias of a duckdb_logical_type.
+
+* @param type The logical type
+* @param alias The alias to set
+*/
+DUCKDB_API void duckdb_logical_type_set_alias(duckdb_logical_type type, const char *alias);
+
 /*!
 Creates a LIST type from its child type.
 The return type must be destroyed with `duckdb_destroy_logical_type`.
@@ -2447,6 +2475,17 @@ Destroys the logical type and de-allocates all memory allocated for that type.
 */
 DUCKDB_API void duckdb_destroy_logical_type(duckdb_logical_type *type);
 
+/*!
+Registers a custom type within the given connection.
+The type must have an alias
+
+* @param con The connection to use
+* @param type The custom type to register
+* @return Whether or not the registration was successful.
+*/
+DUCKDB_API duckdb_state duckdb_register_logical_type(duckdb_connection con, duckdb_logical_type type,
+                                                     duckdb_create_type_info info);
+
 //===--------------------------------------------------------------------===//
 // Data Chunk Interface
 //===--------------------------------------------------------------------===//
@@ -3968,6 +4007,111 @@ It is not known beforehand how many chunks will be returned by this result.
 */
 DUCKDB_API duckdb_data_chunk duckdb_fetch_chunk(duckdb_result result);
 
+//===--------------------------------------------------------------------===//
+// Cast Functions
+//===--------------------------------------------------------------------===//
+
+/*!
+Creates a new cast function object.
+
+* @return The cast function object.
+*/
+DUCKDB_API duckdb_cast_function duckdb_create_cast_function();
+
+/*!
+Sets the source type of the cast function.
+
+* @param cast_function The cast function object.
+* @param source_type The source type to set.
+*/
+DUCKDB_API void duckdb_cast_function_set_source_type(duckdb_cast_function cast_function,
+                                                     duckdb_logical_type source_type);
+
+/*!
+Sets the target type of the cast function.
+
+* @param cast_function The cast function object.
+* @param target_type The target type to set.
+*/
+DUCKDB_API void duckdb_cast_function_set_target_type(duckdb_cast_function cast_function,
+                                                     duckdb_logical_type target_type);
+
+/*!
+Sets the "cost" of implicitly casting the source type to the target type using this function.
+
+* @param cast_function The cast function object.
+* @param cost The cost to set.
+*/
+DUCKDB_API void duckdb_cast_function_set_implicit_cast_cost(duckdb_cast_function cast_function, int64_t cost);
+
+/*!
+Sets the actual cast function to use.
+
+* @param cast_function The cast function object.
+* @param function The function to set.
+*/
+DUCKDB_API void duckdb_cast_function_set_function(duckdb_cast_function cast_function, duckdb_cast_function_t function);
+
+/*!
+Assigns extra information to the cast function that can be fetched during execution, etc.
+
+* @param extra_info The extra information
+* @param destroy The callback that will be called to destroy the extra information (if any)
+*/
+DUCKDB_API void duckdb_cast_function_set_extra_info(duckdb_cast_function cast_function, void *extra_info,
+                                                    duckdb_delete_callback_t destroy);
+
+/*!
+Retrieves the extra info of the function as set in `duckdb_cast_function_set_extra_info`.
+
+* @param info The info object.
+* @return The extra info.
+*/
+DUCKDB_API void *duckdb_cast_function_get_extra_info(duckdb_function_info info);
+
+/*!
+Get the cast execution mode from the given function info.
+
+* @param info The info object.
+* @return The cast mode.
+*/
+DUCKDB_API duckdb_cast_mode duckdb_cast_function_get_cast_mode(duckdb_function_info info);
+
+/*!
+Report that an error has occurred while executing the cast function.
+
+* @param info The info object.
+* @param error The error message.
+*/
+DUCKDB_API void duckdb_cast_function_set_error(duckdb_function_info info, const char *error);
+
+/*!
+Report that an error has occurred while executing the cast function, setting the corresponding output row to NULL.
+
+* @param info The info object.
+* @param error The error message.
+* @param row The index of the row within the output vector to set to NULL.
+* @param output The output vector.
+*/
+DUCKDB_API void duckdb_cast_function_set_row_error(duckdb_function_info info, const char *error, idx_t row,
+                                                   duckdb_vector output);
+
+/*!
+Registers a cast function within the given connection.
+
+* @param con The connection to use.
+* @param cast_function The cast function to register.
+* @return Whether or not the registration was successful.
+*/
+DUCKDB_API duckdb_state duckdb_register_cast_function(duckdb_connection con, duckdb_cast_function cast_function);
+
+/*!
+Destroys the cast function object.
+
+* @param cast_function The cast function object.
+*/
+DUCKDB_API void duckdb_destroy_cast_function(duckdb_cast_function *cast_function);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/include/duckdb/main/capi/extension_api.hpp b/src/include/duckdb/main/capi/extension_api.hpp
index 9dbd5695e978..58c44ff3cc61 100644
--- a/src/include/duckdb/main/capi/extension_api.hpp
+++ b/src/include/duckdb/main/capi/extension_api.hpp
@@ -351,6 +351,23 @@ typedef struct {
 	                                                 duckdb_delete_callback_t destroy);
 	void *(*duckdb_aggregate_function_get_extra_info)(duckdb_function_info info);
 	void (*duckdb_aggregate_function_set_error)(duckdb_function_info info, const char *error);
+	void (*duckdb_logical_type_set_alias)(duckdb_logical_type type, const char *alias);
+	duckdb_state (*duckdb_register_logical_type)(duckdb_connection con, duckdb_logical_type type,
+	                                             duckdb_create_type_info info);
+	duckdb_cast_function (*duckdb_create_cast_function)();
+	void (*duckdb_cast_function_set_source_type)(duckdb_cast_function cast_function, duckdb_logical_type source_type);
+	void (*duckdb_cast_function_set_target_type)(duckdb_cast_function cast_function, duckdb_logical_type target_type);
+	void (*duckdb_cast_function_set_implicit_cast_cost)(duckdb_cast_function cast_function, int64_t cost);
+	void (*duckdb_cast_function_set_function)(duckdb_cast_function cast_function, duckdb_cast_function_t function);
+	void (*duckdb_cast_function_set_extra_info)(duckdb_cast_function cast_function, void *extra_info,
+	                                            duckdb_delete_callback_t destroy);
+	void *(*duckdb_cast_function_get_extra_info)(duckdb_function_info info);
+	duckdb_cast_mode (*duckdb_cast_function_get_cast_mode)(duckdb_function_info info);
+	void (*duckdb_cast_function_set_error)(duckdb_function_info info, const char *error);
+	void (*duckdb_cast_function_set_row_error)(duckdb_function_info info, const char *error, idx_t row,
+	                                           duckdb_vector output);
+	duckdb_state (*duckdb_register_cast_function)(duckdb_connection con, duckdb_cast_function cast_function);
+	void (*duckdb_destroy_cast_function)(duckdb_cast_function *cast_function);
 } duckdb_ext_api_v0;
 
 //===--------------------------------------------------------------------===//
@@ -662,6 +679,20 @@ inline duckdb_ext_api_v0 CreateAPIv0() {
 	result.duckdb_aggregate_function_set_extra_info = duckdb_aggregate_function_set_extra_info;
 	result.duckdb_aggregate_function_get_extra_info = duckdb_aggregate_function_get_extra_info;
 	result.duckdb_aggregate_function_set_error = duckdb_aggregate_function_set_error;
+	result.duckdb_logical_type_set_alias = duckdb_logical_type_set_alias;
+	result.duckdb_register_logical_type = duckdb_register_logical_type;
+	result.duckdb_create_cast_function = duckdb_create_cast_function;
+	result.duckdb_cast_function_set_source_type = duckdb_cast_function_set_source_type;
+	result.duckdb_cast_function_set_target_type = duckdb_cast_function_set_target_type;
+	result.duckdb_cast_function_set_implicit_cast_cost = duckdb_cast_function_set_implicit_cast_cost;
+	result.duckdb_cast_function_set_function = duckdb_cast_function_set_function;
+	result.duckdb_cast_function_set_extra_info = duckdb_cast_function_set_extra_info;
+	result.duckdb_cast_function_get_extra_info = duckdb_cast_function_get_extra_info;
+	result.duckdb_cast_function_get_cast_mode = duckdb_cast_function_get_cast_mode;
+	result.duckdb_cast_function_set_error = duckdb_cast_function_set_error;
+	result.duckdb_cast_function_set_row_error = duckdb_cast_function_set_row_error;
+	result.duckdb_register_cast_function = duckdb_register_cast_function;
+	result.duckdb_destroy_cast_function = duckdb_destroy_cast_function;
 	return result;
 }
 
diff --git a/src/include/duckdb/main/capi/header_generation/apis/v0/dev/dev.json b/src/include/duckdb/main/capi/header_generation/apis/v0/dev/dev.json
index 3d84c89c2401..49ccc526409e 100644
--- a/src/include/duckdb/main/capi/header_generation/apis/v0/dev/dev.json
+++ b/src/include/duckdb/main/capi/header_generation/apis/v0/dev/dev.json
@@ -12,6 +12,20 @@
         "duckdb_aggregate_function_set_special_handling",
         "duckdb_aggregate_function_set_extra_info",
         "duckdb_aggregate_function_get_extra_info",
-        "duckdb_aggregate_function_set_error"
+        "duckdb_aggregate_function_set_error",
+        "duckdb_logical_type_set_alias",
+        "duckdb_register_logical_type",
+        "duckdb_create_cast_function", 
+        "duckdb_cast_function_set_source_type",
+        "duckdb_cast_function_set_target_type",
+        "duckdb_cast_function_set_implicit_cast_cost",
+        "duckdb_cast_function_set_function",
+        "duckdb_cast_function_set_extra_info",
+        "duckdb_cast_function_get_extra_info",
+        "duckdb_cast_function_get_cast_mode",
+        "duckdb_cast_function_set_error",
+        "duckdb_cast_function_set_row_error",
+        "duckdb_register_cast_function",
+        "duckdb_destroy_cast_function"
     ]
 }
diff --git a/src/include/duckdb/main/capi/header_generation/functions/cast_functions.json b/src/include/duckdb/main/capi/header_generation/functions/cast_functions.json
new file mode 100644
index 000000000000..816602f05352
--- /dev/null
+++ b/src/include/duckdb/main/capi/header_generation/functions/cast_functions.json
@@ -0,0 +1,249 @@
+{
+    "group": "cast_functions",
+    "deprecated": false,
+    "entries": [
+        {
+            "name": "duckdb_create_cast_function",
+            "return_type": "duckdb_cast_function",
+            "params": [],
+            "comment": {
+                "description": "Creates a new cast function object.\n\n",
+                "return_value": "The cast function object."
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_source_type",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_cast_function",
+                    "name": "cast_function"
+                },
+                {
+                    "type": "duckdb_logical_type",
+                    "name": "source_type"
+                }
+            ],
+            "comment": {
+                "description": "Sets the source type of the cast function.\n\n",
+                "param_comments": {
+                    "cast_function": "The cast function object.",
+                    "source_type": "The source type to set."
+                }
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_target_type",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_cast_function",
+                    "name": "cast_function"
+                },
+                {
+                    "type": "duckdb_logical_type",
+                    "name": "target_type"
+                }
+            ],
+            "comment": {
+                "description": "Sets the target type of the cast function.\n\n",
+                "param_comments": {
+                    "cast_function": "The cast function object.",
+                    "target_type": "The target type to set."
+                }
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_implicit_cast_cost",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_cast_function",
+                    "name": "cast_function"
+                },
+                {
+                    "type": "int64_t",
+                    "name": "cost"
+                }
+            ],
+            "comment": {
+                "description": "Sets the \"cost\" of implicitly casting the source type to the target type using this function.\n\n",
+                "param_comments": {
+                    "cast_function": "The cast function object.",
+                    "cost": "The cost to set."
+                }
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_function",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_cast_function",
+                    "name": "cast_function"
+                },
+                {
+                    "type": "duckdb_cast_function_t",
+                    "name": "function"
+                }
+            ],
+            "comment": {
+                "description": "Sets the actual cast function to use.\n\n",
+                "param_comments": {
+                    "cast_function": "The cast function object.",
+                    "function": "The function to set."
+                }
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_extra_info",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_cast_function",
+                    "name": "cast_function"
+                },
+                {
+                    "type": "void *",
+                    "name": "extra_info"
+                },
+                {
+                    "type": "duckdb_delete_callback_t",
+                    "name": "destroy"
+                }
+            ],
+            "comment": {
+                "description": "Assigns extra information to the cast function that can be fetched during execution, etc.\n\n",
+                "param_comments": {
+                    "scalar_function": "The cast function",
+                    "extra_info": "The extra information",
+                    "destroy": "The callback that will be called to destroy the extra information (if any)"
+                }
+            }
+        },
+        {
+            "name": "duckdb_cast_function_get_extra_info",
+            "return_type": "void *",
+            "params": [
+                {
+                    "type": "duckdb_function_info",
+                    "name": "info"
+                }
+            ],
+            "comment": {
+                "description": "Retrieves the extra info of the function as set in `duckdb_cast_function_set_extra_info`.\n\n",
+                "param_comments": {
+                    "info": "The info object."
+                },
+                "return_value": "The extra info."
+            }
+        },
+        {
+            "name": "duckdb_cast_function_get_cast_mode",
+            "return_type": "duckdb_cast_mode",
+            "params": [
+                {
+                    "type": "duckdb_function_info",
+                    "name": "info"
+                }
+            ],
+            "comment": {
+                "description": "Get the cast execution mode from the given function info.\n\n",
+                "param_comments": {
+                    "info": "The info object."
+                },
+                "return_value": "The cast mode."
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_error",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_function_info",
+                    "name": "info"
+                },
+                {
+                    "type": "const char *",
+                    "name": "error"
+                }
+            ],
+            "comment": {
+                "description": "Report that an error has occurred while executing the cast function.\n\n",
+                "param_comments": {
+                    "info": "The info object.",
+                    "error": "The error message."
+                }
+            }
+        },
+        {
+            "name": "duckdb_cast_function_set_row_error",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_function_info",
+                    "name": "info"
+                },
+                {
+                    "type": "const char *",
+                    "name": "error"
+                },
+                {
+                    "type": "idx_t",
+                    "name": "row"
+                },
+                {
+                    "type": "duckdb_vector",
+                    "name": "output"
+                }
+            ],
+            "comment": {
+                "description": "Report that an error has occurred while executing the cast function, setting the corresponding output row to NULL.\n\n",
+                "param_comments": {
+                    "info": "The info object.",
+                    "error": "The error message.",
+                    "row": "The index of the row within the output vector to set to NULL.",
+                    "output": "The output vector."
+                }
+            }
+        },
+        {
+            "name": "duckdb_register_cast_function",
+            "return_type": "duckdb_state",
+            "params": [
+                {
+                    "type": "duckdb_connection",
+                    "name": "con"
+                },
+                {
+                    "type": "duckdb_cast_function",
+                    "name": "cast_function"
+                }
+            ],
+            "comment": {
+                "description": "Registers a cast function within the given connection.\n\n",
+                "param_comments": {
+                    "con": "The connection to use.",
+                    "cast_function": "The cast function to register."
+                },
+                "return_value": "Whether or not the registration was successful."
+            }
+        },
+        {
+            "name": "duckdb_destroy_cast_function",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_cast_function *",
+                    "name": "cast_function"
+                }
+            ],
+            "comment": {
+                "description": "Destroys the cast function object.\n\n",
+                "param_comments": {
+                    "cast_function": "The cast function object."
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/include/duckdb/main/capi/header_generation/functions/logical_type_interface.json b/src/include/duckdb/main/capi/header_generation/functions/logical_type_interface.json
index cae203d0e268..6dccc81a54fa 100644
--- a/src/include/duckdb/main/capi/header_generation/functions/logical_type_interface.json
+++ b/src/include/duckdb/main/capi/header_generation/functions/logical_type_interface.json
@@ -36,6 +36,27 @@
                 "return_value": "The alias or `nullptr`"
             }
         },
+        {
+            "name": "duckdb_logical_type_set_alias",
+            "return_type": "void",
+            "params": [
+                {
+                    "type": "duckdb_logical_type",
+                    "name": "type"
+                },
+                {
+                    "type": "const char *",
+                    "name": "alias"
+                }
+            ],
+            "comment": {
+                "description": "Sets the alias of a duckdb_logical_type.\n\n",
+                "param_comments": {
+                    "type": "The logical type",
+                    "alias": "The alias to set"
+                }
+            }
+        },
         {
             "name": "duckdb_create_list_type",
             "return_type": "duckdb_logical_type",
@@ -541,6 +562,33 @@
                     "type": "The logical type to destroy."
                 }
             }
+        },
+        {
+            "name": "duckdb_register_logical_type",
+            "return_type": "duckdb_state",
+            "params": [
+                {
+                    "type": "duckdb_connection",
+                    "name": "con"
+                },
+                {
+                    "type": "duckdb_logical_type",
+                    "name": "type"
+                },
+                {
+                    "type": "duckdb_create_type_info",
+                    "name": "info"
+                }
+            ],
+            "comment": {
+                "description": "Registers a custom type within the given connection.\nThe type must have an alias\n\n",
+                "param_comments": {
+                    "con": "The connection to use",
+                    "type": "The custom type to register",
+                    "extra_info": "Optional extra information used when registering the custom type. Can be `NULL`."
+                },
+                "return_value": "Whether or not the registration was successful."
+            }
         }
     ]
 }
\ No newline at end of file
diff --git a/src/include/duckdb/main/capi/header_generation/header_base.hpp.template b/src/include/duckdb/main/capi/header_generation/header_base.hpp.template
index bae97e92bc73..19a514b899a3 100644
--- a/src/include/duckdb/main/capi/header_generation/header_base.hpp.template
+++ b/src/include/duckdb/main/capi/header_generation/header_base.hpp.template
@@ -213,6 +213,11 @@ typedef enum duckdb_error_type {
 	DUCKDB_ERROR_SEQUENCE = 41,
 	DUCKDB_INVALID_CONFIGURATION = 42
 } duckdb_error_type;
+//! An enum over DuckDB's different cast modes.
+typedef enum duckdb_cast_mode {
+	DUCKDB_CAST_NORMAL = 0,
+	DUCKDB_CAST_TRY = 1
+} duckdb_cast_mode;
 
 //===--------------------------------------------------------------------===//
 // General type definitions
@@ -438,6 +443,12 @@ typedef struct _duckdb_logical_type {
 	void *internal_ptr;
 } * duckdb_logical_type;
 
+//! Holds extra information used when registering a custom logical type.
+//! Reserved for future use.
+typedef struct _duckdb_create_type_info {
+	void *internal_ptr;
+} * duckdb_create_type_info;
+
 //! Contains a data chunk from a duckdb_result.
 //! Must be destroyed with `duckdb_destroy_data_chunk`.
 typedef struct _duckdb_data_chunk {
@@ -549,6 +560,17 @@ typedef void (*duckdb_table_function_init_t)(duckdb_init_info info);
 //! The main function of the table function.
 typedef void (*duckdb_table_function_t)(duckdb_function_info info, duckdb_data_chunk output);
 
+//===--------------------------------------------------------------------===//
+// Cast types
+//===--------------------------------------------------------------------===//
+
+//! A cast function. Must be destroyed with `duckdb_destroy_cast_function`.
+typedef struct _duckdb_cast_function {
+	void *internal_ptr;
+} * duckdb_cast_function;
+
+typedef bool (*duckdb_cast_function_t)(duckdb_function_info info, idx_t count, duckdb_vector input, duckdb_vector output);
+
 //===--------------------------------------------------------------------===//
 // Replacement scan types
 //===--------------------------------------------------------------------===//
diff --git a/src/include/duckdb_extension.h b/src/include/duckdb_extension.h
index dbf3ab07f1fd..cebf2e479813 100644
--- a/src/include/duckdb_extension.h
+++ b/src/include/duckdb_extension.h
@@ -413,6 +413,23 @@ typedef struct {
 	                                                 duckdb_delete_callback_t destroy);
 	void *(*duckdb_aggregate_function_get_extra_info)(duckdb_function_info info);
 	void (*duckdb_aggregate_function_set_error)(duckdb_function_info info, const char *error);
+	void (*duckdb_logical_type_set_alias)(duckdb_logical_type type, const char *alias);
+	duckdb_state (*duckdb_register_logical_type)(duckdb_connection con, duckdb_logical_type type,
+	                                             duckdb_create_type_info info);
+	duckdb_cast_function (*duckdb_create_cast_function)();
+	void (*duckdb_cast_function_set_source_type)(duckdb_cast_function cast_function, duckdb_logical_type source_type);
+	void (*duckdb_cast_function_set_target_type)(duckdb_cast_function cast_function, duckdb_logical_type target_type);
+	void (*duckdb_cast_function_set_implicit_cast_cost)(duckdb_cast_function cast_function, int64_t cost);
+	void (*duckdb_cast_function_set_function)(duckdb_cast_function cast_function, duckdb_cast_function_t function);
+	void (*duckdb_cast_function_set_extra_info)(duckdb_cast_function cast_function, void *extra_info,
+	                                            duckdb_delete_callback_t destroy);
+	void *(*duckdb_cast_function_get_extra_info)(duckdb_function_info info);
+	duckdb_cast_mode (*duckdb_cast_function_get_cast_mode)(duckdb_function_info info);
+	void (*duckdb_cast_function_set_error)(duckdb_function_info info, const char *error);
+	void (*duckdb_cast_function_set_row_error)(duckdb_function_info info, const char *error, idx_t row,
+	                                           duckdb_vector output);
+	duckdb_state (*duckdb_register_cast_function)(duckdb_connection con, duckdb_cast_function cast_function);
+	void (*duckdb_destroy_cast_function)(duckdb_cast_function *cast_function);
 #endif
 
 } duckdb_ext_api_v0;
@@ -750,6 +767,9 @@ typedef struct {
 #define duckdb_table_description_error   duckdb_ext_api.duckdb_table_description_error
 
 // Version dev
+#define duckdb_logical_type_set_alias duckdb_ext_api.duckdb_logical_type_set_alias
+#define duckdb_register_logical_type  duckdb_ext_api.duckdb_register_logical_type
+
 #define duckdb_create_aggregate_function               duckdb_ext_api.duckdb_create_aggregate_function
 #define duckdb_destroy_aggregate_function              duckdb_ext_api.duckdb_destroy_aggregate_function
 #define duckdb_aggregate_function_set_name             duckdb_ext_api.duckdb_aggregate_function_set_name
@@ -763,6 +783,19 @@ typedef struct {
 #define duckdb_aggregate_function_get_extra_info       duckdb_ext_api.duckdb_aggregate_function_get_extra_info
 #define duckdb_aggregate_function_set_error            duckdb_ext_api.duckdb_aggregate_function_set_error
 
+#define duckdb_create_cast_function                 duckdb_ext_api.duckdb_create_cast_function
+#define duckdb_cast_function_set_source_type        duckdb_ext_api.duckdb_cast_function_set_source_type
+#define duckdb_cast_function_set_target_type        duckdb_ext_api.duckdb_cast_function_set_target_type
+#define duckdb_cast_function_set_implicit_cast_cost duckdb_ext_api.duckdb_cast_function_set_implicit_cast_cost
+#define duckdb_cast_function_set_function           duckdb_ext_api.duckdb_cast_function_set_function
+#define duckdb_cast_function_set_extra_info         duckdb_ext_api.duckdb_cast_function_set_extra_info
+#define duckdb_cast_function_get_extra_info         duckdb_ext_api.duckdb_cast_function_get_extra_info
+#define duckdb_cast_function_get_cast_mode          duckdb_ext_api.duckdb_cast_function_get_cast_mode
+#define duckdb_cast_function_set_error              duckdb_ext_api.duckdb_cast_function_set_error
+#define duckdb_cast_function_set_row_error          duckdb_ext_api.duckdb_cast_function_set_row_error
+#define duckdb_register_cast_function               duckdb_ext_api.duckdb_register_cast_function
+#define duckdb_destroy_cast_function                duckdb_ext_api.duckdb_destroy_cast_function
+
 //===--------------------------------------------------------------------===//
 // Struct Global Macros
 //===--------------------------------------------------------------------===//
diff --git a/src/main/capi/CMakeLists.txt b/src/main/capi/CMakeLists.txt
index 82e0941234e7..23f5b399bc93 100644
--- a/src/main/capi/CMakeLists.txt
+++ b/src/main/capi/CMakeLists.txt
@@ -6,6 +6,7 @@ add_library_unity(
   aggregate_function-c.cpp
   appender-c.cpp
   arrow-c.cpp
+  cast_function-c.cpp
   config-c.cpp
   data_chunk-c.cpp
   datetime-c.cpp
diff --git a/src/main/capi/cast_function-c.cpp b/src/main/capi/cast_function-c.cpp
new file mode 100644
index 000000000000..39a5d90a706e
--- /dev/null
+++ b/src/main/capi/cast_function-c.cpp
@@ -0,0 +1,210 @@
+#include "duckdb/common/type_visitor.hpp"
+#include "duckdb/common/types.hpp"
+#include "duckdb/common/helper.hpp"
+#include "duckdb/common/operator/cast_operators.hpp"
+#include "duckdb/function/cast/cast_function_set.hpp"
+#include "duckdb/main/capi/capi_internal.hpp"
+
+namespace duckdb {
+struct CCastExecuteInfo {
+	CastParameters &parameters;
+	string error_message;
+
+	explicit CCastExecuteInfo(CastParameters &parameters) : parameters(parameters), error_message() {
+	}
+};
+
+struct CCastFunction {
+	unique_ptr<LogicalType> source_type;
+	unique_ptr<LogicalType> target_type;
+	int64_t implicit_cast_cost = -1;
+
+	duckdb_cast_function_t function;
+	duckdb_function_info extra_info = nullptr;
+	duckdb_delete_callback_t delete_callback = nullptr;
+};
+
+struct CCastFunctionUserData {
+
+	duckdb_function_info data_ptr = nullptr;
+	duckdb_delete_callback_t delete_callback = nullptr;
+
+	CCastFunctionUserData(duckdb_function_info data_ptr_p, duckdb_delete_callback_t delete_callback_p)
+	    : data_ptr(data_ptr_p), delete_callback(delete_callback_p) {
+	}
+
+	~CCastFunctionUserData() {
+		if (data_ptr && delete_callback) {
+			delete_callback(data_ptr);
+		}
+		data_ptr = nullptr;
+		delete_callback = nullptr;
+	}
+};
+
+struct CCastFunctionData final : public BoundCastData {
+	duckdb_cast_function_t function;
+	shared_ptr<CCastFunctionUserData> extra_info;
+
+	explicit CCastFunctionData(duckdb_cast_function_t function_p, shared_ptr<CCastFunctionUserData> extra_info_p)
+	    : function(function_p), extra_info(std::move(extra_info_p)) {
+	}
+
+	unique_ptr<BoundCastData> Copy() const override {
+		return make_uniq<CCastFunctionData>(function, extra_info);
+	}
+};
+
+static bool CAPICastFunction(Vector &input, Vector &output, idx_t count, CastParameters &parameters) {
+
+	const auto is_const = input.GetVectorType() == VectorType::CONSTANT_VECTOR;
+	input.Flatten(count);
+
+	CCastExecuteInfo exec_info(parameters);
+	const auto &data = parameters.cast_data->Cast<CCastFunctionData>();
+
+	auto c_input = reinterpret_cast<duckdb_vector>(&input);
+	auto c_output = reinterpret_cast<duckdb_vector>(&output);
+	auto c_info = reinterpret_cast<duckdb_function_info>(&exec_info);
+
+	const auto success = data.function(c_info, count, c_input, c_output);
+
+	if (!success) {
+		HandleCastError::AssignError(exec_info.error_message, parameters);
+	}
+
+	if (is_const && count == 1 && (success || !parameters.strict)) {
+		output.SetVectorType(VectorType::CONSTANT_VECTOR);
+	}
+
+	return success;
+}
+
+} // namespace duckdb
+
+duckdb_cast_function duckdb_create_cast_function() {
+	const auto function = new duckdb::CCastFunction();
+	return reinterpret_cast<duckdb_cast_function>(function);
+}
+
+void duckdb_cast_function_set_source_type(duckdb_cast_function cast_function, duckdb_logical_type source_type) {
+	if (!cast_function || !source_type) {
+		return;
+	}
+	const auto &logical_type = *(reinterpret_cast<duckdb::LogicalType *>(source_type));
+	auto &cast = *(reinterpret_cast<duckdb::CCastFunction *>(cast_function));
+	cast.source_type = duckdb::make_uniq<duckdb::LogicalType>(logical_type);
+}
+
+void duckdb_cast_function_set_target_type(duckdb_cast_function cast_function, duckdb_logical_type target_type) {
+	if (!cast_function || !target_type) {
+		return;
+	}
+	const auto &logical_type = *(reinterpret_cast<duckdb::LogicalType *>(target_type));
+	auto &cast = *(reinterpret_cast<duckdb::CCastFunction *>(cast_function));
+	cast.target_type = duckdb::make_uniq<duckdb::LogicalType>(logical_type);
+}
+
+void duckdb_cast_function_set_implicit_cast_cost(duckdb_cast_function cast_function, int64_t cost) {
+	if (!cast_function) {
+		return;
+	}
+	auto &custom_type = *(reinterpret_cast<duckdb::CCastFunction *>(cast_function));
+	custom_type.implicit_cast_cost = cost;
+}
+
+void duckdb_cast_function_set_function(duckdb_cast_function cast_function, duckdb_cast_function_t function) {
+	if (!cast_function || !function) {
+		return;
+	}
+	auto &cast = *(reinterpret_cast<duckdb::CCastFunction *>(cast_function));
+	cast.function = function;
+}
+
+duckdb_cast_mode duckdb_cast_function_get_cast_mode(duckdb_function_info info) {
+	const auto &cast_info = *reinterpret_cast<duckdb::CCastExecuteInfo *>(info);
+	return cast_info.parameters.error_message == nullptr ? DUCKDB_CAST_NORMAL : DUCKDB_CAST_TRY;
+}
+
+void duckdb_cast_function_set_error(duckdb_function_info info, const char *error) {
+	auto &cast_info = *reinterpret_cast<duckdb::CCastExecuteInfo *>(info);
+	cast_info.error_message = error;
+}
+
+void duckdb_cast_function_set_row_error(duckdb_function_info info, const char *error, idx_t row, duckdb_vector output) {
+	auto &cast_info = *reinterpret_cast<duckdb::CCastExecuteInfo *>(info);
+	cast_info.error_message = error;
+	if (!output) {
+		return;
+	}
+	auto &output_vector = *reinterpret_cast<duckdb::Vector *>(output);
+	duckdb::FlatVector::SetNull(output_vector, row, true);
+}
+
+void duckdb_cast_function_set_extra_info(duckdb_cast_function cast_function, void *extra_info,
+                                         duckdb_delete_callback_t destroy) {
+	if (!cast_function || !extra_info) {
+		return;
+	}
+	auto &cast = *reinterpret_cast<duckdb::CCastFunction *>(cast_function);
+	cast.extra_info = static_cast<duckdb_function_info>(extra_info);
+	cast.delete_callback = destroy;
+}
+
+void *duckdb_cast_function_get_extra_info(duckdb_function_info info) {
+	if (!info) {
+		return nullptr;
+	}
+	auto &cast_info = *reinterpret_cast<duckdb::CCastExecuteInfo *>(info);
+	auto &cast_data = cast_info.parameters.cast_data->Cast<duckdb::CCastFunctionData>();
+	return cast_data.extra_info->data_ptr;
+}
+
+duckdb_state duckdb_register_cast_function(duckdb_connection connection, duckdb_cast_function cast_function) {
+	if (!connection || !cast_function) {
+		return DuckDBError;
+	}
+	auto &cast = *reinterpret_cast<duckdb::CCastFunction *>(cast_function);
+	if (!cast.source_type || !cast.target_type || !cast.function) {
+		return DuckDBError;
+	}
+
+	const auto &source_type = *cast.source_type;
+	const auto &target_type = *cast.target_type;
+
+	if (duckdb::TypeVisitor::Contains(source_type, duckdb::LogicalTypeId::INVALID) ||
+	    duckdb::TypeVisitor::Contains(source_type, duckdb::LogicalTypeId::ANY)) {
+		return DuckDBError;
+	}
+
+	if (duckdb::TypeVisitor::Contains(target_type, duckdb::LogicalTypeId::INVALID) ||
+	    duckdb::TypeVisitor::Contains(target_type, duckdb::LogicalTypeId::ANY)) {
+		return DuckDBError;
+	}
+
+	try {
+		const auto con = reinterpret_cast<duckdb::Connection *>(connection);
+		con->context->RunFunctionInTransaction([&]() {
+			auto &config = duckdb::DBConfig::GetConfig(*con->context);
+			auto &casts = config.GetCastFunctions();
+
+			auto extra_info =
+			    duckdb::make_shared_ptr<duckdb::CCastFunctionUserData>(cast.extra_info, cast.delete_callback);
+			auto cast_data = duckdb::make_uniq<duckdb::CCastFunctionData>(cast.function, std::move(extra_info));
+			duckdb::BoundCastInfo cast_info(duckdb::CAPICastFunction, std::move(cast_data));
+			casts.RegisterCastFunction(source_type, target_type, std::move(cast_info), cast.implicit_cast_cost);
+		});
+	} catch (...) {
+		return DuckDBError;
+	}
+	return DuckDBSuccess;
+}
+
+void duckdb_destroy_cast_function(duckdb_cast_function *cast_function) {
+	if (!cast_function || !*cast_function) {
+		return;
+	}
+	const auto function = reinterpret_cast<duckdb::CCastFunction *>(*cast_function);
+	delete function;
+	*cast_function = nullptr;
+}
diff --git a/src/main/capi/logical_types-c.cpp b/src/main/capi/logical_types-c.cpp
index e2bb7744667b..6837c527636e 100644
--- a/src/main/capi/logical_types-c.cpp
+++ b/src/main/capi/logical_types-c.cpp
@@ -1,4 +1,16 @@
 #include "duckdb/main/capi/capi_internal.hpp"
+#include "duckdb/parser/parsed_data/create_type_info.hpp"
+#include "duckdb/common/type_visitor.hpp"
+#include "duckdb/common/helper.hpp"
+
+namespace duckdb {
+
+struct CCustomType {
+	unique_ptr<LogicalType> base_type;
+	string name;
+};
+
+} // namespace duckdb
 
 static bool AssertLogicalTypeId(duckdb_logical_type type, duckdb::LogicalTypeId type_id) {
 	if (!type) {
@@ -323,6 +335,11 @@ char *duckdb_logical_type_get_alias(duckdb_logical_type type) {
 	return logical_type.HasAlias() ? strdup(logical_type.GetAlias().c_str()) : nullptr;
 }
 
+void duckdb_logical_type_set_alias(duckdb_logical_type type, const char *alias) {
+	auto &logical_type = *(reinterpret_cast<duckdb::LogicalType *>(type));
+	logical_type.SetAlias(alias);
+}
+
 duckdb_logical_type duckdb_struct_type_child_type(duckdb_logical_type type, idx_t index) {
 	if (!AssertInternalType(type, duckdb::PhysicalType::STRUCT)) {
 		return nullptr;
@@ -334,3 +351,37 @@ duckdb_logical_type duckdb_struct_type_child_type(duckdb_logical_type type, idx_
 	return reinterpret_cast<duckdb_logical_type>(
 	    new duckdb::LogicalType(duckdb::StructType::GetChildType(logical_type, index)));
 }
+
+duckdb_state duckdb_register_logical_type(duckdb_connection connection, duckdb_logical_type type,
+                                          duckdb_create_type_info info) {
+	if (!connection || !type) {
+		return DuckDBError;
+	}
+
+	// Unused for now
+	(void)info;
+
+	const auto &base_type = *reinterpret_cast<duckdb::LogicalType *>(type);
+	if (!base_type.HasAlias()) {
+		return DuckDBError;
+	}
+
+	if (duckdb::TypeVisitor::Contains(base_type, duckdb::LogicalTypeId::INVALID) ||
+	    duckdb::TypeVisitor::Contains(base_type, duckdb::LogicalTypeId::ANY)) {
+		return DuckDBError;
+	}
+
+	try {
+		const auto con = reinterpret_cast<duckdb::Connection *>(connection);
+		con->context->RunFunctionInTransaction([&]() {
+			auto &catalog = duckdb::Catalog::GetSystemCatalog(*con->context);
+			duckdb::CreateTypeInfo info(base_type.GetAlias(), base_type);
+			info.temporary = true;
+			info.internal = true;
+			catalog.CreateType(*con->context, info);
+		});
+	} catch (...) {
+		return DuckDBError;
+	}
+	return DuckDBSuccess;
+}


Test patch:
diff --git a/test/api/capi/CMakeLists.txt b/test/api/capi/CMakeLists.txt
index bc62ffd47b82..5892ce5641f3 100644
--- a/test/api/capi/CMakeLists.txt
+++ b/test/api/capi/CMakeLists.txt
@@ -2,6 +2,7 @@ add_library_unity(
   test_sql_capi
   OBJECT
   capi_aggregate_functions.cpp
+  capi_custom_type.cpp
   capi_scalar_functions.cpp
   capi_table_functions.cpp
   test_capi.cpp
diff --git a/test/api/capi/capi_custom_type.cpp b/test/api/capi/capi_custom_type.cpp
new file mode 100644
index 000000000000..a47855aa793d
--- /dev/null
+++ b/test/api/capi/capi_custom_type.cpp
@@ -0,0 +1,260 @@
+#include "capi_tester.hpp"
+#include <cstdio>
+
+using namespace duckdb;
+using namespace std;
+
+static void CAPIRegisterCustomType(duckdb_connection connection, const char *name, duckdb_type duckdb_type,
+                                   duckdb_state expected_outcome) {
+	duckdb_state status;
+
+	auto base_type = duckdb_create_logical_type(duckdb_type);
+	duckdb_logical_type_set_alias(base_type, name);
+
+	status = duckdb_register_logical_type(connection, base_type, nullptr);
+	REQUIRE(status == expected_outcome);
+
+	duckdb_destroy_logical_type(&base_type);
+	duckdb_destroy_logical_type(&base_type);
+	duckdb_destroy_logical_type(nullptr);
+}
+
+static void Vec3DAddFunction(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) {
+	const auto count = duckdb_data_chunk_get_size(input);
+	const auto left_vector = duckdb_data_chunk_get_vector(input, 0);
+	const auto right_vector = duckdb_data_chunk_get_vector(input, 1);
+	const auto left_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(left_vector)));
+	const auto right_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(right_vector)));
+	const auto result_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(output)));
+
+	for (idx_t i = 0; i < count; i++) {
+		for (idx_t j = 0; j < 3; j++) {
+			const auto idx = i * 3 + j;
+			result_data[idx] = left_data[idx] + right_data[idx];
+		}
+	}
+}
+
+static bool Vec3DToVarcharCastFunction(duckdb_function_info info, idx_t count, duckdb_vector input,
+                                       duckdb_vector output) {
+	const auto input_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(input)));
+
+	for (idx_t i = 0; i < count; i++) {
+		const auto x = input_data[i * 3];
+		const auto y = input_data[i * 3 + 1];
+		const auto z = input_data[i * 3 + 2];
+		const auto res = StringUtil::Format("<%f, %f, %f>", x, y, z);
+		duckdb_vector_assign_string_element_len(output, i, res.c_str(), res.size());
+	}
+	return true;
+}
+
+static bool TryParseVec3D(char *str, idx_t len, float &x, float &y, float &z) {
+	char *end = str + len;
+	while (str < end && *str != '<') {
+		str++;
+	}
+	str++;
+	if (str >= end) {
+		return false;
+	}
+	x = std::strtof(str, &str);
+	if (str >= end || *str != ',') {
+		return false;
+	}
+	str++;
+	y = std::strtof(str, &str);
+	if (str >= end || *str != ',') {
+		return false;
+	}
+	str++;
+	z = std::strtof(str, &str);
+	if (str >= end || *str != '>') {
+		return false;
+	}
+	str++;
+	return str == end;
+}
+
+static bool Vec3DFromVarcharCastFunction(duckdb_function_info info, idx_t count, duckdb_vector input,
+                                         duckdb_vector output) {
+	const auto cast_mode = duckdb_cast_function_get_cast_mode(info);
+
+	// For testing purposes, check that we got the custom data
+	auto custom_data = reinterpret_cast<string *>(duckdb_cast_function_get_extra_info(info));
+	REQUIRE(*custom_data == "foobar");
+
+	const auto input_data = static_cast<duckdb_string_t *>(duckdb_vector_get_data(input));
+	const auto output_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(output)));
+
+	bool success = true;
+	for (idx_t i = 0; i < count; i++) {
+		auto x = 0.0f, y = 0.0f, z = 0.0f;
+		auto str = input_data[i];
+		auto str_len = duckdb_string_t_length(str);
+		char *str_ptr = duckdb_string_is_inlined(str) ? str.value.inlined.inlined : str.value.pointer.ptr;
+
+		// yes, sscanf is not safe, but this is just a test
+		if (TryParseVec3D(str_ptr, str_len, x, y, z)) {
+			// Success
+			output_data[i * 3] = x;
+			output_data[i * 3 + 1] = y;
+			output_data[i * 3 + 2] = z;
+		} else {
+			// Error
+			duckdb_cast_function_set_row_error(info, "Failed to parse VEC3D", i, output);
+			if (cast_mode == DUCKDB_CAST_TRY) {
+				// Try cast, continue with the next row
+				success = false;
+			} else {
+				// Strict cast, short-circuit and return false
+				return false;
+			}
+		}
+	}
+
+	return success;
+}
+
+TEST_CASE("Test Custom Type Registration", "[capi]") {
+	CAPITester tester;
+	duckdb::unique_ptr<CAPIResult> result;
+
+	REQUIRE(tester.OpenDatabase(nullptr));
+
+	// try to register a custom type with an invalid base type
+	CAPIRegisterCustomType(tester.connection, "NUMBER", DUCKDB_TYPE_INVALID, DuckDBError);
+	// try to register a custom type with a valid base type
+	CAPIRegisterCustomType(tester.connection, "NUMBER", DUCKDB_TYPE_INTEGER, DuckDBSuccess);
+	// try to register it again - this should be an error
+	CAPIRegisterCustomType(tester.connection, "NUMBER", DUCKDB_TYPE_INTEGER, DuckDBError);
+
+	// check that it is in the catalog
+	result = tester.Query("SELECT type_name FROM duckdb_types WHERE type_name = 'NUMBER'");
+	REQUIRE_NO_FAIL(*result);
+	REQUIRE(result->row_count() == 1);
+	REQUIRE(result->Fetch<string>(0, 0) == "NUMBER");
+}
+
+TEST_CASE("Test Custom Type Function", "[capi]") {
+	CAPITester tester;
+	duckdb::unique_ptr<CAPIResult> result;
+
+	REQUIRE(tester.OpenDatabase(nullptr));
+
+	// Register a custom type (VEC3D) that is between 0 and 1
+	auto element_type = duckdb_create_logical_type(DUCKDB_TYPE_FLOAT);
+	auto vector_type = duckdb_create_array_type(element_type, 3);
+	duckdb_logical_type_set_alias(vector_type, "VEC3D");
+
+	REQUIRE(duckdb_register_logical_type(tester.connection, vector_type, nullptr) == DuckDBSuccess);
+
+	// Register a scalar function that adds two vectors
+	auto function = duckdb_create_scalar_function();
+	duckdb_scalar_function_set_name(function, "vec3d_add");
+	duckdb_scalar_function_set_return_type(function, vector_type);
+	duckdb_scalar_function_add_parameter(function, vector_type);
+	duckdb_scalar_function_add_parameter(function, vector_type);
+	duckdb_scalar_function_set_function(function, Vec3DAddFunction);
+
+	REQUIRE(duckdb_register_scalar_function(tester.connection, function) == DuckDBSuccess);
+
+	// Also add a cast function to convert VEC3D to VARCHAR
+	auto varchar_type = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
+
+	auto to_varchar_cast_function = duckdb_create_cast_function();
+	duckdb_cast_function_set_implicit_cast_cost(to_varchar_cast_function, 0);
+	duckdb_cast_function_set_source_type(to_varchar_cast_function, vector_type);
+	duckdb_cast_function_set_target_type(to_varchar_cast_function, varchar_type);
+	duckdb_cast_function_set_function(to_varchar_cast_function, Vec3DToVarcharCastFunction);
+
+	REQUIRE(duckdb_register_cast_function(tester.connection, to_varchar_cast_function) == DuckDBSuccess);
+
+	auto from_varchar_cast_function = duckdb_create_cast_function();
+	duckdb_cast_function_set_implicit_cast_cost(from_varchar_cast_function, 0);
+	duckdb_cast_function_set_source_type(from_varchar_cast_function, varchar_type);
+	duckdb_cast_function_set_target_type(from_varchar_cast_function, vector_type);
+	duckdb_cast_function_set_function(from_varchar_cast_function, Vec3DFromVarcharCastFunction);
+
+	auto cast_custom_data = new string("foobar");
+	auto cast_custom_data_delete = [](void *data) {
+		delete reinterpret_cast<string *>(data);
+	};
+	duckdb_cast_function_set_extra_info(from_varchar_cast_function, cast_custom_data, cast_custom_data_delete);
+
+	REQUIRE(duckdb_register_cast_function(tester.connection, from_varchar_cast_function) == DuckDBSuccess);
+
+	// Cleanup the custom type and functions
+	duckdb_destroy_scalar_function(&function);
+	duckdb_destroy_cast_function(&to_varchar_cast_function);
+	duckdb_destroy_cast_function(&from_varchar_cast_function);
+	duckdb_destroy_logical_type(&varchar_type);
+	duckdb_destroy_logical_type(&element_type);
+	duckdb_destroy_logical_type(&vector_type);
+
+	// Ensure that we can free the casts multiple times without issue
+	duckdb_destroy_cast_function(&from_varchar_cast_function);
+	duckdb_destroy_cast_function(nullptr);
+
+	// Create a table with the custom type
+	REQUIRE_NO_FAIL(tester.Query("CREATE TABLE vec3d_table (a VEC3D)"));
+	REQUIRE_NO_FAIL(tester.Query("INSERT INTO vec3d_table VALUES ([1.0, 2.0, 3.0]::FLOAT[3])"));
+	REQUIRE_NO_FAIL(tester.Query("INSERT INTO vec3d_table VALUES ([4.0, 5.0, 6.0]::FLOAT[3])"));
+
+	// Query the table
+	result = tester.Query("SELECT vec3d_add(a, a) FROM vec3d_table");
+	REQUIRE_NO_FAIL(*result);
+	REQUIRE(result->row_count() == 2);
+
+	// Check that the result is correct
+	auto chunk = result->FetchChunk(0);
+	auto data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(chunk->GetVector(0))));
+	REQUIRE(data[0] == 2.0f);
+	REQUIRE(data[1] == 4.0f);
+	REQUIRE(data[2] == 6.0f);
+	REQUIRE(data[3] == 8.0f);
+	REQUIRE(data[4] == 10.0f);
+	REQUIRE(data[5] == 12.0f);
+
+	// But we cant execute the function with a non-VEC3D type
+	result = tester.Query("SELECT vec3d_add([0,0,0]::FLOAT[3], [1,1,1]::FLOAT[3])");
+	REQUIRE_FAIL(result);
+	REQUIRE(result->ErrorType() == DUCKDB_ERROR_BINDER);
+
+	// But we can cast the base type to the custom type
+	result = tester.Query("SELECT vec3d_add(CAST([0,0,0] AS VEC3D), CAST([1,1,1] AS VEC3D))");
+	REQUIRE_NO_FAIL(*result);
+	REQUIRE(result->row_count() == 1);
+
+	// Speaking of casts, let's test the VEC3D to VARCHAR cast
+	result = tester.Query("SELECT CAST(a AS VARCHAR) FROM vec3d_table");
+	REQUIRE_NO_FAIL(*result);
+	REQUIRE(result->row_count() == 2);
+	// Check that the result is correct
+	REQUIRE(result->Fetch<string>(0, 0) == "<1.000000, 2.000000, 3.000000>");
+	REQUIRE(result->Fetch<string>(0, 1) == "<4.000000, 5.000000, 6.000000>");
+
+	// Now cast from varchar to VEC3D
+	result = tester.Query("SELECT CAST('<1.0, 3.0, 4.0>' AS VEC3D)");
+	REQUIRE_NO_FAIL(*result);
+	REQUIRE(result->row_count() == 1);
+
+	// Check that the result is correct
+	chunk = result->FetchChunk(0);
+	data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(chunk->GetVector(0))));
+	REQUIRE(data[0] == 1.0f);
+	REQUIRE(data[1] == 3.0f);
+	REQUIRE(data[2] == 4.0f);
+
+	// Try a faulty cast
+	result = tester.Query("SELECT CAST('<1.0, 3.0, abc' AS VEC3D)");
+	REQUIRE_FAIL(result);
+	REQUIRE(result->ErrorType() == DUCKDB_ERROR_CONVERSION);
+	REQUIRE_THAT(result->ErrorMessage(), Catch::Matchers::StartsWith("Conversion Error: Failed to parse VEC3D"));
+
+	// Try a faulty cast with TRY_CAST
+	result = tester.Query("SELECT TRY_CAST('<1.0, 3.0, abc' AS FLOAT[3]) IS NULL");
+	REQUIRE_NO_FAIL(*result);
+	REQUIRE(result->row_count() == 1);
+	REQUIRE(result->Fetch<bool>(0, 0) == true);
+}