{"id":15285398,"url":"https://github.com/pluots/sql-udf","last_synced_at":"2025-10-12T07:25:44.927Z","repository":{"id":62445069,"uuid":"531423709","full_name":"pluots/sql-udf","owner":"pluots","description":"A wrapper for writing MariaDB/MySQL user defined functions in Rust","archived":false,"fork":false,"pushed_at":"2024-05-08T02:05:49.000Z","size":323,"stargazers_count":21,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-08T19:52:39.207Z","etag":null,"topics":["mariadb","mysql","rust","udf","user-defined-functions"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pluots.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.Apache-2.0","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-01T08:10:29.000Z","updated_at":"2025-01-26T06:50:20.000Z","dependencies_parsed_at":"2024-05-07T10:55:58.880Z","dependency_job_id":null,"html_url":"https://github.com/pluots/sql-udf","commit_stats":{"total_commits":134,"total_committers":2,"mean_commits":67.0,"dds":0.08208955223880599,"last_synced_commit":"00314229a82bdba83e54860ab4bd6b360dd57214"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluots%2Fsql-udf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluots%2Fsql-udf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluots%2Fsql-udf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluots%2Fsql-udf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pluots","download_url":"https://codeload.github.com/pluots/sql-udf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248559992,"owners_count":21124578,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mariadb","mysql","rust","udf","user-defined-functions"],"created_at":"2024-09-30T15:04:35.547Z","updated_at":"2025-10-12T07:25:39.899Z","avatar_url":"https://github.com/pluots.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UDF: MariaDB/MySQL User Defined Functions in Rust\n\nThis crate aims to make it extremely simple to implement UDFs for SQL, in a\nminimally error-prone fashion.\n\nLooking for prewritten useful UDFs? Check out the UDF suite, which provides\ndownloadable binaries for some useful functions:\n\u003chttps://github.com/pluots/udf-suite\u003e.\n\nView the docs here: \u003chttps://docs.rs/udf/latest\u003e\n\n## UDF Theory\n\nBasic SQL UDFs consist of three exposed functions:\n\n- An initialization function where arguments are checked and memory is allocated\n- A processing function where a result is returned\n- A deinitialization function where anything on the heap is cleaned up (performed\n  automatically in this library)\n\nAggregate UDFs (those that work on more than one row at a time) simply need to\nregister two to three additional functions.\n\nThis library handles everything that used to be difficult about writing UDFs\n(dynamic registration, allocation/deallocation, error handling, nullable values,\nlogging) and makes it _trivial_ to add any function to your SQL server instance.\nIt also inclues a mock interface, for testing your function implementation\nwithout needing a server.\n\n## Quickstart\n\nThe steps to create a working UDF using this library are:\n\n- Create a new rust project (`cargo new --lib my-udf`), add `udf` as a\n  dependency (`cd my-udf; cargo add udf`) and change the crate type to a\n  `cdylib` by adding the following to `Cargo.toml`:\n\n  ```toml\n  [lib]\n  crate-type = [\"cdylib\"]\n  ```\n\n- Make a struct or enum that will share data between initializing and processing\n  steps (it may be empty). The default name of your UDF will be your struct's\n  name converted to snake case.\n- Implement the `BasicUdf` trait on this struct\n- Implement the `AggregateUdf` trait if you want it to be an aggregate function\n- Add `#[udf::register]` to each of these `impl` blocks (optionally with a\n  `(name = \"my_name\")` argument)\n- Compile the project with `cargo build --release` (output will be\n  `target/release/libmy_udf.so`)\n- Load the struct into MariaDB/MySql using `CREATE FUNCTION ...`\n- Use the function in SQL!\n\nFor an example of some UDFs written using this library, see either the\n`udf-examples/` directory or the [`udf-suite`](https://github.com/pluots/udf-suite)\nrepository.\n\n## Detailed overview\n\nThis section goes into the details of implementing a UDF with this library, but\nit is non-exhaustive. For that, see the documentation, or the `udf-examples`\ndirectory for well-annotated examples.\n\n### Struct creation\n\nThe first step is to create a struct (or enum) that will be used to share data\nbetween all relevant SQL functions. These include:\n\n- `init` Called once per result set. Here, you can store const data to your\n  struct (if applicable)\n- `process` Called once per row (or per group for aggregate functions). This\n  function uses data in the struct and in the current row's arguments\n- `clear` Aggregate only, called once per group at the beginning. Reset the\n  struct as needed.\n- `add` Aggregate only, called once per row within a group. Perform needed\n  calculations and save the data in the struct.\n- `remove` Window functions only, called to remove a value from a group\n\nIt is quite possible, especially for simple functions, that there is no data\nthat needs sharing. In this case, just make an empty struct and no allocation\nwill take place.\n\n\n```rust\n/// Function `sum_int` just adds all arguments as integers and needs no shared data\nstruct SumInt;\n\n/// Function `avg` on the other hand may want to save data to perform aggregation\nstruct Avg {\n    running_total: f64\n}\n```\n\nThere is a bit of a caveat for functions returning buffers (string \u0026 decimal\nfunctions): if there is a possibility that string length exceeds\n`MYSQL_RESULT_BUFFER_SIZE` (255), then the string to be returned must be\ncontained within the struct (the `process` function will then return a\nreference).\n\n```rust\n/// Generate random lipsum that may be longer than 255 bytes\nstruct Lipsum {\n    res: String\n}\n```\n\n### Trait Implementation\n\nThe next step is to implement the `BasicUdf` and optionally `AggregateUdf`\ntraits. See [the docs](https://docs.rs/udf/latest/udf/trait.BasicUdf.html)\nfor more information.\n\nIf you use rust-analyzer with your IDE, it can help you out. Just type\n`impl BasicUdf for MyStruct {}` and place your cursor between the brackets -\nit will offer to autofill the function skeletons (`ctrl+.` or `cmd+.`\nbrings up this menu if it doesn't show up by default).\n\n```rust\nuse udf::prelude::*;\n\nstruct SumInt;\n\n#[register]\nimpl BasicUdf for SumInt {\n    type Returns\u003c'a\u003e = Option\u003ci64\u003e;\n\n    fn init\u003c'a\u003e(\n      cfg: \u0026UdfCfg\u003cInit\u003e,\n      args: \u0026'a ArgList\u003c'a, Init\u003e\n    ) -\u003e Result\u003cSelf, String\u003e {\n      // ...\n    }\n\n    fn process\u003c'a\u003e(\n        \u0026'a mut self,\n        cfg: \u0026UdfCfg\u003cProcess\u003e,\n        args: \u0026ArgList\u003cProcess\u003e,\n        error: Option\u003cNonZeroU8\u003e,\n    ) -\u003e Result\u003cSelf::Returns\u003c'a\u003e, ProcessError\u003e {\n      // ...\n    }\n}\n```\n\n### Compiling\n\nAssuming the above has been followed, all that is needed is to produce a C\ndynamic library for the project. This can be done by specifying\n`crate-type = [\"cdylib\"]` in your `Cargo.toml`. After this, compiling with\n`cargo build --release` will produce a loadable `.so` file (located in\n`target/release`).\n\nImportant version note: this crate relies on a feature called generic associated\ntypes (GATs) which are only available on rust \u003e= 1.65. This version only just\nbecame stable (2022-11-03), so be sure to run `rustup update` if you run into\ncompiler issues.\n\nCI runs tests on both Linux and Windows, and this crate should work for either.\nMacOS is untested, but will likely work as well.\n\n### Symbol Inspection\n\nIf you would like to verify that the correct C-callable functions are present,\nyou can inspect the dynamic library with `nm`.\n\n```sh\n# Output of example .so\n$ nm -gC --defined-only target/release/libudf_examples.so\n00000000000081b0 T avg_cost\n0000000000008200 T avg_cost_add\n00000000000081e0 T avg_cost_clear\n0000000000008190 T avg_cost_deinit\n0000000000008100 T avg_cost_init\n0000000000009730 T is_const\n0000000000009710 T is_const_deinit\n0000000000009680 T is_const_init\n0000000000009320 T sql_sequence\n...\n```\n\n### Usage\n\nOnce compiled, the produced object file needs to be copied to the location of\nthe `plugin_dir` SQL variable - usually, this is `/usr/lib/mysql/plugin/`.\n\nOnce that has been done, `CREATE FUNCTION` can be used in MariaDB/MySql to load\nit.\n\n\n## Docker Use\n\nTesting in Docker is highly recommended, so as to avoid disturbing a host SQL\ninstallation. See [the udf-examples readme](udf-examples/README.md) for\ninstructions on how to do this.\n\n\n## Examples\n\nThe `udf-examples` crate contains examples of various UDFs, as well as\ninstructions on how to compile them. See [the readme](udf-examples/README.md)\nthere.\n\n\n## Logging \u0026 Debugging Note\n\nIf you need to log things like warnings during normal use of the function,\nanything printed to `stderr` will appear in the server logs (which can be viewed\nwith e.g. `docker logs mariadb_udf_test` if testing in Docker). The `udf_log!`\nmacro will print a message that matches the formatting of other SQL log\ninformation. You can also enable the crate features `logging-debug` for function\nentry/exitpoint debugging, or `logging-debug-calls` for information on the exact\ncall parameters from the MariaDB/MySQL server.\n\nThe best way to debug is to use the `udf::mock` module to create s.all unit\ntests. These can be run to validate correctness, or stepped through with a\ndebugger if needed (this use case is likely somewhat rare). All types implement\n`Debug` so they can also be easily printed (the builtin `dbg!` macro prints to\n`stderr`, so this will also appear in logs):\n\n```rust\ndbg!(\u0026self);\nlet arg0 = dbg!(args.get(0).unwrap())\n```\n\n```\n[udf_examples/src/avgcost.rs:58] \u0026self = AvgCost {\n    count: 0,\n    total_qty: 0,\n    total_price: 0.0,\n}\n\n[udf_examples/src/avgcost.rs:60] args.get(0).unwrap() = SqlArg {\n    value: Int(\n        Some(\n            10,\n        ),\n    ),\n    attribute: \"qty\",\n    maybe_null: true,\n    arg_type: Cell {\n        value: INT_RESULT,\n    },\n    marker: PhantomData\u003cudf::traits::Process\u003e,\n}\n```\n\n## License\n\nThis work is dual-licensed under Apache 2.0 and GPL 2.0 (or any later version)\nas of version 0.5.1. You can choose either of them if you use this work.\n\n`SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpluots%2Fsql-udf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpluots%2Fsql-udf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpluots%2Fsql-udf/lists"}