├── .github └── workflows │ ├── build-mysql.yml │ ├── build-sql-lib.yml │ └── bump-version.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── luals └── gmod_include.lua ├── mysql ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── rust-toolchain └── src │ ├── conn │ ├── connect.rs │ ├── disconnect.rs │ ├── mod.rs │ ├── options.rs │ ├── ping.rs │ ├── query.rs │ └── state.rs │ ├── constants.rs │ ├── error.rs │ ├── lib.rs │ ├── query │ ├── mod.rs │ ├── process.rs │ └── result.rs │ └── runtime.rs └── sql ├── .gitignore ├── build.py ├── lua └── goobie-sql │ ├── common.lua │ ├── cross_syntaxes.lua │ ├── goobie-sql.lua │ ├── migrations.lua │ ├── mysql │ ├── main.lua │ └── txn.lua │ └── sqlite │ ├── main.lua │ └── txn.lua └── tests.lua /.github/workflows/build-mysql.yml: -------------------------------------------------------------------------------- 1 | name: Build MySQL 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Bump Version"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | build: 11 | permissions: write-all 12 | defaults: 13 | run: 14 | working-directory: mysql 15 | strategy: 16 | matrix: 17 | os: [linux, windows] 18 | include: 19 | - os: linux 20 | runs_on: ubuntu-22.04 21 | target1: i686-unknown-linux-gnu 22 | target2: x86_64-unknown-linux-gnu 23 | extra_deps: | 24 | sudo apt update 25 | sudo apt install gcc-i686-linux-gnu 26 | sudo apt install gcc-multilib 27 | binary_prefix: "lib" 28 | file_ext: "so" 29 | suffix32: _linux.dll 30 | suffix64: _linux64.dll 31 | - os: windows 32 | runs_on: windows-2022 33 | target1: i686-pc-windows-msvc 34 | target2: x86_64-pc-windows-msvc 35 | extra_deps: "" 36 | binary_prefix: "" 37 | file_ext: "dll" 38 | suffix32: _win32.dll 39 | suffix64: _win64.dll 40 | runs-on: ${{ matrix.runs_on }} 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | fetch-depth: "0" 45 | 46 | - uses: actions/download-artifact@v4 47 | with: 48 | name: version 49 | run-id: ${{ github.event.workflow_run.id }} 50 | github-token: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: Get version 53 | id: get_version 54 | shell: bash 55 | run: | 56 | echo "version=$(cat ../version.txt)" >> $GITHUB_OUTPUT 57 | echo "version_major=$(cat ../version_major.txt)" >> $GITHUB_OUTPUT 58 | 59 | - name: Set up Rust 60 | run: rustup toolchain install stable --profile minimal 61 | 62 | - name: Install build dependencies 63 | if: matrix.os == 'linux' 64 | run: ${{ matrix.extra_deps }} 65 | 66 | - name: Add targets 67 | run: rustup target add ${{ matrix.target1 }} ${{ matrix.target2 }} 68 | 69 | - name: Install cargo-set-version 70 | run: cargo install cargo-set-version 71 | 72 | - name: Bump Cargo version 73 | run: cargo set-version "${{ steps.get_version.outputs.version }}.0" 74 | 75 | - name: Build targets 76 | run: | 77 | cargo build --release --target ${{ matrix.target1 }} 78 | cargo build --release --target ${{ matrix.target2 }} 79 | 80 | - name: Rename binaries 81 | run: | 82 | mv target/${{ matrix.target1 }}/release/${{ matrix.binary_prefix }}gmsv_goobie_mysql.${{ matrix.file_ext }} target/${{ matrix.target1 }}/release/gmsv_goobie_mysql_${{ steps.get_version.outputs.version_major }}${{ matrix.suffix32 }} 83 | mv target/${{ matrix.target2 }}/release/${{ matrix.binary_prefix }}gmsv_goobie_mysql.${{ matrix.file_ext }} target/${{ matrix.target2 }}/release/gmsv_goobie_mysql_${{ steps.get_version.outputs.version_major }}${{ matrix.suffix64 }} 84 | 85 | - name: Release Artifacts 86 | uses: ncipollo/release-action@v1 87 | with: 88 | allowUpdates: true 89 | token: "${{ secrets.GITHUB_TOKEN }}" 90 | tag: "${{ steps.get_version.outputs.version }}" 91 | artifacts: | 92 | mysql/target/${{ matrix.target1 }}/release/gmsv_goobie_mysql_${{ steps.get_version.outputs.version_major }}${{ matrix.suffix32 }} 93 | mysql/target/${{ matrix.target2 }}/release/gmsv_goobie_mysql_${{ steps.get_version.outputs.version_major }}${{ matrix.suffix64 }} 94 | -------------------------------------------------------------------------------- /.github/workflows/build-sql-lib.yml: -------------------------------------------------------------------------------- 1 | name: Build SQL Lib 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Bump Version"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | build: 11 | permissions: write-all 12 | defaults: 13 | run: 14 | working-directory: sql 15 | runs-on: ubuntu-22.04 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: "0" 20 | 21 | - uses: actions/download-artifact@v4 22 | with: 23 | name: version 24 | run-id: ${{ github.event.workflow_run.id }} 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - name: Get version 28 | id: get_version 29 | shell: bash 30 | run: | 31 | echo "version=$(cat ../version.txt)" >> $GITHUB_OUTPUT 32 | 33 | - name: Build targets 34 | run: | 35 | python3 build.py ${{ steps.get_version.outputs.version }} 36 | 37 | - name: Release Artifacts 38 | uses: ncipollo/release-action@v1 39 | with: 40 | allowUpdates: true 41 | token: "${{ secrets.GITHUB_TOKEN }}" 42 | tag: "${{ steps.get_version.outputs.version }}" 43 | artifacts: sql/goobie-sql.lua 44 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - "**" 9 | - "!README.md" 10 | 11 | jobs: 12 | bump-version: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | outputs: 17 | new_tag: ${{ steps.bump_version.outputs.new_tag }} 18 | new_tag_major: ${{ steps.bump_version.outputs.new_tag_major }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: "0" 23 | 24 | - name: Bump Version 25 | id: bump_version 26 | uses: Srlion/float-version-semantic@2811c4af2d8e37a37d3b8113ca5a07b4152be2fd 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Output version to file 31 | run: | 32 | mkdir -p output 33 | echo ${{ steps.bump_version.outputs.new_tag }} > output/version.txt 34 | echo ${{ steps.bump_version.outputs.new_tag_major }} > output/version_major.txt 35 | 36 | - uses: actions/upload-artifact@v4 37 | with: 38 | name: version 39 | path: | 40 | output/version.txt 41 | output/version_major.txt 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.runtime.plugin": "${workspaceFolder}/luals/gmod_include.lua" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Most changes will be documented here. 4 | 5 | ## 2.0 6 | 7 | Combined goobie-sql and goobie-mysql into one library. 8 | 9 | This change was made to make it easier for me to maintain and update the library. 10 | 11 | ## 1.0 12 | 13 | Whole library rewritten to improve performance and fix some bugs. 14 | 15 | - Switched from SemVer to Float-Versioning. 16 | - Added `conn:Run`. This is just the same as `conn:Execute` but return no result at all. (This is for @morgverd the betch to to calm down about sqlite doing another query in `goobie-sql`) 17 | - Removed `on_connected` & `on_disconnected` & `on_error` callbacks from `goobie_mysql.NewConn`. 18 | - `conn:Start` and `conn:Disconnect` now take callbacks. 19 | - Removed `sync` option from all queries, now you can use `conn:RunSync`, `conn:ExecuteSync`, `conn:FetchSync`, and `conn:FetchOneSync`. 20 | - `conn:Ping` is now asynchronous. Use `conn:PingSync` for synchronous ping. 21 | - It also returns latency now, in microseconds. 22 | - Removed `goobie_mysql.Poll`. 23 | - Use `conn:Poll` instead. 24 | - Queries are now guaranteed to processed in order, this is to try to keep behavior consistent with sqlite in garry's mod. 25 | - Added `goobie_mysql.MAJOR_VERSION`. 26 | - Added `conn:ID` to get the connection ID. It's incremental for each inner connection. (You can test it by running `conn:StartSync()` multiple times). 27 | - Added `conn:StateName` to get the current connection state as a string. 28 | - Added **LuaLS** support, which can be found in `luals/goobie_mysql.lua`. 29 | - Database now attempts to reconnect if connection is lost. 30 | - Transactions will play nicely incase it happens. 31 | - Added `GOOBIE_MYSQL_GRACEFUL_SHUTDOWN_TIMEOUT` convar to control the timeout for graceful shutdown when restarting or closing the server. 32 | - Query results are now processed before being sent to the lua thread, this should give a small boost incase of large result sets. 33 | - Updated sqlx to `0.8.3` 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Srlion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Goobie SQL 2 | 3 | A simple, lightweight, and fast MySQL/SQLite library for Garry's Mod. 4 | 5 | ## Features 6 | 7 | - MySQL and SQLite. 8 | - Asynchronous and synchronous queries. 9 | - Easy to use transactions. (Using coroutines) 10 | - Simple migrations system. 11 | - One-file library. 12 | 13 | ## Installation 14 | 15 | Download the latest `goobie-sql.lua` from [GitHub releases](https://github.com/Srlion/goobie-sql/releases/latest). 16 | 17 | - If you are going to use MySQL, also download the `gmsv_goobie_mysql_x_x_x.dll` from [GitHub releases](https://github.com/Srlion/goobie-sql/releases/latest). Extract it to `garrysmod/lua/bin/gmsv_goobie_mysql_x_x_x.dll`. 18 | 19 | Add `goobie-sql.lua` to your addon folder in `thirdparty`. 20 | 21 | ## Usage 22 | 23 | #### SQLite 24 | 25 | ```lua 26 | local goobie_sql = include("myaddon/thirdparty/goobie-sql.lua") 27 | local conn = goobie_sql.NewConn({ 28 | driver = "sqlite", 29 | }) 30 | ``` 31 | 32 | #### MySQL 33 | 34 | ```lua 35 | local goobie_sql = include("myaddon/thirdparty/goobie-sql.lua") 36 | local conn = goobie_sql.NewConn({ 37 | driver = "mysql", 38 | uri = "mysql://USERNAME:PASSWORD@HOST/DATABASE", 39 | }) 40 | ``` 41 | 42 | ## Documentation 43 | 44 | ```lua 45 | -- NewConn Starts a connection automatically synchronously, if you want to use it asynchronously, pass a function as the second argument. 46 | local conn = goobie_sql.NewConn({ 47 | driver = "mysql", -- or "sqlite" 48 | 49 | -- called when a query returns an error 50 | on_error = function(err) 51 | end, 52 | 53 | -- MySQL specific options 54 | 55 | -- The URI format is `mysql://[user[:password]@][host][:port]/[database][?properties]`. 56 | -- Read more info here https://docs.rs/sqlx/latest/sqlx/mysql/struct.MySqlConnectOptions.html 57 | uri = "mysql://USERNAME:PASSWORD@HOST/DATABASE", 58 | -- OR 59 | host = "127.0.0.1", 60 | port = 3306, 61 | username = "root", 62 | password = "1234", 63 | database = "test", 64 | 65 | charset = "utf8mb4", 66 | collation = "utf8mb4_unicode_ci", 67 | timezone = "UTC", 68 | statement_cache_capacity = 100, 69 | socket = "/tmp/mysql.sock", 70 | }) 71 | ``` 72 | 73 | ### Error object 74 | 75 | ```lua 76 | { 77 | message = string, 78 | code = number|nil, 79 | sqlstate = string|nil, 80 | } 81 | ``` 82 | 83 | - Has `__tostring` metamethod that returns a formatted string. 84 | 85 | ### Query options 86 | 87 | ```lua 88 | { 89 | params = table, -- {1, 3, 5} 90 | callback = function(err, res), 91 | end, 92 | -- if true, the params will not be used and you can have multi statement queries. 93 | raw = boolean 94 | } 95 | ``` 96 | 97 | ### Connection methods 98 | 99 | `Conn:Start(function(err) end)` 100 | 101 | - Attempts to reconnect if the connection is lost. 102 | 103 | `Conn:StartSync()` 104 | 105 | - Attempts to reconnect if the connection is lost. 106 | - Throws an error if the connection fails. 107 | 108 | `Conn:Disconnect(function(err) end)` 109 | 110 | - Disconnects from the database asynchronously. 111 | 112 | `Conn:DisconnectSync()` -> `err` 113 | 114 | - Disconnects from the database synchronously. 115 | - Unlike `Conn:StartSync`, this function returns an error if the connection fails. 116 | 117 | `Conn:State()` -> `state: number` 118 | 119 | `Conn:StateName()` -> `state: string` 120 | 121 | - Returns the current state of the connection as a string. 122 | 123 | `Conn:ID()` -> `id: number` 124 | 125 | - Returns the id of the inner mysql connection, it's incremental for each inner connection that is created. 126 | - Returns 1 if it's sqlite connection. 127 | 128 | `Conn:Host()` -> `host: string` 129 | 130 | `Conn:Port()` -> `port: number` 131 | 132 | `Conn:Ping(function(err, latency) end)` 133 | 134 | - Pings the database to check the connection status. 135 | - **Note:** It's generally not recommended to use this method to check if a connection is alive, as it may not be reliable. For more information, refer to [this article](https://www.percona.com/blog/checking-for-a-live-database-connection-considered-harmful/). 136 | 137 | `Conn:PingSync()` -> `err, latency` 138 | 139 | - Pings the database to check the connection status. 140 | 141 | ### Query methods 142 | 143 | `Conn:Run(query: string, opts: table)` 144 | 145 | - Runs a query asynchronously. 146 | - Callback gets called with `err` if the query fails. Nothing is passed if the query succeeds. 147 | 148 | `Conn:RunSync(query: string, opts: table)` -> `err` 149 | 150 | `Conn:Execute(query: string, opts: table)` -> `err, res` 151 | 152 | - Executes a query asynchronously. 153 | - Callback gets called with `err, res` where `res` is a table with the following fields: 154 | - `last_insert_id: number` 155 | - `rows_affected: number` 156 | 157 | `Conn:ExecuteSync(query: string, opts: table)` -> `err, res` 158 | 159 | `Conn:Fetch(query: string, opts: table)` -> `err, res` 160 | 161 | - Fetches a query asynchronously. 162 | - Callback gets called with `err, res` where `res` is an array of rows. 163 | 164 | `Conn:FetchSync(query: string, opts: table)` -> `err, res` 165 | 166 | `Conn:FetchOne(query: string, opts: table)` -> `err, res` 167 | 168 | - Fetches a single row asynchronously. 169 | - Callback gets called with `err, res` where `res` is a single row. 170 | 171 | `Conn:FetchOneSync(query: string, opts: table)` -> `err, res` 172 | 173 | #### Example 174 | 175 | ```lua 176 | conn:Execute("INSERT INTO test_table (value, value2) VALUES ({1}, {2})", { 177 | params = {"test", "test2"}, 178 | callback = function(err, res) 179 | print(err, res) 180 | end 181 | }) 182 | ``` 183 | 184 | ### UpsertQuery 185 | 186 | ```lua 187 | local opts = { 188 | -- primary keys that could conflict, basically the unique/primary key 189 | primary_keys = { "id" }, 190 | -- will try to insert these values, if it fails due to a conflict, it will update the values 191 | inserts = { 192 | id = 1, 193 | value = "test", 194 | }, 195 | -- will update these values, if it fails due to a conflict, it will insert the values 196 | updates = { 197 | value = "test2" 198 | }, 199 | binary_columns = { "value" }, -- if you want to insert binary data, you need to specify the columns that are binary, this is just sqlite specific till Rubat adds https://github.com/Facepunch/garrysmod-requests/issues/2654 200 | callback = function(err, res) 201 | end, 202 | } 203 | 204 | Conn:UpsertQuery("test_table", opts) 205 | local err, res = Conn:UpsertQuerySync("test_table", opts) 206 | ``` 207 | 208 | ### Transactions 209 | 210 | Inside `Begin(Sync)`, you don't use callback, instead queries return errors and results directly. 211 | 212 | In MySQL, this is achieved by using coroutines to make transactions easier to use. 213 | 214 | ```lua 215 | Conn:Begin(function(err, txn) 216 | if err then 217 | return 218 | end 219 | local err, res = txn:Execute("INSERT INTO test_table (value) VALUES ('test')") 220 | if err then 221 | txn:Rollback() -- you must rollback explicitly if you want to stop execution 222 | return 223 | end 224 | local err = txn:Commit() 225 | print(txn:IsOpen()) -- false 226 | end) 227 | 228 | -- If you want to have it run synchronously, you can use `BeginSync`, it's the the same as `Begin` but instead everything runs synchronously. 229 | ``` 230 | 231 | ### Cross Syntaxes 232 | 233 | Cross syntaxes try to make queries easier to write for both SQLite and MySQL. 234 | 235 | Here is a list of current cross syntaxes: 236 | 237 | ```lua 238 | --- SQLite 239 | { 240 | CROSS_NOW = "(CAST(strftime('%s', 'now') AS INTEGER))", 241 | -- INTEGER PRIMARY KEY auto increments in SQLite, see https://www.sqlite.org/autoinc.html 242 | CROSS_PRIMARY_AUTO_INCREMENTED = "INTEGER PRIMARY KEY", 243 | CROSS_COLLATE_BINARY = "COLLATE BINARY", 244 | CROSS_CURRENT_DATE = "DATE('now')", 245 | CROSS_OS_TIME_TYPE = "INT UNSIGNED NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INTEGER))", 246 | CROSS_INT_TYPE = "INTEGER", 247 | CROSS_JSON_TYPE = "TEXT", 248 | } 249 | 250 | --- MySQL 251 | { 252 | CROSS_NOW = "(UNIX_TIMESTAMP())", 253 | CROSS_PRIMARY_AUTO_INCREMENTED = "BIGINT AUTO_INCREMENT PRIMARY KEY", 254 | CROSS_COLLATE_BINARY = "BINARY", 255 | CROSS_CURRENT_DATE = "CURDATE()", 256 | CROSS_OS_TIME_TYPE = "INT UNSIGNED NOT NULL DEFAULT (UNIX_TIMESTAMP())", 257 | CROSS_INT_TYPE = "BIGINT", 258 | CROSS_JSON_TYPE = "JSON", 259 | } 260 | ``` 261 | 262 | They can be used in the following way: 263 | 264 | ```lua 265 | conn:RunMigrations({ 266 | { 267 | UP = [[ 268 | CREATE TABLE IF NOT EXISTS test_table ( 269 | id {CROSS_PRIMARY_AUTO_INCREMENTED}, 270 | value TEXT, 271 | `created_at` {CROSS_OS_TIME_TYPE}, 272 | ); 273 | ]], 274 | DOWN = [[ 275 | DROP TABLE test_table; 276 | ]] 277 | } 278 | }) 279 | 280 | conn:RunSync([[ 281 | SELECT * FROM test_table WHERE `created_at` > {CROSS_NOW}; 282 | ]]) 283 | ``` 284 | 285 | ### Migrations 286 | 287 | ```lua 288 | local conn = goobie_sql.NewConn({ 289 | driver = "sqlite", 290 | addon_name = "test", 291 | }) 292 | 293 | local current_version, first_run = conn:RunMigrations({ 294 | -- can use string or function for UP and DOWN 295 | { 296 | UP = [[ 297 | CREATE TABLE IF NOT EXISTS test_table ( 298 | id INTEGER PRIMARY KEY, 299 | value TEXT 300 | ); 301 | ]], 302 | DOWN = [[ 303 | DROP TABLE test_table; 304 | ]] 305 | }, 306 | { 307 | UP = function(process, conn) 308 | process([[ 309 | CREATE TABLE IF NOT EXISTS test_table ( 310 | id INTEGER PRIMARY KEY, 311 | value TEXT 312 | ); 313 | ]]) 314 | end, 315 | DOWN = function(process, conn) 316 | process([[ 317 | DROP TABLE test_table; 318 | ]]) 319 | end, 320 | } 321 | }) 322 | 323 | print(current_version, first_run) 324 | ``` 325 | 326 | You can also have conditionals in your migrations: 327 | 328 | ```lua 329 | conn:RunMigrations({ 330 | { 331 | UP = [[ 332 | CREATE TABLE IF NOT EXISTS test_table ( 333 | --@ifdef SQLITE 334 | id INTEGER PRIMARY KEY, 335 | --@else 336 | id BIGINT AUTO_INCREMENT PRIMARY KEY, 337 | --@endif 338 | value TEXT 339 | ); 340 | ]], 341 | DOWN = [[ 342 | DROP TABLE test_table; 343 | ]] 344 | } 345 | }) 346 | ``` 347 | -------------------------------------------------------------------------------- /luals/gmod_include.lua: -------------------------------------------------------------------------------- 1 | function ResolveRequire(uri, name) 2 | return { uri .. "/sql/lua/" .. name } 3 | end 4 | -------------------------------------------------------------------------------- /mysql/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | build.bat 3 | buildrelease.bat 4 | unused 5 | /.vscode 6 | -------------------------------------------------------------------------------- /mysql/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = false 2 | imports_granularity = "Crate" 3 | chain_width = 50 4 | -------------------------------------------------------------------------------- /mysql/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.7.8" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 25 | dependencies = [ 26 | "getrandom 0.2.15", 27 | "once_cell", 28 | "version_check", 29 | ] 30 | 31 | [[package]] 32 | name = "allocator-api2" 33 | version = "0.2.21" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 36 | 37 | [[package]] 38 | name = "android-tzdata" 39 | version = "0.1.1" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 42 | 43 | [[package]] 44 | name = "android_system_properties" 45 | version = "0.1.5" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 48 | dependencies = [ 49 | "libc", 50 | ] 51 | 52 | [[package]] 53 | name = "anyhow" 54 | version = "1.0.98" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 57 | 58 | [[package]] 59 | name = "arrayvec" 60 | version = "0.7.6" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 63 | 64 | [[package]] 65 | name = "atoi" 66 | version = "2.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 69 | dependencies = [ 70 | "num-traits", 71 | ] 72 | 73 | [[package]] 74 | name = "atomic_enum" 75 | version = "0.3.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" 78 | dependencies = [ 79 | "proc-macro2", 80 | "quote", 81 | "syn 2.0.100", 82 | ] 83 | 84 | [[package]] 85 | name = "autocfg" 86 | version = "1.4.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 89 | 90 | [[package]] 91 | name = "backtrace" 92 | version = "0.3.74" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 95 | dependencies = [ 96 | "addr2line", 97 | "cfg-if", 98 | "libc", 99 | "miniz_oxide", 100 | "object", 101 | "rustc-demangle", 102 | "windows-targets 0.52.6", 103 | ] 104 | 105 | [[package]] 106 | name = "base64" 107 | version = "0.22.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 110 | 111 | [[package]] 112 | name = "base64ct" 113 | version = "1.7.3" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" 116 | 117 | [[package]] 118 | name = "bitflags" 119 | version = "2.9.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 122 | dependencies = [ 123 | "serde", 124 | ] 125 | 126 | [[package]] 127 | name = "bitvec" 128 | version = "1.0.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 131 | dependencies = [ 132 | "funty", 133 | "radium", 134 | "tap", 135 | "wyz", 136 | ] 137 | 138 | [[package]] 139 | name = "block-buffer" 140 | version = "0.10.4" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 143 | dependencies = [ 144 | "generic-array", 145 | ] 146 | 147 | [[package]] 148 | name = "borsh" 149 | version = "1.5.7" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" 152 | dependencies = [ 153 | "borsh-derive", 154 | "cfg_aliases", 155 | ] 156 | 157 | [[package]] 158 | name = "borsh-derive" 159 | version = "1.5.7" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" 162 | dependencies = [ 163 | "once_cell", 164 | "proc-macro-crate", 165 | "proc-macro2", 166 | "quote", 167 | "syn 2.0.100", 168 | ] 169 | 170 | [[package]] 171 | name = "bumpalo" 172 | version = "3.17.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 175 | 176 | [[package]] 177 | name = "bytecheck" 178 | version = "0.6.12" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" 181 | dependencies = [ 182 | "bytecheck_derive", 183 | "ptr_meta", 184 | "simdutf8", 185 | ] 186 | 187 | [[package]] 188 | name = "bytecheck_derive" 189 | version = "0.6.12" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" 192 | dependencies = [ 193 | "proc-macro2", 194 | "quote", 195 | "syn 1.0.109", 196 | ] 197 | 198 | [[package]] 199 | name = "byteorder" 200 | version = "1.5.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 203 | 204 | [[package]] 205 | name = "bytes" 206 | version = "1.10.1" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 209 | 210 | [[package]] 211 | name = "cc" 212 | version = "1.2.19" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" 215 | dependencies = [ 216 | "shlex", 217 | ] 218 | 219 | [[package]] 220 | name = "cfg-if" 221 | version = "1.0.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 224 | 225 | [[package]] 226 | name = "cfg_aliases" 227 | version = "0.2.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 230 | 231 | [[package]] 232 | name = "chrono" 233 | version = "0.4.40" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 236 | dependencies = [ 237 | "android-tzdata", 238 | "iana-time-zone", 239 | "num-traits", 240 | "windows-link", 241 | ] 242 | 243 | [[package]] 244 | name = "concurrent-queue" 245 | version = "2.5.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 248 | dependencies = [ 249 | "crossbeam-utils", 250 | ] 251 | 252 | [[package]] 253 | name = "const-oid" 254 | version = "0.9.6" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 257 | 258 | [[package]] 259 | name = "const_format" 260 | version = "0.2.34" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" 263 | dependencies = [ 264 | "const_format_proc_macros", 265 | ] 266 | 267 | [[package]] 268 | name = "const_format_proc_macros" 269 | version = "0.2.34" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" 272 | dependencies = [ 273 | "proc-macro2", 274 | "quote", 275 | "unicode-xid", 276 | ] 277 | 278 | [[package]] 279 | name = "core-foundation" 280 | version = "0.9.4" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 283 | dependencies = [ 284 | "core-foundation-sys", 285 | "libc", 286 | ] 287 | 288 | [[package]] 289 | name = "core-foundation-sys" 290 | version = "0.8.7" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 293 | 294 | [[package]] 295 | name = "cpufeatures" 296 | version = "0.2.17" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 299 | dependencies = [ 300 | "libc", 301 | ] 302 | 303 | [[package]] 304 | name = "crc" 305 | version = "3.2.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 308 | dependencies = [ 309 | "crc-catalog", 310 | ] 311 | 312 | [[package]] 313 | name = "crc-catalog" 314 | version = "2.4.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 317 | 318 | [[package]] 319 | name = "crossbeam-queue" 320 | version = "0.3.12" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 323 | dependencies = [ 324 | "crossbeam-utils", 325 | ] 326 | 327 | [[package]] 328 | name = "crossbeam-utils" 329 | version = "0.8.21" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 332 | 333 | [[package]] 334 | name = "crypto-common" 335 | version = "0.1.6" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 338 | dependencies = [ 339 | "generic-array", 340 | "typenum", 341 | ] 342 | 343 | [[package]] 344 | name = "defer" 345 | version = "0.2.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "930c7171c8df9fb1782bdf9b918ed9ed2d33d1d22300abb754f9085bc48bf8e8" 348 | 349 | [[package]] 350 | name = "der" 351 | version = "0.7.9" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" 354 | dependencies = [ 355 | "const-oid", 356 | "pem-rfc7468", 357 | "zeroize", 358 | ] 359 | 360 | [[package]] 361 | name = "digest" 362 | version = "0.10.7" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 365 | dependencies = [ 366 | "block-buffer", 367 | "const-oid", 368 | "crypto-common", 369 | "subtle", 370 | ] 371 | 372 | [[package]] 373 | name = "displaydoc" 374 | version = "0.2.5" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 377 | dependencies = [ 378 | "proc-macro2", 379 | "quote", 380 | "syn 2.0.100", 381 | ] 382 | 383 | [[package]] 384 | name = "dotenvy" 385 | version = "0.15.7" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 388 | 389 | [[package]] 390 | name = "either" 391 | version = "1.15.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 394 | dependencies = [ 395 | "serde", 396 | ] 397 | 398 | [[package]] 399 | name = "equivalent" 400 | version = "1.0.2" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 403 | 404 | [[package]] 405 | name = "errno" 406 | version = "0.3.11" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 409 | dependencies = [ 410 | "libc", 411 | "windows-sys 0.59.0", 412 | ] 413 | 414 | [[package]] 415 | name = "etcetera" 416 | version = "0.8.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 419 | dependencies = [ 420 | "cfg-if", 421 | "home", 422 | "windows-sys 0.48.0", 423 | ] 424 | 425 | [[package]] 426 | name = "event-listener" 427 | version = "5.4.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 430 | dependencies = [ 431 | "concurrent-queue", 432 | "parking", 433 | "pin-project-lite", 434 | ] 435 | 436 | [[package]] 437 | name = "fastrand" 438 | version = "2.3.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 441 | 442 | [[package]] 443 | name = "flume" 444 | version = "0.11.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 447 | dependencies = [ 448 | "futures-core", 449 | "futures-sink", 450 | "spin", 451 | ] 452 | 453 | [[package]] 454 | name = "foldhash" 455 | version = "0.1.5" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 458 | 459 | [[package]] 460 | name = "foreign-types" 461 | version = "0.3.2" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 464 | dependencies = [ 465 | "foreign-types-shared", 466 | ] 467 | 468 | [[package]] 469 | name = "foreign-types-shared" 470 | version = "0.1.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 473 | 474 | [[package]] 475 | name = "form_urlencoded" 476 | version = "1.2.1" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 479 | dependencies = [ 480 | "percent-encoding", 481 | ] 482 | 483 | [[package]] 484 | name = "funty" 485 | version = "2.0.0" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 488 | 489 | [[package]] 490 | name = "futures-channel" 491 | version = "0.3.31" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 494 | dependencies = [ 495 | "futures-core", 496 | "futures-sink", 497 | ] 498 | 499 | [[package]] 500 | name = "futures-core" 501 | version = "0.3.31" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 504 | 505 | [[package]] 506 | name = "futures-executor" 507 | version = "0.3.31" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 510 | dependencies = [ 511 | "futures-core", 512 | "futures-task", 513 | "futures-util", 514 | ] 515 | 516 | [[package]] 517 | name = "futures-intrusive" 518 | version = "0.5.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 521 | dependencies = [ 522 | "futures-core", 523 | "lock_api", 524 | "parking_lot", 525 | ] 526 | 527 | [[package]] 528 | name = "futures-io" 529 | version = "0.3.31" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 532 | 533 | [[package]] 534 | name = "futures-macro" 535 | version = "0.3.31" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 538 | dependencies = [ 539 | "proc-macro2", 540 | "quote", 541 | "syn 2.0.100", 542 | ] 543 | 544 | [[package]] 545 | name = "futures-sink" 546 | version = "0.3.31" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 549 | 550 | [[package]] 551 | name = "futures-task" 552 | version = "0.3.31" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 555 | 556 | [[package]] 557 | name = "futures-util" 558 | version = "0.3.31" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 561 | dependencies = [ 562 | "futures-core", 563 | "futures-io", 564 | "futures-macro", 565 | "futures-sink", 566 | "futures-task", 567 | "memchr", 568 | "pin-project-lite", 569 | "pin-utils", 570 | "slab", 571 | ] 572 | 573 | [[package]] 574 | name = "generic-array" 575 | version = "0.14.7" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 578 | dependencies = [ 579 | "typenum", 580 | "version_check", 581 | ] 582 | 583 | [[package]] 584 | name = "getrandom" 585 | version = "0.2.15" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 588 | dependencies = [ 589 | "cfg-if", 590 | "libc", 591 | "wasi 0.11.0+wasi-snapshot-preview1", 592 | ] 593 | 594 | [[package]] 595 | name = "getrandom" 596 | version = "0.3.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 599 | dependencies = [ 600 | "cfg-if", 601 | "libc", 602 | "r-efi", 603 | "wasi 0.14.2+wasi-0.2.4", 604 | ] 605 | 606 | [[package]] 607 | name = "gimli" 608 | version = "0.31.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 611 | 612 | [[package]] 613 | name = "gmod" 614 | version = "17.0.0" 615 | source = "git+https://github.com/Srlion/gmod-rs.git?rev=b0ca2f1#b0ca2f139634e468bfb1fbb387c0b39e15a62142" 616 | dependencies = [ 617 | "anyhow", 618 | "defer", 619 | "gmod-macros", 620 | "libloading", 621 | "linkme", 622 | "paste", 623 | ] 624 | 625 | [[package]] 626 | name = "gmod-macros" 627 | version = "2.0.1" 628 | source = "git+https://github.com/Srlion/gmod-rs.git?rev=b0ca2f1#b0ca2f139634e468bfb1fbb387c0b39e15a62142" 629 | dependencies = [ 630 | "proc-macro2", 631 | "quote", 632 | "syn 2.0.100", 633 | ] 634 | 635 | [[package]] 636 | name = "goobie-mysql" 637 | version = "1.0.0" 638 | dependencies = [ 639 | "anyhow", 640 | "atomic_enum", 641 | "const_format", 642 | "gmod", 643 | "openssl-sys", 644 | "sqlx", 645 | "tokio", 646 | "tokio-util", 647 | ] 648 | 649 | [[package]] 650 | name = "hashbrown" 651 | version = "0.12.3" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 654 | dependencies = [ 655 | "ahash", 656 | ] 657 | 658 | [[package]] 659 | name = "hashbrown" 660 | version = "0.14.5" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 663 | 664 | [[package]] 665 | name = "hashbrown" 666 | version = "0.15.2" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 669 | dependencies = [ 670 | "allocator-api2", 671 | "equivalent", 672 | "foldhash", 673 | ] 674 | 675 | [[package]] 676 | name = "hashlink" 677 | version = "0.10.0" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 680 | dependencies = [ 681 | "hashbrown 0.15.2", 682 | ] 683 | 684 | [[package]] 685 | name = "heck" 686 | version = "0.5.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 689 | 690 | [[package]] 691 | name = "hex" 692 | version = "0.4.3" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 695 | 696 | [[package]] 697 | name = "hkdf" 698 | version = "0.12.4" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 701 | dependencies = [ 702 | "hmac", 703 | ] 704 | 705 | [[package]] 706 | name = "hmac" 707 | version = "0.12.1" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 710 | dependencies = [ 711 | "digest", 712 | ] 713 | 714 | [[package]] 715 | name = "home" 716 | version = "0.5.11" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 719 | dependencies = [ 720 | "windows-sys 0.59.0", 721 | ] 722 | 723 | [[package]] 724 | name = "iana-time-zone" 725 | version = "0.1.63" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 728 | dependencies = [ 729 | "android_system_properties", 730 | "core-foundation-sys", 731 | "iana-time-zone-haiku", 732 | "js-sys", 733 | "log", 734 | "wasm-bindgen", 735 | "windows-core", 736 | ] 737 | 738 | [[package]] 739 | name = "iana-time-zone-haiku" 740 | version = "0.1.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 743 | dependencies = [ 744 | "cc", 745 | ] 746 | 747 | [[package]] 748 | name = "icu_collections" 749 | version = "1.5.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 752 | dependencies = [ 753 | "displaydoc", 754 | "yoke", 755 | "zerofrom", 756 | "zerovec", 757 | ] 758 | 759 | [[package]] 760 | name = "icu_locid" 761 | version = "1.5.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 764 | dependencies = [ 765 | "displaydoc", 766 | "litemap", 767 | "tinystr", 768 | "writeable", 769 | "zerovec", 770 | ] 771 | 772 | [[package]] 773 | name = "icu_locid_transform" 774 | version = "1.5.0" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 777 | dependencies = [ 778 | "displaydoc", 779 | "icu_locid", 780 | "icu_locid_transform_data", 781 | "icu_provider", 782 | "tinystr", 783 | "zerovec", 784 | ] 785 | 786 | [[package]] 787 | name = "icu_locid_transform_data" 788 | version = "1.5.1" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" 791 | 792 | [[package]] 793 | name = "icu_normalizer" 794 | version = "1.5.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 797 | dependencies = [ 798 | "displaydoc", 799 | "icu_collections", 800 | "icu_normalizer_data", 801 | "icu_properties", 802 | "icu_provider", 803 | "smallvec", 804 | "utf16_iter", 805 | "utf8_iter", 806 | "write16", 807 | "zerovec", 808 | ] 809 | 810 | [[package]] 811 | name = "icu_normalizer_data" 812 | version = "1.5.1" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" 815 | 816 | [[package]] 817 | name = "icu_properties" 818 | version = "1.5.1" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 821 | dependencies = [ 822 | "displaydoc", 823 | "icu_collections", 824 | "icu_locid_transform", 825 | "icu_properties_data", 826 | "icu_provider", 827 | "tinystr", 828 | "zerovec", 829 | ] 830 | 831 | [[package]] 832 | name = "icu_properties_data" 833 | version = "1.5.1" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" 836 | 837 | [[package]] 838 | name = "icu_provider" 839 | version = "1.5.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 842 | dependencies = [ 843 | "displaydoc", 844 | "icu_locid", 845 | "icu_provider_macros", 846 | "stable_deref_trait", 847 | "tinystr", 848 | "writeable", 849 | "yoke", 850 | "zerofrom", 851 | "zerovec", 852 | ] 853 | 854 | [[package]] 855 | name = "icu_provider_macros" 856 | version = "1.5.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 859 | dependencies = [ 860 | "proc-macro2", 861 | "quote", 862 | "syn 2.0.100", 863 | ] 864 | 865 | [[package]] 866 | name = "idna" 867 | version = "1.0.3" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 870 | dependencies = [ 871 | "idna_adapter", 872 | "smallvec", 873 | "utf8_iter", 874 | ] 875 | 876 | [[package]] 877 | name = "idna_adapter" 878 | version = "1.2.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 881 | dependencies = [ 882 | "icu_normalizer", 883 | "icu_properties", 884 | ] 885 | 886 | [[package]] 887 | name = "indexmap" 888 | version = "2.9.0" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 891 | dependencies = [ 892 | "equivalent", 893 | "hashbrown 0.15.2", 894 | ] 895 | 896 | [[package]] 897 | name = "itoa" 898 | version = "1.0.15" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 901 | 902 | [[package]] 903 | name = "js-sys" 904 | version = "0.3.77" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 907 | dependencies = [ 908 | "once_cell", 909 | "wasm-bindgen", 910 | ] 911 | 912 | [[package]] 913 | name = "lazy_static" 914 | version = "1.5.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 917 | dependencies = [ 918 | "spin", 919 | ] 920 | 921 | [[package]] 922 | name = "libc" 923 | version = "0.2.172" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 926 | 927 | [[package]] 928 | name = "libloading" 929 | version = "0.8.6" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 932 | dependencies = [ 933 | "cfg-if", 934 | "windows-targets 0.52.6", 935 | ] 936 | 937 | [[package]] 938 | name = "libm" 939 | version = "0.2.11" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 942 | 943 | [[package]] 944 | name = "libsqlite3-sys" 945 | version = "0.30.1" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" 948 | dependencies = [ 949 | "pkg-config", 950 | "vcpkg", 951 | ] 952 | 953 | [[package]] 954 | name = "linkme" 955 | version = "0.3.32" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe" 958 | dependencies = [ 959 | "linkme-impl", 960 | ] 961 | 962 | [[package]] 963 | name = "linkme-impl" 964 | version = "0.3.32" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be" 967 | dependencies = [ 968 | "proc-macro2", 969 | "quote", 970 | "syn 2.0.100", 971 | ] 972 | 973 | [[package]] 974 | name = "linux-raw-sys" 975 | version = "0.9.4" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 978 | 979 | [[package]] 980 | name = "litemap" 981 | version = "0.7.5" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 984 | 985 | [[package]] 986 | name = "lock_api" 987 | version = "0.4.12" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 990 | dependencies = [ 991 | "autocfg", 992 | "scopeguard", 993 | ] 994 | 995 | [[package]] 996 | name = "log" 997 | version = "0.4.27" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1000 | 1001 | [[package]] 1002 | name = "md-5" 1003 | version = "0.10.6" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1006 | dependencies = [ 1007 | "cfg-if", 1008 | "digest", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "memchr" 1013 | version = "2.7.4" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1016 | 1017 | [[package]] 1018 | name = "miniz_oxide" 1019 | version = "0.8.8" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 1022 | dependencies = [ 1023 | "adler2", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "mio" 1028 | version = "1.0.3" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1031 | dependencies = [ 1032 | "libc", 1033 | "wasi 0.11.0+wasi-snapshot-preview1", 1034 | "windows-sys 0.52.0", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "native-tls" 1039 | version = "0.2.14" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1042 | dependencies = [ 1043 | "libc", 1044 | "log", 1045 | "openssl", 1046 | "openssl-probe", 1047 | "openssl-sys", 1048 | "schannel", 1049 | "security-framework", 1050 | "security-framework-sys", 1051 | "tempfile", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "num-bigint-dig" 1056 | version = "0.8.4" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1059 | dependencies = [ 1060 | "byteorder", 1061 | "lazy_static", 1062 | "libm", 1063 | "num-integer", 1064 | "num-iter", 1065 | "num-traits", 1066 | "rand", 1067 | "smallvec", 1068 | "zeroize", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "num-integer" 1073 | version = "0.1.46" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1076 | dependencies = [ 1077 | "num-traits", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "num-iter" 1082 | version = "0.1.45" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1085 | dependencies = [ 1086 | "autocfg", 1087 | "num-integer", 1088 | "num-traits", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "num-traits" 1093 | version = "0.2.19" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1096 | dependencies = [ 1097 | "autocfg", 1098 | "libm", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "object" 1103 | version = "0.36.7" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1106 | dependencies = [ 1107 | "memchr", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "once_cell" 1112 | version = "1.21.3" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1115 | 1116 | [[package]] 1117 | name = "openssl" 1118 | version = "0.10.72" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" 1121 | dependencies = [ 1122 | "bitflags", 1123 | "cfg-if", 1124 | "foreign-types", 1125 | "libc", 1126 | "once_cell", 1127 | "openssl-macros", 1128 | "openssl-sys", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "openssl-macros" 1133 | version = "0.1.1" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1136 | dependencies = [ 1137 | "proc-macro2", 1138 | "quote", 1139 | "syn 2.0.100", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "openssl-probe" 1144 | version = "0.1.6" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1147 | 1148 | [[package]] 1149 | name = "openssl-src" 1150 | version = "300.5.0+3.5.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" 1153 | dependencies = [ 1154 | "cc", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "openssl-sys" 1159 | version = "0.9.107" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" 1162 | dependencies = [ 1163 | "cc", 1164 | "libc", 1165 | "openssl-src", 1166 | "pkg-config", 1167 | "vcpkg", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "parking" 1172 | version = "2.2.1" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1175 | 1176 | [[package]] 1177 | name = "parking_lot" 1178 | version = "0.12.3" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1181 | dependencies = [ 1182 | "lock_api", 1183 | "parking_lot_core", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "parking_lot_core" 1188 | version = "0.9.10" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1191 | dependencies = [ 1192 | "cfg-if", 1193 | "libc", 1194 | "redox_syscall", 1195 | "smallvec", 1196 | "windows-targets 0.52.6", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "paste" 1201 | version = "1.0.15" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1204 | 1205 | [[package]] 1206 | name = "pem-rfc7468" 1207 | version = "0.7.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1210 | dependencies = [ 1211 | "base64ct", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "percent-encoding" 1216 | version = "2.3.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1219 | 1220 | [[package]] 1221 | name = "pin-project-lite" 1222 | version = "0.2.16" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1225 | 1226 | [[package]] 1227 | name = "pin-utils" 1228 | version = "0.1.0" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1231 | 1232 | [[package]] 1233 | name = "pkcs1" 1234 | version = "0.7.5" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 1237 | dependencies = [ 1238 | "der", 1239 | "pkcs8", 1240 | "spki", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "pkcs8" 1245 | version = "0.10.2" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1248 | dependencies = [ 1249 | "der", 1250 | "spki", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "pkg-config" 1255 | version = "0.3.32" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1258 | 1259 | [[package]] 1260 | name = "ppv-lite86" 1261 | version = "0.2.21" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1264 | dependencies = [ 1265 | "zerocopy", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "proc-macro-crate" 1270 | version = "3.3.0" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" 1273 | dependencies = [ 1274 | "toml_edit", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "proc-macro2" 1279 | version = "1.0.95" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1282 | dependencies = [ 1283 | "unicode-ident", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "ptr_meta" 1288 | version = "0.1.4" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 1291 | dependencies = [ 1292 | "ptr_meta_derive", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "ptr_meta_derive" 1297 | version = "0.1.4" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 1300 | dependencies = [ 1301 | "proc-macro2", 1302 | "quote", 1303 | "syn 1.0.109", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "quote" 1308 | version = "1.0.40" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1311 | dependencies = [ 1312 | "proc-macro2", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "r-efi" 1317 | version = "5.2.0" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1320 | 1321 | [[package]] 1322 | name = "radium" 1323 | version = "0.7.0" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 1326 | 1327 | [[package]] 1328 | name = "rand" 1329 | version = "0.8.5" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1332 | dependencies = [ 1333 | "libc", 1334 | "rand_chacha", 1335 | "rand_core", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "rand_chacha" 1340 | version = "0.3.1" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1343 | dependencies = [ 1344 | "ppv-lite86", 1345 | "rand_core", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "rand_core" 1350 | version = "0.6.4" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1353 | dependencies = [ 1354 | "getrandom 0.2.15", 1355 | ] 1356 | 1357 | [[package]] 1358 | name = "redox_syscall" 1359 | version = "0.5.11" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 1362 | dependencies = [ 1363 | "bitflags", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "rend" 1368 | version = "0.4.2" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" 1371 | dependencies = [ 1372 | "bytecheck", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "rkyv" 1377 | version = "0.7.45" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" 1380 | dependencies = [ 1381 | "bitvec", 1382 | "bytecheck", 1383 | "bytes", 1384 | "hashbrown 0.12.3", 1385 | "ptr_meta", 1386 | "rend", 1387 | "rkyv_derive", 1388 | "seahash", 1389 | "tinyvec", 1390 | "uuid", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "rkyv_derive" 1395 | version = "0.7.45" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" 1398 | dependencies = [ 1399 | "proc-macro2", 1400 | "quote", 1401 | "syn 1.0.109", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "rsa" 1406 | version = "0.9.8" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" 1409 | dependencies = [ 1410 | "const-oid", 1411 | "digest", 1412 | "num-bigint-dig", 1413 | "num-integer", 1414 | "num-traits", 1415 | "pkcs1", 1416 | "pkcs8", 1417 | "rand_core", 1418 | "signature", 1419 | "spki", 1420 | "subtle", 1421 | "zeroize", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "rust_decimal" 1426 | version = "1.37.1" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" 1429 | dependencies = [ 1430 | "arrayvec", 1431 | "borsh", 1432 | "bytes", 1433 | "num-traits", 1434 | "rand", 1435 | "rkyv", 1436 | "serde", 1437 | "serde_json", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "rustc-demangle" 1442 | version = "0.1.24" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1445 | 1446 | [[package]] 1447 | name = "rustix" 1448 | version = "1.0.5" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 1451 | dependencies = [ 1452 | "bitflags", 1453 | "errno", 1454 | "libc", 1455 | "linux-raw-sys", 1456 | "windows-sys 0.59.0", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "rustversion" 1461 | version = "1.0.20" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1464 | 1465 | [[package]] 1466 | name = "ryu" 1467 | version = "1.0.20" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1470 | 1471 | [[package]] 1472 | name = "schannel" 1473 | version = "0.1.27" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1476 | dependencies = [ 1477 | "windows-sys 0.59.0", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "scopeguard" 1482 | version = "1.2.0" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1485 | 1486 | [[package]] 1487 | name = "seahash" 1488 | version = "4.1.0" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 1491 | 1492 | [[package]] 1493 | name = "security-framework" 1494 | version = "2.11.1" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1497 | dependencies = [ 1498 | "bitflags", 1499 | "core-foundation", 1500 | "core-foundation-sys", 1501 | "libc", 1502 | "security-framework-sys", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "security-framework-sys" 1507 | version = "2.14.0" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1510 | dependencies = [ 1511 | "core-foundation-sys", 1512 | "libc", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "serde" 1517 | version = "1.0.219" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1520 | dependencies = [ 1521 | "serde_derive", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "serde_derive" 1526 | version = "1.0.219" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1529 | dependencies = [ 1530 | "proc-macro2", 1531 | "quote", 1532 | "syn 2.0.100", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "serde_json" 1537 | version = "1.0.140" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1540 | dependencies = [ 1541 | "itoa", 1542 | "memchr", 1543 | "ryu", 1544 | "serde", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "serde_urlencoded" 1549 | version = "0.7.1" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1552 | dependencies = [ 1553 | "form_urlencoded", 1554 | "itoa", 1555 | "ryu", 1556 | "serde", 1557 | ] 1558 | 1559 | [[package]] 1560 | name = "sha1" 1561 | version = "0.10.6" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1564 | dependencies = [ 1565 | "cfg-if", 1566 | "cpufeatures", 1567 | "digest", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "sha2" 1572 | version = "0.10.8" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1575 | dependencies = [ 1576 | "cfg-if", 1577 | "cpufeatures", 1578 | "digest", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "shlex" 1583 | version = "1.3.0" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1586 | 1587 | [[package]] 1588 | name = "signature" 1589 | version = "2.2.0" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 1592 | dependencies = [ 1593 | "digest", 1594 | "rand_core", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "simdutf8" 1599 | version = "0.1.5" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 1602 | 1603 | [[package]] 1604 | name = "slab" 1605 | version = "0.4.9" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1608 | dependencies = [ 1609 | "autocfg", 1610 | ] 1611 | 1612 | [[package]] 1613 | name = "smallvec" 1614 | version = "1.15.0" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1617 | dependencies = [ 1618 | "serde", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "socket2" 1623 | version = "0.5.9" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 1626 | dependencies = [ 1627 | "libc", 1628 | "windows-sys 0.52.0", 1629 | ] 1630 | 1631 | [[package]] 1632 | name = "spin" 1633 | version = "0.9.8" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1636 | dependencies = [ 1637 | "lock_api", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "spki" 1642 | version = "0.7.3" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 1645 | dependencies = [ 1646 | "base64ct", 1647 | "der", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "sqlx" 1652 | version = "0.8.5" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" 1655 | dependencies = [ 1656 | "sqlx-core", 1657 | "sqlx-macros", 1658 | "sqlx-mysql", 1659 | "sqlx-postgres", 1660 | "sqlx-sqlite", 1661 | ] 1662 | 1663 | [[package]] 1664 | name = "sqlx-core" 1665 | version = "0.8.5" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" 1668 | dependencies = [ 1669 | "base64", 1670 | "bytes", 1671 | "chrono", 1672 | "crc", 1673 | "crossbeam-queue", 1674 | "either", 1675 | "event-listener", 1676 | "futures-core", 1677 | "futures-intrusive", 1678 | "futures-io", 1679 | "futures-util", 1680 | "hashbrown 0.15.2", 1681 | "hashlink", 1682 | "indexmap", 1683 | "log", 1684 | "memchr", 1685 | "native-tls", 1686 | "once_cell", 1687 | "percent-encoding", 1688 | "rust_decimal", 1689 | "serde", 1690 | "serde_json", 1691 | "sha2", 1692 | "smallvec", 1693 | "thiserror", 1694 | "tokio", 1695 | "tokio-stream", 1696 | "tracing", 1697 | "url", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "sqlx-macros" 1702 | version = "0.8.5" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" 1705 | dependencies = [ 1706 | "proc-macro2", 1707 | "quote", 1708 | "sqlx-core", 1709 | "sqlx-macros-core", 1710 | "syn 2.0.100", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "sqlx-macros-core" 1715 | version = "0.8.5" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" 1718 | dependencies = [ 1719 | "dotenvy", 1720 | "either", 1721 | "heck", 1722 | "hex", 1723 | "once_cell", 1724 | "proc-macro2", 1725 | "quote", 1726 | "serde", 1727 | "serde_json", 1728 | "sha2", 1729 | "sqlx-core", 1730 | "sqlx-mysql", 1731 | "sqlx-postgres", 1732 | "sqlx-sqlite", 1733 | "syn 2.0.100", 1734 | "tempfile", 1735 | "tokio", 1736 | "url", 1737 | ] 1738 | 1739 | [[package]] 1740 | name = "sqlx-mysql" 1741 | version = "0.8.5" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" 1744 | dependencies = [ 1745 | "atoi", 1746 | "base64", 1747 | "bitflags", 1748 | "byteorder", 1749 | "bytes", 1750 | "chrono", 1751 | "crc", 1752 | "digest", 1753 | "dotenvy", 1754 | "either", 1755 | "futures-channel", 1756 | "futures-core", 1757 | "futures-io", 1758 | "futures-util", 1759 | "generic-array", 1760 | "hex", 1761 | "hkdf", 1762 | "hmac", 1763 | "itoa", 1764 | "log", 1765 | "md-5", 1766 | "memchr", 1767 | "once_cell", 1768 | "percent-encoding", 1769 | "rand", 1770 | "rsa", 1771 | "rust_decimal", 1772 | "serde", 1773 | "sha1", 1774 | "sha2", 1775 | "smallvec", 1776 | "sqlx-core", 1777 | "stringprep", 1778 | "thiserror", 1779 | "tracing", 1780 | "whoami", 1781 | ] 1782 | 1783 | [[package]] 1784 | name = "sqlx-postgres" 1785 | version = "0.8.5" 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" 1787 | checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" 1788 | dependencies = [ 1789 | "atoi", 1790 | "base64", 1791 | "bitflags", 1792 | "byteorder", 1793 | "chrono", 1794 | "crc", 1795 | "dotenvy", 1796 | "etcetera", 1797 | "futures-channel", 1798 | "futures-core", 1799 | "futures-util", 1800 | "hex", 1801 | "hkdf", 1802 | "hmac", 1803 | "home", 1804 | "itoa", 1805 | "log", 1806 | "md-5", 1807 | "memchr", 1808 | "once_cell", 1809 | "rand", 1810 | "rust_decimal", 1811 | "serde", 1812 | "serde_json", 1813 | "sha2", 1814 | "smallvec", 1815 | "sqlx-core", 1816 | "stringprep", 1817 | "thiserror", 1818 | "tracing", 1819 | "whoami", 1820 | ] 1821 | 1822 | [[package]] 1823 | name = "sqlx-sqlite" 1824 | version = "0.8.5" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" 1827 | dependencies = [ 1828 | "atoi", 1829 | "chrono", 1830 | "flume", 1831 | "futures-channel", 1832 | "futures-core", 1833 | "futures-executor", 1834 | "futures-intrusive", 1835 | "futures-util", 1836 | "libsqlite3-sys", 1837 | "log", 1838 | "percent-encoding", 1839 | "serde", 1840 | "serde_urlencoded", 1841 | "sqlx-core", 1842 | "thiserror", 1843 | "tracing", 1844 | "url", 1845 | ] 1846 | 1847 | [[package]] 1848 | name = "stable_deref_trait" 1849 | version = "1.2.0" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1852 | 1853 | [[package]] 1854 | name = "stringprep" 1855 | version = "0.1.5" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 1858 | dependencies = [ 1859 | "unicode-bidi", 1860 | "unicode-normalization", 1861 | "unicode-properties", 1862 | ] 1863 | 1864 | [[package]] 1865 | name = "subtle" 1866 | version = "2.6.1" 1867 | source = "registry+https://github.com/rust-lang/crates.io-index" 1868 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1869 | 1870 | [[package]] 1871 | name = "syn" 1872 | version = "1.0.109" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1875 | dependencies = [ 1876 | "proc-macro2", 1877 | "quote", 1878 | "unicode-ident", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "syn" 1883 | version = "2.0.100" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 1886 | dependencies = [ 1887 | "proc-macro2", 1888 | "quote", 1889 | "unicode-ident", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "synstructure" 1894 | version = "0.13.1" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1897 | dependencies = [ 1898 | "proc-macro2", 1899 | "quote", 1900 | "syn 2.0.100", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "tap" 1905 | version = "1.0.1" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1908 | 1909 | [[package]] 1910 | name = "tempfile" 1911 | version = "3.19.1" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 1914 | dependencies = [ 1915 | "fastrand", 1916 | "getrandom 0.3.2", 1917 | "once_cell", 1918 | "rustix", 1919 | "windows-sys 0.59.0", 1920 | ] 1921 | 1922 | [[package]] 1923 | name = "thiserror" 1924 | version = "2.0.12" 1925 | source = "registry+https://github.com/rust-lang/crates.io-index" 1926 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1927 | dependencies = [ 1928 | "thiserror-impl", 1929 | ] 1930 | 1931 | [[package]] 1932 | name = "thiserror-impl" 1933 | version = "2.0.12" 1934 | source = "registry+https://github.com/rust-lang/crates.io-index" 1935 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1936 | dependencies = [ 1937 | "proc-macro2", 1938 | "quote", 1939 | "syn 2.0.100", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "tinystr" 1944 | version = "0.7.6" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1947 | dependencies = [ 1948 | "displaydoc", 1949 | "zerovec", 1950 | ] 1951 | 1952 | [[package]] 1953 | name = "tinyvec" 1954 | version = "1.9.0" 1955 | source = "registry+https://github.com/rust-lang/crates.io-index" 1956 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 1957 | dependencies = [ 1958 | "tinyvec_macros", 1959 | ] 1960 | 1961 | [[package]] 1962 | name = "tinyvec_macros" 1963 | version = "0.1.1" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1966 | 1967 | [[package]] 1968 | name = "tokio" 1969 | version = "1.44.2" 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" 1971 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 1972 | dependencies = [ 1973 | "backtrace", 1974 | "bytes", 1975 | "libc", 1976 | "mio", 1977 | "pin-project-lite", 1978 | "socket2", 1979 | "tokio-macros", 1980 | "windows-sys 0.52.0", 1981 | ] 1982 | 1983 | [[package]] 1984 | name = "tokio-macros" 1985 | version = "2.5.0" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1988 | dependencies = [ 1989 | "proc-macro2", 1990 | "quote", 1991 | "syn 2.0.100", 1992 | ] 1993 | 1994 | [[package]] 1995 | name = "tokio-stream" 1996 | version = "0.1.17" 1997 | source = "registry+https://github.com/rust-lang/crates.io-index" 1998 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 1999 | dependencies = [ 2000 | "futures-core", 2001 | "pin-project-lite", 2002 | "tokio", 2003 | ] 2004 | 2005 | [[package]] 2006 | name = "tokio-util" 2007 | version = "0.7.14" 2008 | source = "registry+https://github.com/rust-lang/crates.io-index" 2009 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" 2010 | dependencies = [ 2011 | "bytes", 2012 | "futures-core", 2013 | "futures-sink", 2014 | "futures-util", 2015 | "hashbrown 0.14.5", 2016 | "pin-project-lite", 2017 | "tokio", 2018 | ] 2019 | 2020 | [[package]] 2021 | name = "toml_datetime" 2022 | version = "0.6.8" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 2025 | 2026 | [[package]] 2027 | name = "toml_edit" 2028 | version = "0.22.24" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 2031 | dependencies = [ 2032 | "indexmap", 2033 | "toml_datetime", 2034 | "winnow", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "tracing" 2039 | version = "0.1.41" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2042 | dependencies = [ 2043 | "log", 2044 | "pin-project-lite", 2045 | "tracing-attributes", 2046 | "tracing-core", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "tracing-attributes" 2051 | version = "0.1.28" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2054 | dependencies = [ 2055 | "proc-macro2", 2056 | "quote", 2057 | "syn 2.0.100", 2058 | ] 2059 | 2060 | [[package]] 2061 | name = "tracing-core" 2062 | version = "0.1.33" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2065 | dependencies = [ 2066 | "once_cell", 2067 | ] 2068 | 2069 | [[package]] 2070 | name = "typenum" 2071 | version = "1.18.0" 2072 | source = "registry+https://github.com/rust-lang/crates.io-index" 2073 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2074 | 2075 | [[package]] 2076 | name = "unicode-bidi" 2077 | version = "0.3.18" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 2080 | 2081 | [[package]] 2082 | name = "unicode-ident" 2083 | version = "1.0.18" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2086 | 2087 | [[package]] 2088 | name = "unicode-normalization" 2089 | version = "0.1.24" 2090 | source = "registry+https://github.com/rust-lang/crates.io-index" 2091 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 2092 | dependencies = [ 2093 | "tinyvec", 2094 | ] 2095 | 2096 | [[package]] 2097 | name = "unicode-properties" 2098 | version = "0.1.3" 2099 | source = "registry+https://github.com/rust-lang/crates.io-index" 2100 | checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 2101 | 2102 | [[package]] 2103 | name = "unicode-xid" 2104 | version = "0.2.6" 2105 | source = "registry+https://github.com/rust-lang/crates.io-index" 2106 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 2107 | 2108 | [[package]] 2109 | name = "url" 2110 | version = "2.5.4" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2113 | dependencies = [ 2114 | "form_urlencoded", 2115 | "idna", 2116 | "percent-encoding", 2117 | ] 2118 | 2119 | [[package]] 2120 | name = "utf16_iter" 2121 | version = "1.0.5" 2122 | source = "registry+https://github.com/rust-lang/crates.io-index" 2123 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2124 | 2125 | [[package]] 2126 | name = "utf8_iter" 2127 | version = "1.0.4" 2128 | source = "registry+https://github.com/rust-lang/crates.io-index" 2129 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2130 | 2131 | [[package]] 2132 | name = "uuid" 2133 | version = "1.16.0" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" 2136 | 2137 | [[package]] 2138 | name = "vcpkg" 2139 | version = "0.2.15" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2142 | 2143 | [[package]] 2144 | name = "version_check" 2145 | version = "0.9.5" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2148 | 2149 | [[package]] 2150 | name = "wasi" 2151 | version = "0.11.0+wasi-snapshot-preview1" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2154 | 2155 | [[package]] 2156 | name = "wasi" 2157 | version = "0.14.2+wasi-0.2.4" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2160 | dependencies = [ 2161 | "wit-bindgen-rt", 2162 | ] 2163 | 2164 | [[package]] 2165 | name = "wasite" 2166 | version = "0.1.0" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 2169 | 2170 | [[package]] 2171 | name = "wasm-bindgen" 2172 | version = "0.2.100" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2175 | dependencies = [ 2176 | "cfg-if", 2177 | "once_cell", 2178 | "rustversion", 2179 | "wasm-bindgen-macro", 2180 | ] 2181 | 2182 | [[package]] 2183 | name = "wasm-bindgen-backend" 2184 | version = "0.2.100" 2185 | source = "registry+https://github.com/rust-lang/crates.io-index" 2186 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2187 | dependencies = [ 2188 | "bumpalo", 2189 | "log", 2190 | "proc-macro2", 2191 | "quote", 2192 | "syn 2.0.100", 2193 | "wasm-bindgen-shared", 2194 | ] 2195 | 2196 | [[package]] 2197 | name = "wasm-bindgen-macro" 2198 | version = "0.2.100" 2199 | source = "registry+https://github.com/rust-lang/crates.io-index" 2200 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2201 | dependencies = [ 2202 | "quote", 2203 | "wasm-bindgen-macro-support", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "wasm-bindgen-macro-support" 2208 | version = "0.2.100" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2211 | dependencies = [ 2212 | "proc-macro2", 2213 | "quote", 2214 | "syn 2.0.100", 2215 | "wasm-bindgen-backend", 2216 | "wasm-bindgen-shared", 2217 | ] 2218 | 2219 | [[package]] 2220 | name = "wasm-bindgen-shared" 2221 | version = "0.2.100" 2222 | source = "registry+https://github.com/rust-lang/crates.io-index" 2223 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2224 | dependencies = [ 2225 | "unicode-ident", 2226 | ] 2227 | 2228 | [[package]] 2229 | name = "whoami" 2230 | version = "1.6.0" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" 2233 | dependencies = [ 2234 | "redox_syscall", 2235 | "wasite", 2236 | ] 2237 | 2238 | [[package]] 2239 | name = "windows-core" 2240 | version = "0.61.0" 2241 | source = "registry+https://github.com/rust-lang/crates.io-index" 2242 | checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" 2243 | dependencies = [ 2244 | "windows-implement", 2245 | "windows-interface", 2246 | "windows-link", 2247 | "windows-result", 2248 | "windows-strings", 2249 | ] 2250 | 2251 | [[package]] 2252 | name = "windows-implement" 2253 | version = "0.60.0" 2254 | source = "registry+https://github.com/rust-lang/crates.io-index" 2255 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2256 | dependencies = [ 2257 | "proc-macro2", 2258 | "quote", 2259 | "syn 2.0.100", 2260 | ] 2261 | 2262 | [[package]] 2263 | name = "windows-interface" 2264 | version = "0.59.1" 2265 | source = "registry+https://github.com/rust-lang/crates.io-index" 2266 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2267 | dependencies = [ 2268 | "proc-macro2", 2269 | "quote", 2270 | "syn 2.0.100", 2271 | ] 2272 | 2273 | [[package]] 2274 | name = "windows-link" 2275 | version = "0.1.1" 2276 | source = "registry+https://github.com/rust-lang/crates.io-index" 2277 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2278 | 2279 | [[package]] 2280 | name = "windows-result" 2281 | version = "0.3.2" 2282 | source = "registry+https://github.com/rust-lang/crates.io-index" 2283 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 2284 | dependencies = [ 2285 | "windows-link", 2286 | ] 2287 | 2288 | [[package]] 2289 | name = "windows-strings" 2290 | version = "0.4.0" 2291 | source = "registry+https://github.com/rust-lang/crates.io-index" 2292 | checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" 2293 | dependencies = [ 2294 | "windows-link", 2295 | ] 2296 | 2297 | [[package]] 2298 | name = "windows-sys" 2299 | version = "0.48.0" 2300 | source = "registry+https://github.com/rust-lang/crates.io-index" 2301 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2302 | dependencies = [ 2303 | "windows-targets 0.48.5", 2304 | ] 2305 | 2306 | [[package]] 2307 | name = "windows-sys" 2308 | version = "0.52.0" 2309 | source = "registry+https://github.com/rust-lang/crates.io-index" 2310 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2311 | dependencies = [ 2312 | "windows-targets 0.52.6", 2313 | ] 2314 | 2315 | [[package]] 2316 | name = "windows-sys" 2317 | version = "0.59.0" 2318 | source = "registry+https://github.com/rust-lang/crates.io-index" 2319 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2320 | dependencies = [ 2321 | "windows-targets 0.52.6", 2322 | ] 2323 | 2324 | [[package]] 2325 | name = "windows-targets" 2326 | version = "0.48.5" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2329 | dependencies = [ 2330 | "windows_aarch64_gnullvm 0.48.5", 2331 | "windows_aarch64_msvc 0.48.5", 2332 | "windows_i686_gnu 0.48.5", 2333 | "windows_i686_msvc 0.48.5", 2334 | "windows_x86_64_gnu 0.48.5", 2335 | "windows_x86_64_gnullvm 0.48.5", 2336 | "windows_x86_64_msvc 0.48.5", 2337 | ] 2338 | 2339 | [[package]] 2340 | name = "windows-targets" 2341 | version = "0.52.6" 2342 | source = "registry+https://github.com/rust-lang/crates.io-index" 2343 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2344 | dependencies = [ 2345 | "windows_aarch64_gnullvm 0.52.6", 2346 | "windows_aarch64_msvc 0.52.6", 2347 | "windows_i686_gnu 0.52.6", 2348 | "windows_i686_gnullvm", 2349 | "windows_i686_msvc 0.52.6", 2350 | "windows_x86_64_gnu 0.52.6", 2351 | "windows_x86_64_gnullvm 0.52.6", 2352 | "windows_x86_64_msvc 0.52.6", 2353 | ] 2354 | 2355 | [[package]] 2356 | name = "windows_aarch64_gnullvm" 2357 | version = "0.48.5" 2358 | source = "registry+https://github.com/rust-lang/crates.io-index" 2359 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2360 | 2361 | [[package]] 2362 | name = "windows_aarch64_gnullvm" 2363 | version = "0.52.6" 2364 | source = "registry+https://github.com/rust-lang/crates.io-index" 2365 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2366 | 2367 | [[package]] 2368 | name = "windows_aarch64_msvc" 2369 | version = "0.48.5" 2370 | source = "registry+https://github.com/rust-lang/crates.io-index" 2371 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2372 | 2373 | [[package]] 2374 | name = "windows_aarch64_msvc" 2375 | version = "0.52.6" 2376 | source = "registry+https://github.com/rust-lang/crates.io-index" 2377 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2378 | 2379 | [[package]] 2380 | name = "windows_i686_gnu" 2381 | version = "0.48.5" 2382 | source = "registry+https://github.com/rust-lang/crates.io-index" 2383 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2384 | 2385 | [[package]] 2386 | name = "windows_i686_gnu" 2387 | version = "0.52.6" 2388 | source = "registry+https://github.com/rust-lang/crates.io-index" 2389 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2390 | 2391 | [[package]] 2392 | name = "windows_i686_gnullvm" 2393 | version = "0.52.6" 2394 | source = "registry+https://github.com/rust-lang/crates.io-index" 2395 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2396 | 2397 | [[package]] 2398 | name = "windows_i686_msvc" 2399 | version = "0.48.5" 2400 | source = "registry+https://github.com/rust-lang/crates.io-index" 2401 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2402 | 2403 | [[package]] 2404 | name = "windows_i686_msvc" 2405 | version = "0.52.6" 2406 | source = "registry+https://github.com/rust-lang/crates.io-index" 2407 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2408 | 2409 | [[package]] 2410 | name = "windows_x86_64_gnu" 2411 | version = "0.48.5" 2412 | source = "registry+https://github.com/rust-lang/crates.io-index" 2413 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2414 | 2415 | [[package]] 2416 | name = "windows_x86_64_gnu" 2417 | version = "0.52.6" 2418 | source = "registry+https://github.com/rust-lang/crates.io-index" 2419 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2420 | 2421 | [[package]] 2422 | name = "windows_x86_64_gnullvm" 2423 | version = "0.48.5" 2424 | source = "registry+https://github.com/rust-lang/crates.io-index" 2425 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2426 | 2427 | [[package]] 2428 | name = "windows_x86_64_gnullvm" 2429 | version = "0.52.6" 2430 | source = "registry+https://github.com/rust-lang/crates.io-index" 2431 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2432 | 2433 | [[package]] 2434 | name = "windows_x86_64_msvc" 2435 | version = "0.48.5" 2436 | source = "registry+https://github.com/rust-lang/crates.io-index" 2437 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2438 | 2439 | [[package]] 2440 | name = "windows_x86_64_msvc" 2441 | version = "0.52.6" 2442 | source = "registry+https://github.com/rust-lang/crates.io-index" 2443 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2444 | 2445 | [[package]] 2446 | name = "winnow" 2447 | version = "0.7.6" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" 2450 | dependencies = [ 2451 | "memchr", 2452 | ] 2453 | 2454 | [[package]] 2455 | name = "wit-bindgen-rt" 2456 | version = "0.39.0" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2459 | dependencies = [ 2460 | "bitflags", 2461 | ] 2462 | 2463 | [[package]] 2464 | name = "write16" 2465 | version = "1.0.0" 2466 | source = "registry+https://github.com/rust-lang/crates.io-index" 2467 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2468 | 2469 | [[package]] 2470 | name = "writeable" 2471 | version = "0.5.5" 2472 | source = "registry+https://github.com/rust-lang/crates.io-index" 2473 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2474 | 2475 | [[package]] 2476 | name = "wyz" 2477 | version = "0.5.1" 2478 | source = "registry+https://github.com/rust-lang/crates.io-index" 2479 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 2480 | dependencies = [ 2481 | "tap", 2482 | ] 2483 | 2484 | [[package]] 2485 | name = "yoke" 2486 | version = "0.7.5" 2487 | source = "registry+https://github.com/rust-lang/crates.io-index" 2488 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2489 | dependencies = [ 2490 | "serde", 2491 | "stable_deref_trait", 2492 | "yoke-derive", 2493 | "zerofrom", 2494 | ] 2495 | 2496 | [[package]] 2497 | name = "yoke-derive" 2498 | version = "0.7.5" 2499 | source = "registry+https://github.com/rust-lang/crates.io-index" 2500 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2501 | dependencies = [ 2502 | "proc-macro2", 2503 | "quote", 2504 | "syn 2.0.100", 2505 | "synstructure", 2506 | ] 2507 | 2508 | [[package]] 2509 | name = "zerocopy" 2510 | version = "0.8.24" 2511 | source = "registry+https://github.com/rust-lang/crates.io-index" 2512 | checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" 2513 | dependencies = [ 2514 | "zerocopy-derive", 2515 | ] 2516 | 2517 | [[package]] 2518 | name = "zerocopy-derive" 2519 | version = "0.8.24" 2520 | source = "registry+https://github.com/rust-lang/crates.io-index" 2521 | checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" 2522 | dependencies = [ 2523 | "proc-macro2", 2524 | "quote", 2525 | "syn 2.0.100", 2526 | ] 2527 | 2528 | [[package]] 2529 | name = "zerofrom" 2530 | version = "0.1.6" 2531 | source = "registry+https://github.com/rust-lang/crates.io-index" 2532 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2533 | dependencies = [ 2534 | "zerofrom-derive", 2535 | ] 2536 | 2537 | [[package]] 2538 | name = "zerofrom-derive" 2539 | version = "0.1.6" 2540 | source = "registry+https://github.com/rust-lang/crates.io-index" 2541 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2542 | dependencies = [ 2543 | "proc-macro2", 2544 | "quote", 2545 | "syn 2.0.100", 2546 | "synstructure", 2547 | ] 2548 | 2549 | [[package]] 2550 | name = "zeroize" 2551 | version = "1.8.1" 2552 | source = "registry+https://github.com/rust-lang/crates.io-index" 2553 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2554 | 2555 | [[package]] 2556 | name = "zerovec" 2557 | version = "0.10.4" 2558 | source = "registry+https://github.com/rust-lang/crates.io-index" 2559 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2560 | dependencies = [ 2561 | "yoke", 2562 | "zerofrom", 2563 | "zerovec-derive", 2564 | ] 2565 | 2566 | [[package]] 2567 | name = "zerovec-derive" 2568 | version = "0.10.3" 2569 | source = "registry+https://github.com/rust-lang/crates.io-index" 2570 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2571 | dependencies = [ 2572 | "proc-macro2", 2573 | "quote", 2574 | "syn 2.0.100", 2575 | ] 2576 | -------------------------------------------------------------------------------- /mysql/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "goobie-mysql" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "gmsv_goobie_mysql" 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | gmod = { git = "https://github.com/Srlion/gmod-rs.git", rev = "b0ca2f1" } 12 | tokio = { version = "1.44.2", default-features = false, features = [ 13 | "rt-multi-thread", 14 | "macros", 15 | ] } 16 | anyhow = "1.0.96" 17 | sqlx = { version = "0.8.5", features = [ 18 | "runtime-tokio", 19 | "tls-native-tls", 20 | "mysql", 21 | "rust_decimal", 22 | "chrono", 23 | ] } 24 | atomic_enum = "0.3.0" 25 | openssl-sys = { version = "0.9.107", features = ["vendored"] } 26 | const_format = "0.2.34" 27 | tokio-util = { version = "0.7.14", features = ["rt"] } 28 | 29 | [profile.release] 30 | opt-level = 3 31 | lto = "fat" 32 | codegen-units = 1 33 | strip = true 34 | panic = "abort" 35 | -------------------------------------------------------------------------------- /mysql/rust-toolchain: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /mysql/src/conn/connect.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | self, 3 | sync::{atomic::Ordering, Arc}, 4 | }; 5 | 6 | use gmod::lua::*; 7 | use sqlx::{mysql::MySqlConnection, Connection}; 8 | 9 | use super::{state::State, ConnMeta}; 10 | use crate::error::handle_error; 11 | 12 | #[inline(always)] 13 | pub async fn connect( 14 | conn: &mut Option, 15 | meta: &Arc, 16 | callback: LuaReference, 17 | ) -> bool { 18 | if let Some(old_conn) = conn.take() { 19 | // let's gracefully close the connection if there is any 20 | // we don't care if it fails, as we are reconnecting anyway 21 | let _ = old_conn.close().await; 22 | } 23 | 24 | meta.set_state(State::Connecting); 25 | 26 | let res = match MySqlConnection::connect_with(&meta.opts.inner).await { 27 | Ok(new_conn) => { 28 | *conn = Some(new_conn); 29 | meta.id.fetch_add(1, Ordering::Release); // increment the id 30 | meta.set_state(State::Connected); 31 | Ok(()) 32 | } 33 | Err(e) => { 34 | meta.set_state(State::NotConnected); 35 | Err(e) 36 | } 37 | }; 38 | 39 | if callback == LUA_NOREF { 40 | match res { 41 | Ok(_) => return true, 42 | Err(_) => return false, 43 | }; 44 | } 45 | 46 | meta.task_queue.add(move |l| { 47 | match res { 48 | Ok(_) => { 49 | l.pcall_ignore_func_ref(callback, || 0); 50 | } 51 | Err(e) => { 52 | l.pcall_ignore_func_ref(callback, || { 53 | handle_error(&l, &e.into()); // this will push the error to the stack 54 | 0 55 | }); 56 | } 57 | }; 58 | }); 59 | 60 | true 61 | } 62 | -------------------------------------------------------------------------------- /mysql/src/conn/disconnect.rs: -------------------------------------------------------------------------------- 1 | use std::{self, sync::Arc}; 2 | 3 | use gmod::lua::*; 4 | use sqlx::{mysql::MySqlConnection, Connection}; 5 | 6 | use super::{state::State, ConnMeta}; 7 | use crate::error::handle_error; 8 | 9 | #[inline(always)] 10 | pub async fn disconnect( 11 | conn: &mut Option, 12 | meta: &Arc, 13 | callback: LuaReference, 14 | ) { 15 | meta.set_state(State::Disconnected); 16 | 17 | let res = match conn.take() { 18 | Some(old_conn) => old_conn.close().await, 19 | None => Ok(()), 20 | }; 21 | 22 | meta.task_queue.add(move |l| { 23 | match res { 24 | Ok(_) => { 25 | l.pcall_ignore_func_ref(callback, || 0); 26 | } 27 | Err(e) => { 28 | l.pcall_ignore_func_ref(callback, || { 29 | handle_error(&l, &e.into()); // this will push the error to the stack 30 | 0 31 | }); 32 | } 33 | }; 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /mysql/src/conn/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | self, 3 | sync::{ 4 | atomic::{AtomicUsize, Ordering}, 5 | Arc, 6 | }, 7 | }; 8 | 9 | use anyhow::Result; 10 | use gmod::{lua::*, rstruct::RStruct, task_queue::TaskQueue, *}; 11 | use sqlx::mysql::MySqlConnection; 12 | use tokio::sync::mpsc; 13 | 14 | mod connect; 15 | mod disconnect; 16 | mod options; 17 | mod ping; 18 | mod query; 19 | pub mod state; 20 | 21 | use options::Options as ConnectOptions; 22 | use state::{AtomicState, State}; 23 | 24 | use crate::{cstr_from_args, run_async, GLOBAL_TABLE_NAME, GLOBAL_TABLE_NAME_C}; 25 | 26 | const META_TABLE_NAME: LuaCStr = cstr_from_args!(GLOBAL_TABLE_NAME, "_connection"); 27 | 28 | enum ConnMessage { 29 | Connect(LuaReference), 30 | Disconnect(LuaReference), 31 | Query(crate::query::Query), 32 | Ping(LuaReference), 33 | Close, 34 | } 35 | 36 | pub struct ConnMeta { 37 | // each connection needs a unique id for each inner connection 38 | // this is to be used for transactions to know if they are still in a transaction or not 39 | // if it's a new connection, it's not in a transaction, so it MUST forget about it 40 | // we don't use the state alone because it could switch back to Connected quickly and the 41 | // transaction would think it's still in a transaction 42 | id: AtomicUsize, 43 | state: AtomicState, 44 | opts: ConnectOptions, 45 | task_queue: TaskQueue, 46 | } 47 | 48 | impl ConnMeta { 49 | pub fn set_state(&self, state: State) { 50 | self.state.store(state, Ordering::Release); 51 | } 52 | } 53 | 54 | pub struct Conn { 55 | meta: Arc, 56 | sender: mpsc::UnboundedSender, 57 | } 58 | 59 | impl Conn { 60 | pub fn new(l: lua::State, opts: ConnectOptions) -> Self { 61 | let (sender, mut receiver) = mpsc::unbounded_channel(); 62 | 63 | let conn = Conn { 64 | meta: Arc::new(ConnMeta { 65 | id: AtomicUsize::new(0), 66 | state: AtomicState::new(State::NotConnected), 67 | opts, 68 | task_queue: TaskQueue::new(l), 69 | }), 70 | sender, 71 | }; 72 | 73 | let meta = conn.meta.clone(); 74 | run_async(async move { 75 | let mut db_conn: Option = None; 76 | while let Some(msg) = receiver.recv().await { 77 | match msg { 78 | ConnMessage::Connect(callback) => { 79 | // result is handed off to the query callback 80 | let _ = connect::connect(&mut db_conn, &meta, callback).await; 81 | } 82 | ConnMessage::Disconnect(callback) => { 83 | disconnect::disconnect(&mut db_conn, &meta, callback).await 84 | } 85 | ConnMessage::Query(query) => { 86 | query::query(&mut db_conn, &meta, query).await; 87 | } 88 | ConnMessage::Ping(callback) => { 89 | ping::ping(&mut db_conn, &meta, callback).await; 90 | } 91 | // This should be called after "disconnect" 92 | ConnMessage::Close => { 93 | break; 94 | } 95 | } 96 | } 97 | }); 98 | 99 | conn 100 | } 101 | 102 | #[inline] 103 | fn id(&self) -> usize { 104 | self.meta.id.load(Ordering::Acquire) 105 | } 106 | 107 | #[inline] 108 | fn state(&self) -> State { 109 | self.meta.state.load(Ordering::Acquire) 110 | } 111 | 112 | #[inline] 113 | fn poll(&self, l: lua::State) { 114 | self.meta.task_queue.poll(l); 115 | } 116 | } 117 | 118 | register_lua_rstruct!( 119 | Conn, 120 | META_TABLE_NAME, 121 | &[ 122 | (c"Poll", poll), 123 | // 124 | (c"Start", start_connect), 125 | (c"Disconnect", start_disconnect), 126 | // 127 | (c"State", get_state), 128 | (c"Ping", ping), 129 | // 130 | (c"Run", run), 131 | (c"Execute", execute), 132 | (c"FetchOne", fetch_one), 133 | (c"Fetch", fetch), 134 | // 135 | (c"ID", get_id), 136 | (c"Host", get_host), 137 | (c"Port", get_port), 138 | // 139 | (c"__tostring", __tostring), 140 | ] 141 | ); 142 | 143 | pub fn on_gmod_open(l: lua::State) { 144 | state::setup(l); 145 | 146 | l.get_global(GLOBAL_TABLE_NAME_C); 147 | l.get_metatable_name(META_TABLE_NAME); 148 | l.set_field(-2, c"CONN_META"); 149 | l.pop(); // pop the global table 150 | } 151 | 152 | impl std::fmt::Display for Conn { 153 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 154 | write!( 155 | f, 156 | "Goobie MySQL Connection [ID: {} | IP: {} | Port: {} | State: {}]", 157 | self.id(), 158 | self.meta.opts.inner.get_host(), 159 | self.meta.opts.inner.get_port(), 160 | self.state() 161 | ) 162 | } 163 | } 164 | 165 | impl Drop for Conn { 166 | fn drop(&mut self) { 167 | let _ = self 168 | .sender 169 | .send(ConnMessage::Disconnect(LUA_NOREF)); 170 | let _ = self.sender.send(ConnMessage::Close); 171 | } 172 | } 173 | 174 | #[lua_function] 175 | pub fn new_conn(l: lua::State) -> Result { 176 | let mut opts = ConnectOptions::new(); 177 | opts.parse(l)?; 178 | 179 | l.pop(); 180 | 181 | let conn = Conn::new(l, opts); 182 | l.push_struct(conn); 183 | 184 | Ok(1) 185 | } 186 | 187 | #[lua_function] 188 | fn poll(l: lua::State) -> Result { 189 | let conn = l.get_struct::(1)?; 190 | conn.poll(l); 191 | Ok(0) 192 | } 193 | 194 | #[lua_function] 195 | fn start_connect(l: lua::State) -> Result { 196 | let conn = l.get_struct::(1)?; 197 | let callback_ref = l.check_function(2)?; 198 | 199 | let _ = conn 200 | .sender 201 | .send(ConnMessage::Connect(callback_ref)); 202 | 203 | Ok(0) 204 | } 205 | 206 | #[lua_function] 207 | fn start_disconnect(l: lua::State) -> Result { 208 | let conn = l.get_struct::(1)?; 209 | let callback_ref = l.check_function(2)?; 210 | 211 | let _ = conn 212 | .sender 213 | .send(ConnMessage::Disconnect(callback_ref)); 214 | 215 | Ok(0) 216 | } 217 | 218 | fn start_query(l: lua::State, query_type: crate::query::QueryType) -> Result { 219 | let conn = l.get_struct::(1)?; 220 | 221 | let mut on_error = LUA_NOREF; 222 | l.get_field(1, c"on_error"); 223 | if l.is_function(-1) { 224 | on_error = l.reference(); 225 | } else { 226 | l.pop(); // pop the nil 227 | } 228 | 229 | let query_str = l.check_string(2)?; 230 | let mut query = crate::query::Query::new(query_str, query_type, on_error); 231 | query.parse_options(l, 3)?; 232 | 233 | let _ = conn.sender.send(ConnMessage::Query(query)); 234 | 235 | Ok(0) 236 | } 237 | 238 | #[lua_function] 239 | fn run(l: lua::State) -> Result { 240 | start_query(l, crate::query::QueryType::Run) 241 | } 242 | 243 | #[lua_function] 244 | fn execute(l: lua::State) -> Result { 245 | start_query(l, crate::query::QueryType::Execute) 246 | } 247 | 248 | #[lua_function] 249 | fn fetch_one(l: lua::State) -> Result { 250 | start_query(l, crate::query::QueryType::FetchOne) 251 | } 252 | 253 | #[lua_function] 254 | fn fetch(l: lua::State) -> Result { 255 | start_query(l, crate::query::QueryType::FetchAll) 256 | } 257 | 258 | #[lua_function] 259 | fn get_state(l: lua::State) -> Result { 260 | let conn = l.get_struct::(1)?; 261 | l.push_number(conn.state().to_usize()); 262 | Ok(1) 263 | } 264 | 265 | #[lua_function] 266 | fn ping(l: lua::State) -> Result { 267 | let conn = l.get_struct::(1)?; 268 | let callback_ref = l.check_function(2)?; 269 | 270 | let _ = conn.sender.send(ConnMessage::Ping(callback_ref)); 271 | 272 | Ok(0) 273 | } 274 | 275 | #[lua_function] 276 | fn get_id(l: lua::State) -> Result { 277 | let conn = l.get_struct::(1)?; 278 | let id = conn.meta.id.load(Ordering::Acquire); 279 | l.push_number(id); 280 | Ok(1) 281 | } 282 | 283 | #[lua_function] 284 | fn get_host(l: lua::State) -> Result { 285 | let conn = l.get_struct::(1)?; 286 | l.push_string(conn.meta.opts.inner.get_host()); 287 | Ok(1) 288 | } 289 | 290 | #[lua_function] 291 | fn get_port(l: lua::State) -> Result { 292 | let conn = l.get_struct::(1)?; 293 | l.push_number(conn.meta.opts.inner.get_port()); 294 | Ok(1) 295 | } 296 | 297 | #[lua_function] 298 | fn __tostring(l: lua::State) -> Result { 299 | let conn = l.get_struct::(1)?; 300 | l.push_string(&conn.to_string()); 301 | Ok(1) 302 | } 303 | -------------------------------------------------------------------------------- /mysql/src/conn/options.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use gmod::{lua::*, *}; 3 | use sqlx::mysql::MySqlConnectOptions; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Options { 7 | pub inner: MySqlConnectOptions, 8 | } 9 | 10 | impl Options { 11 | pub fn new() -> Self { 12 | Options { 13 | inner: MySqlConnectOptions::new(), 14 | } 15 | } 16 | 17 | pub fn parse(&mut self, l: lua::State) -> Result<()> { 18 | // if first argument is a string then it's a uri and increment arg_number as next argument has to be a table or nil 19 | l.check_table(1)?; 20 | 21 | self.parse_uri_options(l, 1)?; 22 | // self.parse_on_fns(l, 1)?; 23 | self.parse_connect_options(l, 1)?; 24 | 25 | Ok(()) 26 | } 27 | 28 | // fn parse_on_fns(&mut self, l: lua::State, arg_n: i32) -> Result<()> { 29 | // // if l.get_field_type_or_nil(arg_n, c"on_error", LUA_TFUNCTION)? { 30 | // // self.on_error = l.reference(); 31 | // // } 32 | // 33 | // Ok(()) 34 | // } 35 | 36 | fn parse_uri_options(&mut self, l: lua::State, arg_n: i32) -> Result<()> { 37 | if l.get_field_type_or_nil(arg_n, c"uri", LUA_TSTRING)? { 38 | let uri = l.get_string_unchecked(-1); 39 | self.inner = uri.parse()?; 40 | l.pop(); 41 | } 42 | 43 | if l.get_field_type_or_nil(arg_n, c"host", LUA_TSTRING)? 44 | || l.get_field_type_or_nil(arg_n, c"hostname", LUA_TSTRING)? 45 | { 46 | let hot = l.get_string_unchecked(-1); // 😲 47 | self.inner = self.inner.clone().host(&hot); 48 | l.pop(); 49 | } 50 | 51 | if l.get_field_type_or_nil(arg_n, c"port", LUA_TNUMBER)? { 52 | let port = l.to_number(-1) as u16; 53 | self.inner = self.inner.clone().port(port); 54 | l.pop(); 55 | } 56 | 57 | if l.get_field_type_or_nil(arg_n, c"username", LUA_TSTRING)? 58 | || l.get_field_type_or_nil(arg_n, c"user", LUA_TSTRING)? 59 | { 60 | let user = l.get_string_unchecked(-1); 61 | self.inner = self.inner.clone().username(&user); 62 | l.pop(); 63 | } 64 | 65 | if l.get_field_type_or_nil(arg_n, c"password", LUA_TSTRING)? { 66 | let pass = l.get_string_unchecked(-1); 67 | self.inner = self.inner.clone().password(&pass); 68 | l.pop(); 69 | } 70 | 71 | if l.get_field_type_or_nil(arg_n, c"database", LUA_TSTRING)? 72 | || l.get_field_type_or_nil(arg_n, c"db", LUA_TSTRING)? 73 | { 74 | let db = l.get_string_unchecked(-1); 75 | self.inner = self.inner.clone().database(db.as_ref()); 76 | l.pop(); 77 | } 78 | 79 | if self.inner.get_database().is_none() { 80 | bail!("Database name is required!"); 81 | } 82 | 83 | Ok(()) 84 | } 85 | 86 | fn parse_connect_options(&mut self, l: lua::State, arg_n: i32) -> Result<()> { 87 | if l.get_field_type_or_nil(arg_n, c"charset", LUA_TSTRING)? { 88 | let charset = l.get_string_unchecked(-1); 89 | self.inner = self.inner.clone().charset(&charset); 90 | l.pop(); 91 | } 92 | 93 | if l.get_field_type_or_nil(arg_n, c"collation", LUA_TSTRING)? { 94 | let collation = l.get_string_unchecked(-1); 95 | self.inner = self.inner.clone().collation(&collation); 96 | l.pop(); 97 | } 98 | 99 | if l.get_field_type_or_nil(arg_n, c"timezone", LUA_TSTRING)? { 100 | let timezone = l.get_string_unchecked(-1); 101 | self.inner = self.inner.clone().timezone(timezone); 102 | l.pop(); 103 | } 104 | 105 | if l.get_field_type_or_nil(arg_n, c"statement_cache_capacity", LUA_TNUMBER)? { 106 | let capacity = l.to_number(-1) as usize; 107 | self.inner = self 108 | .inner 109 | .clone() 110 | .statement_cache_capacity(capacity); 111 | l.pop(); 112 | } 113 | 114 | if l.get_field_type_or_nil(arg_n, c"socket", LUA_TSTRING)? { 115 | let socket = l.get_string_unchecked(-1); 116 | self.inner = self.inner.clone().socket(socket); 117 | l.pop(); 118 | } 119 | 120 | Ok(()) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /mysql/src/conn/ping.rs: -------------------------------------------------------------------------------- 1 | use std::{self, sync::Arc}; 2 | 3 | use gmod::lua::*; 4 | use sqlx::{mysql::MySqlConnection, Connection}; 5 | 6 | use super::ConnMeta; 7 | use crate::error::handle_error; 8 | 9 | #[inline(always)] 10 | pub async fn ping( 11 | conn: &mut Option, 12 | meta: &Arc, 13 | callback: LuaReference, 14 | ) { 15 | let conn = match conn { 16 | Some(conn) => conn, 17 | None => { 18 | meta.task_queue.add(move |l| { 19 | l.pcall_ignore_func_ref(callback, || { 20 | handle_error(&l, &anyhow::anyhow!("connection is not open")); 21 | 0 22 | }); 23 | }); 24 | return; 25 | } 26 | }; 27 | let start = tokio::time::Instant::now(); 28 | let res = conn.ping().await; 29 | let latency = start.elapsed().as_micros() as f64; 30 | meta.task_queue.add(move |l| { 31 | match res { 32 | Ok(_) => { 33 | l.pcall_ignore_func_ref(callback, || { 34 | l.push_nil(); // err is nil 35 | l.push_number(latency); 36 | 0 37 | }); 38 | } 39 | Err(e) => { 40 | l.pcall_ignore_func_ref(callback, || { 41 | handle_error(&l, &e.into()); 42 | 0 43 | }); 44 | } 45 | }; 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /mysql/src/conn/query.rs: -------------------------------------------------------------------------------- 1 | use gmod::*; 2 | use sqlx::{mysql::MySqlConnection, Connection}; 3 | use std::{ 4 | self, 5 | sync::{atomic::Ordering, Arc}, 6 | time::Duration, 7 | }; 8 | 9 | use crate::{error::handle_error, print_goobie_with_host}; 10 | 11 | use super::{state::State, ConnMeta}; 12 | 13 | fn should_reconnect(e: &anyhow::Error) -> bool { 14 | let sqlx_e = match e.downcast_ref::() { 15 | Some(e) => e, 16 | None => return false, 17 | }; 18 | match sqlx_e { 19 | sqlx::Error::Io(io_err) => { 20 | let conn_dropped = matches!( 21 | io_err.kind(), 22 | std::io::ErrorKind::ConnectionRefused 23 | | std::io::ErrorKind::ConnectionReset 24 | | std::io::ErrorKind::ConnectionAborted 25 | | std::io::ErrorKind::NotConnected 26 | | std::io::ErrorKind::TimedOut 27 | | std::io::ErrorKind::BrokenPipe 28 | | std::io::ErrorKind::UnexpectedEof 29 | ); 30 | conn_dropped 31 | } 32 | sqlx::Error::Tls(tls_err) => { 33 | tls_err.to_string().contains("handshake failed") 34 | || tls_err.to_string().contains("connection closed") 35 | || tls_err.to_string().contains("unexpected EOF") 36 | } 37 | sqlx::Error::Database(db_err) => { 38 | if let Some(mysql_err) = db_err.try_downcast_ref::() { 39 | let code = mysql_err.number(); 40 | let connection_dropped = matches!( 41 | code, 42 | 2002 // Can't connect to local MySQL server (socket issues) 43 | | 2003 // Can't connect to MySQL server on 'hostname' (network issues) 44 | | 2006 // MySQL server has gone away 45 | | 2013 // Lost connection during query 46 | | 2055 // Lost connection with system error 47 | ); 48 | connection_dropped 49 | } else { 50 | false 51 | } 52 | } 53 | _ => false, 54 | } 55 | } 56 | 57 | #[inline(always)] 58 | pub async fn query( 59 | conn: &mut Option, 60 | meta: &Arc, 61 | mut query: crate::query::Query, 62 | ) { 63 | let db_conn = match conn { 64 | Some(conn) => conn, 65 | None => { 66 | meta.task_queue.add(move |l| { 67 | l.pcall_ignore_func_ref(query.callback, || { 68 | handle_error(&l, &anyhow::anyhow!("connection is not open")); 69 | 0 70 | }); 71 | }); 72 | return; 73 | } 74 | }; 75 | query.start(db_conn).await; 76 | 77 | let should_reconnect = { 78 | if let Err(e) = query.result.as_ref() { 79 | let should = should_reconnect(e); 80 | // we need to actually ping the connection, as extra validation that the connection is actually dead to not mess up with any queries 81 | if should && db_conn.ping().await.is_err() { 82 | // make sure that it's set before we return back to lua 83 | // this is a MUST because if we are inside a transaction and reconnect, lua MUST forget about the transaction 84 | // it can cause issues if we reconnect and lua thinks it's still in a transaction 85 | // we do it by changing the state AND having a unique id for each inner connection 86 | // this way a transaction can check the state AND the id to know if it's still in a transaction 87 | // if it's not, it can forget about it completely 88 | meta.state 89 | .store(State::NotConnected, Ordering::Release); 90 | print_goobie_with_host!( 91 | meta.opts.inner.get_host(), 92 | "Database connection is lost, reconnecting..." 93 | ); 94 | } 95 | should 96 | } else { 97 | false 98 | } 99 | }; 100 | 101 | // if we should reconnect, we need to let lua know that there is an error so it can handle it 102 | meta.task_queue 103 | .add(move |l| query.process_result(l)); 104 | 105 | if !should_reconnect { 106 | return; 107 | } 108 | 109 | let mut delay = Duration::from_secs(2); 110 | let mut reconnected = false; 111 | for _ in 0..7 { 112 | tokio::time::sleep(delay).await; 113 | delay += Duration::from_secs(1); 114 | if super::connect::connect(conn, meta, LUA_NOREF).await { 115 | print_goobie_with_host!(meta.opts.inner.get_host(), "Reconnected!"); 116 | reconnected = true; 117 | break; 118 | } else { 119 | print_goobie_with_host!( 120 | meta.opts.inner.get_host(), 121 | "Failed to reconnect, retrying in {} seconds...", 122 | delay.as_secs() 123 | ); 124 | } 125 | } 126 | if !reconnected { 127 | print_goobie_with_host!( 128 | meta.opts.inner.get_host(), 129 | "Failed to reconnect, giving up!", 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /mysql/src/conn/state.rs: -------------------------------------------------------------------------------- 1 | use gmod::lua; 2 | 3 | use crate::GLOBAL_TABLE_NAME_C; 4 | 5 | #[derive(PartialEq)] 6 | #[atomic_enum::atomic_enum] 7 | pub enum State { 8 | Connected = 0, 9 | Connecting = 1, 10 | NotConnected = 2, 11 | Disconnected = 3, 12 | } 13 | 14 | impl State { 15 | pub const fn to_usize(self) -> usize { 16 | self as usize 17 | } 18 | } 19 | 20 | impl std::fmt::Display for State { 21 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 22 | match self { 23 | State::Connected => write!(f, "Connected"), 24 | State::Connecting => write!(f, "Connecting"), 25 | State::NotConnected => write!(f, "Not Connected"), 26 | State::Disconnected => write!(f, "Disconnected"), 27 | } 28 | } 29 | } 30 | 31 | pub fn setup(l: lua::State) { 32 | l.get_global(GLOBAL_TABLE_NAME_C); 33 | { 34 | l.new_table(); 35 | { 36 | l.push_number(AtomicState::to_usize(State::Connected)); 37 | l.set_field(-2, c"CONNECTED"); 38 | 39 | l.push_number(AtomicState::to_usize(State::Connecting)); 40 | l.set_field(-2, c"CONNECTING"); 41 | 42 | l.push_number(AtomicState::to_usize(State::NotConnected)); 43 | l.set_field(-2, c"NOT_CONNECTED"); 44 | 45 | l.push_number(AtomicState::to_usize(State::Disconnected)); 46 | l.set_field(-2, c"DISCONNECTED"); 47 | } 48 | l.set_field(-2, c"STATES"); 49 | } 50 | l.pop(); 51 | } 52 | -------------------------------------------------------------------------------- /mysql/src/constants.rs: -------------------------------------------------------------------------------- 1 | use const_format::str_index; 2 | #[cfg(not(debug_assertions))] 3 | use const_format::{formatcp, str_replace}; 4 | use gmod::*; 5 | 6 | use crate::cstr_from_args; 7 | 8 | const fn index_of_dot(s: &str) -> usize { 9 | let bytes = s.as_bytes(); 10 | let mut i = 0; 11 | while i < bytes.len() { 12 | if bytes[i] == b'.' { 13 | return i; 14 | } 15 | i += 1; 16 | } 17 | s.len() // if no dot is found, return the length of the string 18 | } 19 | 20 | pub const VERSION: &str = str_index!( 21 | env!("CARGO_PKG_VERSION"), 22 | ..env!("CARGO_PKG_VERSION").len() - 2 23 | ); 24 | pub const MAJOR_VERSION: &str = str_index!( 25 | env!("CARGO_PKG_VERSION"), 26 | ..index_of_dot(env!("CARGO_PKG_VERSION")) 27 | ); 28 | 29 | #[cfg(not(debug_assertions))] 30 | pub const GLOBAL_TABLE_NAME: &str = 31 | formatcp!("goobie_mysql_{}", str_replace!(MAJOR_VERSION, ".", "_")); 32 | #[cfg(debug_assertions)] 33 | pub const GLOBAL_TABLE_NAME: &str = "goobie_mysql"; 34 | pub const GLOBAL_TABLE_NAME_C: LuaCStr = cstr_from_args!(GLOBAL_TABLE_NAME); 35 | 36 | // How many threads to use for the runtime 37 | pub const DEFAULT_WORKER_THREADS: u16 = 1; 38 | 39 | pub const DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT: u32 = 20; 40 | -------------------------------------------------------------------------------- /mysql/src/error.rs: -------------------------------------------------------------------------------- 1 | use gmod::*; 2 | use sqlx::mysql::MySqlDatabaseError; 3 | 4 | use crate::GLOBAL_TABLE_NAME_C; 5 | 6 | // call this function after creating a table 7 | fn handle_database_error(l: &lua::State, db_e: &MySqlDatabaseError) -> String { 8 | if let Some(sqlstate) = db_e.code() { 9 | l.push_string(sqlstate); 10 | l.set_field(-2, c"sqlstate"); 11 | } 12 | 13 | l.push_number(db_e.number()); 14 | l.set_field(-2, c"code"); 15 | 16 | db_e.message().to_string() 17 | } 18 | 19 | // call this function after creating a table 20 | fn handle_sqlx_error_internal(l: &lua::State, e: &sqlx::Error) { 21 | let msg = match e { 22 | sqlx::Error::Database(ref db_e) => match db_e.try_downcast_ref::() { 23 | Some(mysql_e) => handle_database_error(l, mysql_e), 24 | _ => e.to_string(), 25 | }, 26 | _ => e.to_string(), 27 | }; 28 | 29 | l.push_string(&msg); 30 | l.set_field(-2, c"message"); 31 | } 32 | 33 | pub fn handle_error(l: &lua::State, e: &anyhow::Error) { 34 | l.create_table(0, 3); 35 | 36 | l.get_global(GLOBAL_TABLE_NAME_C); 37 | if l.is_table(-1) { 38 | l.get_field(-1, c"ERROR_META"); 39 | l.set_metatable(-3); 40 | } 41 | l.pop(); 42 | 43 | match e.downcast_ref::() { 44 | Some(sqlx_e) => { 45 | handle_sqlx_error_internal(l, sqlx_e); 46 | } 47 | _ => { 48 | let err_msg = e.to_string(); 49 | l.push_string(&err_msg); 50 | l.set_field(-2, c"message"); 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /mysql/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gmod::*; 2 | 3 | mod conn; 4 | mod constants; 5 | mod error; 6 | mod query; 7 | mod runtime; 8 | 9 | pub use constants::*; 10 | pub use runtime::run_async; 11 | 12 | #[gmod13_open] 13 | fn gmod13_open(l: lua::State) -> i32 { 14 | runtime::load(l); 15 | 16 | l.new_table(); 17 | { 18 | l.push_string(crate::VERSION); 19 | l.set_field(-2, c"VERSION"); 20 | 21 | l.push_string(crate::MAJOR_VERSION); 22 | l.set_field(-2, c"MAJOR_VERSION"); 23 | 24 | l.push_function(conn::new_conn); 25 | l.set_field(-2, c"NewConn"); 26 | } 27 | l.set_global(GLOBAL_TABLE_NAME_C); 28 | 29 | conn::on_gmod_open(l); 30 | 31 | 0 32 | } 33 | 34 | #[gmod13_close] 35 | fn gmod13_close(l: lua::State) -> i32 { 36 | runtime::unload(l); 37 | 38 | 0 39 | } 40 | 41 | #[macro_export] 42 | macro_rules! print_goobie { 43 | ($($arg:tt)*) => { 44 | println!("(Goobie MySQL v{}) {}", $crate::VERSION, format_args!($($arg)*)); 45 | }; 46 | } 47 | 48 | #[macro_export] 49 | macro_rules! print_goobie_with_host { 50 | ($host:expr, $($arg:tt)*) => { 51 | println!("(Goobie MySQL v{}) |{}| {}", $crate::VERSION, $host, format_args!($($arg)*)); 52 | }; 53 | } 54 | 55 | #[macro_export] 56 | macro_rules! cstr_from_args { 57 | ($($arg:expr),+) => {{ 58 | use std::ffi::{c_char, CStr}; 59 | const BYTES: &[u8] = const_format::concatcp!($($arg),+, "\0").as_bytes(); 60 | let ptr: *const c_char = BYTES.as_ptr().cast(); 61 | unsafe { CStr::from_ptr(ptr) } 62 | }}; 63 | } 64 | -------------------------------------------------------------------------------- /mysql/src/query/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use gmod::{push_to_lua::PushToLua, *}; 3 | use sqlx::{Executor as _, MySqlConnection}; 4 | 5 | pub mod process; 6 | pub mod result; 7 | 8 | pub use result::QueryResult; 9 | 10 | use process::{convert_row, convert_rows}; 11 | 12 | use crate::error::handle_error; 13 | 14 | #[derive(Debug)] 15 | pub enum QueryType { 16 | Run, 17 | Execute, 18 | FetchOne, 19 | FetchAll, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub enum Param { 24 | Number(i32), 25 | String(Vec), 26 | Boolean(bool), 27 | } 28 | 29 | #[derive(Debug)] 30 | pub struct Query { 31 | pub query: String, 32 | pub r#type: QueryType, 33 | pub params: Vec, 34 | pub callback: LuaReference, 35 | pub on_error: LuaReference, 36 | pub raw: bool, 37 | pub result: Result, 38 | } 39 | 40 | impl Query { 41 | pub fn new(query: String, r#type: QueryType, on_error: LuaReference) -> Self { 42 | Self { 43 | query, 44 | r#type, 45 | raw: false, 46 | params: Vec::new(), 47 | callback: LUA_NOREF, 48 | on_error, 49 | result: Ok(QueryResult::Run), // we just need a placeholder 50 | } 51 | } 52 | 53 | pub fn parse_options(&mut self, l: lua::State, arg_n: i32) -> Result<()> { 54 | if !l.is_none_or_nil(arg_n) { 55 | l.check_table(arg_n)?; 56 | } else { 57 | return Ok(()); 58 | } 59 | 60 | if l.get_field_type_or_nil(arg_n, c"params", LUA_TTABLE)? { 61 | self.bind_params(l)? 62 | } 63 | 64 | if l.get_field_type_or_nil(arg_n, c"callback", LUA_TFUNCTION)? { 65 | self.callback = l.reference(); 66 | } 67 | 68 | if l.get_field_type_or_nil(arg_n, c"raw", LUA_TBOOLEAN)? { 69 | self.raw = l.get_boolean(-1); 70 | l.pop(); 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | pub fn bind_params(&mut self, l: lua::State) -> Result<()> { 77 | for i in 1..=l.len(-1) { 78 | l.raw_geti(-1, i); 79 | match l.lua_type(-1) { 80 | LUA_TNUMBER => { 81 | let num = l.to_number(-1); 82 | self.params.push(Param::Number(num as i32)); 83 | } 84 | LUA_TSTRING => { 85 | // SAFETY: We just checked the type 86 | let s = l.get_binary_string(-1).unwrap(); 87 | self.params.push(Param::String(s)); 88 | } 89 | LUA_TBOOLEAN => { 90 | let b = l.get_boolean(-1); 91 | self.params.push(Param::Boolean(b)); 92 | } 93 | _ => { 94 | bail!( 95 | "Unsupported type for parameter {}: {}", 96 | i, 97 | l.lua_type_name(-1) 98 | ); 99 | } 100 | } 101 | 102 | l.pop(); 103 | } 104 | Ok(()) 105 | } 106 | 107 | #[inline] 108 | pub async fn start(&mut self, conn: &'_ mut MySqlConnection) { 109 | let r#type = &self.r#type; 110 | if self.raw { 111 | // &str gets treated as raw query in sqlx 112 | self.result = handle_query(self.query.as_str(), conn, r#type).await; 113 | } else { 114 | let mut query = sqlx::query(self.query.as_str()); 115 | for param in self.params.drain(..) { 116 | match param { 117 | Param::Number(n) => query = query.bind(n), 118 | Param::String(s) => query = query.bind(s), 119 | Param::Boolean(b) => query = query.bind(b), 120 | }; 121 | } 122 | self.result = handle_query(query, conn, r#type).await; 123 | } 124 | } 125 | 126 | pub fn process_result(&mut self, l: lua::State) { 127 | match &self.result { 128 | Ok(query_result) => { 129 | l.pcall_ignore_func_ref(self.callback.as_static(), || { 130 | query_result.push_to_lua(&l); 131 | 0 132 | }); 133 | } 134 | Err(e) => { 135 | l.pcall_ignore_func_ref(self.on_error.as_static(), || { 136 | handle_error(&l, self.result.as_ref().unwrap_err()); 137 | 0 138 | }); 139 | l.pcall_ignore_func_ref(self.callback.as_static(), || { 140 | handle_error(&l, e); 141 | 0 142 | }); 143 | } 144 | }; 145 | } 146 | } 147 | 148 | async fn handle_query<'a, 'q, E>( 149 | query: E, 150 | conn: &'q mut MySqlConnection, 151 | query_type: &QueryType, 152 | ) -> Result 153 | where 154 | E: 'q + sqlx::Execute<'q, sqlx::MySql>, 155 | { 156 | match query_type { 157 | QueryType::Run => { 158 | conn.execute(query).await?; 159 | Ok(QueryResult::Run) 160 | } 161 | QueryType::Execute => { 162 | let info = conn.execute(query).await?; 163 | Ok(QueryResult::Execute(info)) 164 | } 165 | QueryType::FetchAll => { 166 | let rows = conn.fetch_all(query).await?; 167 | let rows = convert_rows(&rows); 168 | Ok(QueryResult::Rows(rows)) 169 | } 170 | QueryType::FetchOne => { 171 | let row = conn.fetch_optional(query).await?; 172 | let row = convert_row(&row); 173 | Ok(QueryResult::Row(row)) 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /mysql/src/query/process.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use anyhow::{bail, Result}; 4 | use gmod::{push_to_lua::PushToLua, *}; 5 | use sqlx::{ 6 | mysql::MySqlRow, 7 | types::{ 8 | chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}, 9 | Decimal, 10 | }, 11 | Column, Row, TypeInfo, ValueRef as _, 12 | }; 13 | 14 | #[derive(Debug)] 15 | pub struct ColumnValue { 16 | pub column_name: CString, 17 | pub value: lua::Value, 18 | } 19 | 20 | impl PushToLua for ColumnValue { 21 | fn push_to_lua(&self, l: &gmod::State) { 22 | self.value.push_to_lua(l); 23 | } 24 | } 25 | 26 | pub fn convert_rows(rows: &[MySqlRow]) -> Result>> { 27 | rows.iter().map(extract_row_values).collect() 28 | } 29 | 30 | pub fn convert_row(row: &Option) -> Result>> { 31 | match row { 32 | Some(row) => Ok(Some(extract_row_values(row)?)), 33 | None => Ok(None), 34 | } 35 | } 36 | 37 | fn extract_row_values(row: &MySqlRow) -> Result> { 38 | let mut values = Vec::with_capacity(row.columns().len()); 39 | for column in row.columns() { 40 | let name = column.name(); 41 | let col_type = column.type_info().name(); 42 | let column_name = cstring(name); 43 | let value = extract_column_value(row, name, col_type)?; 44 | values.push(ColumnValue { column_name, value }); 45 | } 46 | Ok(values) 47 | } 48 | 49 | fn extract_column_value( 50 | row: &MySqlRow, 51 | column_name: &str, 52 | column_type: &str, 53 | ) -> Result { 54 | let raw_value = row.try_get_raw(column_name)?; 55 | if raw_value.is_null() { 56 | return Ok(lua::Value::Nil); 57 | } 58 | let value = match column_type { 59 | "NULL" => lua::Value::Nil, 60 | "BOOLEAN" | "BOOL" => { 61 | let b: bool = row.get(column_name); 62 | lua::Value::Boolean(b) 63 | } 64 | "TINYINT" => { 65 | let i8: i8 = row.get(column_name); 66 | lua::Value::F64(i8 as f64) 67 | } 68 | "SMALLINT" => { 69 | let i16: i16 = row.get(column_name); 70 | lua::Value::F64(i16 as f64) 71 | } 72 | "INT" | "INTEGER" | "MEDIUMINT" => { 73 | let i32: i32 = row.get(column_name); 74 | lua::Value::F64(i32 as f64) 75 | } 76 | "BIGINT" => { 77 | let i64: i64 = row.get(column_name); 78 | lua::Value::I64(i64) 79 | } 80 | "TINYINT UNSIGNED" => { 81 | let u8: u8 = row.get(column_name); 82 | lua::Value::F64(u8 as f64) 83 | } 84 | "SMALLINT UNSIGNED" => { 85 | let u16: u16 = row.get(column_name); 86 | lua::Value::F64(u16 as f64) 87 | } 88 | "INT UNSIGNED" | "MEDIUMINT UNSIGNED" => { 89 | let u32: u32 = row.get(column_name); 90 | lua::Value::F64(u32 as f64) 91 | } 92 | "BIGINT UNSIGNED" => { 93 | let u64: u64 = row.get(column_name); 94 | lua::Value::U64(u64) 95 | } 96 | "FLOAT" => { 97 | let f32: f32 = row.get(column_name); 98 | lua::Value::F64(f32 as f64) 99 | } 100 | "DOUBLE" | "REAL" => { 101 | let f64: f64 = row.get(column_name); 102 | lua::Value::F64(f64) 103 | } 104 | "DECIMAL" => { 105 | let decimal: Decimal = row.get(column_name); 106 | lua::Value::String(decimal.to_string()) 107 | } 108 | "TIME" => { 109 | let time: NaiveTime = row.get(column_name); 110 | lua::Value::String(time.to_string()) 111 | } 112 | "DATE" => { 113 | let date: NaiveDate = row.get(column_name); 114 | lua::Value::String(date.to_string()) 115 | } 116 | "DATETIME" => { 117 | let datetime: NaiveDateTime = row.get(column_name); 118 | lua::Value::String(datetime.to_string()) 119 | } 120 | "TIMESTAMP" => { 121 | let timestamp: DateTime = row.get(column_name); 122 | lua::Value::String(timestamp.to_string()) 123 | } 124 | "YEAR" => { 125 | let year: i32 = row.get(column_name); 126 | lua::Value::F64(year as f64) 127 | } 128 | "BINARY" | "VARBINARY" | "TINYBLOB" | "BLOB" | "MEDIUMBLOB" | "LONGBLOB" | "CHAR" 129 | | "VARCHAR" | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" | "JSON" | "ENUM" | "SET" => { 130 | let binary: Vec = row.get(column_name); 131 | lua::Value::BinaryString(binary) 132 | } 133 | "BIT" => { 134 | // figure out what to push, string or a vector or a number 135 | bail!("BIT type is not supported, if you need it, please open an issue explaining how it should be handled"); 136 | } 137 | _ => { 138 | bail!("unsupported column type: {}", column_type); 139 | } 140 | }; 141 | Ok(value) 142 | } 143 | -------------------------------------------------------------------------------- /mysql/src/query/result.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use gmod::push_to_lua::PushToLua; 3 | use sqlx::mysql::MySqlQueryResult; 4 | 5 | use crate::error::handle_error; 6 | 7 | use super::process::ColumnValue; 8 | 9 | #[derive(Debug)] 10 | pub enum QueryResult { 11 | Run, 12 | Execute(MySqlQueryResult), 13 | Rows(Result>>), 14 | Row(Result>>), // Option is used incase of no row was found 15 | } 16 | 17 | impl PushToLua for QueryResult { 18 | fn push_to_lua(&self, l: &gmod::State) { 19 | use QueryResult::*; 20 | match self { 21 | Run => {} // nothing to push 22 | Execute(info) => { 23 | l.push_nil(); // error is nil 24 | l.create_table(0, 2); 25 | { 26 | l.push_number(info.rows_affected()); 27 | l.set_field(-2, c"rows_affected"); 28 | 29 | l.push_number(info.last_insert_id()); 30 | l.set_field(-2, c"last_insert_id"); 31 | } 32 | } 33 | Rows(rows) => { 34 | let rows = match rows { 35 | Ok(rows) => rows, 36 | Err(e) => { 37 | handle_error(l, e); 38 | return; 39 | } 40 | }; 41 | 42 | l.push_nil(); // error is nil 43 | l.create_table(rows.len() as i32, 0); 44 | for (idx, row) in rows.iter().enumerate() { 45 | l.create_table(0, row.len() as i32); 46 | for value in row.iter() { 47 | value.push_to_lua(l); 48 | l.set_field(-2, &value.column_name); 49 | } 50 | l.raw_seti(-2, idx as i32 + 1); 51 | } 52 | } 53 | Row(row) => { 54 | let row = match row { 55 | Ok(Some(row)) => row, 56 | Ok(None) => return, 57 | Err(e) => { 58 | handle_error(l, e); 59 | return; 60 | } 61 | }; 62 | 63 | l.push_nil(); // error is nil 64 | l.create_table(0, row.len() as i32); 65 | for value in row.iter() { 66 | value.push_to_lua(l); 67 | l.set_field(-2, &value.column_name); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mysql/src/runtime.rs: -------------------------------------------------------------------------------- 1 | #![allow(static_mut_refs)] 2 | 3 | use std::mem::MaybeUninit; 4 | 5 | use gmod::lua; 6 | use tokio::runtime::{Builder, Runtime}; 7 | use tokio_util::task::TaskTracker; 8 | 9 | use crate::{constants::*, print_goobie}; 10 | 11 | static mut RUN_TIME: MaybeUninit = MaybeUninit::uninit(); 12 | static mut TASK_TRACKER: MaybeUninit = MaybeUninit::uninit(); 13 | static mut SHUTDOWN_TIMEOUT: u32 = DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT; 14 | 15 | pub(super) fn load(l: lua::State) { 16 | let worker_threads = get_max_worker_threads(l); 17 | unsafe { 18 | SHUTDOWN_TIMEOUT = get_graceful_shutdown_timeout(l); 19 | } 20 | print_goobie!("Using {worker_threads} worker threads"); 21 | 22 | let run_time = Builder::new_multi_thread() 23 | .worker_threads(worker_threads as usize) 24 | .max_blocking_threads(1) 25 | .enable_all() 26 | .build() 27 | .expect("Failed to create Tokio runtime"); 28 | 29 | let task_tracker = TaskTracker::new(); 30 | 31 | unsafe { 32 | RUN_TIME = MaybeUninit::new(run_time); 33 | TASK_TRACKER = MaybeUninit::new(task_tracker); 34 | } 35 | } 36 | 37 | pub(super) fn unload(_: lua::State) { 38 | let run_time = unsafe { RUN_TIME.assume_init_read() }; 39 | 40 | let task_tracker = unsafe { TASK_TRACKER.assume_init_read() }; 41 | task_tracker.close(); 42 | 43 | if !task_tracker.is_empty() { 44 | let timeout = std::time::Duration::from_secs(unsafe { SHUTDOWN_TIMEOUT } as u64); 45 | 46 | print_goobie!( 47 | "Waiting up to {} seconds for {} connection(s) to complete...", 48 | timeout.as_secs(), 49 | task_tracker.len() 50 | ); 51 | 52 | run_time.block_on(async { 53 | tokio::select! { 54 | _ = task_tracker.wait() => { 55 | print_goobie!("All connections have completed!"); 56 | }, 57 | _ = tokio::time::sleep(timeout) => { 58 | print_goobie!("Timed out waiting for connections to complete!"); 59 | }, 60 | } 61 | }); 62 | } 63 | 64 | unsafe { 65 | RUN_TIME = MaybeUninit::uninit(); 66 | TASK_TRACKER = MaybeUninit::uninit(); 67 | } 68 | } 69 | 70 | fn read<'a>() -> &'a Runtime { 71 | unsafe { RUN_TIME.assume_init_ref() } 72 | } 73 | 74 | fn read_tracker<'a>() -> &'a TaskTracker { 75 | unsafe { TASK_TRACKER.assume_init_ref() } 76 | } 77 | 78 | pub fn run_async(fut: F) -> tokio::task::JoinHandle 79 | where 80 | F: std::future::Future + Send + 'static, 81 | F::Output: Send + 'static, 82 | { 83 | read().spawn(read_tracker().track_future(fut)) 84 | } 85 | 86 | fn get_max_worker_threads(l: lua::State) -> u16 { 87 | let mut max_worker_threads = DEFAULT_WORKER_THREADS; 88 | 89 | l.get_global(c"CreateConVar"); 90 | let success = l.pcall_ignore(|| { 91 | l.push_string("GOOBIE_MYSQL_WORKER_THREADS"); 92 | l.push_number(DEFAULT_WORKER_THREADS); 93 | l.create_table(2, 0); 94 | { 95 | l.get_global(c"FCVAR_ARCHIVE"); 96 | l.raw_seti(-2, 1); 97 | 98 | l.get_global(c"FCVAR_PROTECTED"); 99 | l.raw_seti(-2, 2); 100 | } 101 | l.push_string("Number of worker threads for the mysql connection pool"); 102 | 1 103 | }); 104 | 105 | if success { 106 | l.get_field(-1, c"GetInt"); 107 | let success = l.pcall_ignore(|| { 108 | l.push_value(-2); // push the convar 109 | 1 110 | }); 111 | if success { 112 | max_worker_threads = l.to_number(-1) as u16; 113 | l.pop(); // pop the number 114 | } 115 | l.pop(); // pop the object 116 | } 117 | 118 | max_worker_threads 119 | } 120 | 121 | fn get_graceful_shutdown_timeout(l: lua::State) -> u32 { 122 | let mut timeout = DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT; 123 | 124 | l.get_global(c"CreateConVar"); 125 | let success = l.pcall_ignore(|| { 126 | l.push_string("GOOBIE_MYSQL_GRACEFUL_SHUTDOWN_TIMEOUT"); 127 | l.push_number(DEFAULT_WORKER_THREADS); 128 | l.create_table(2, 0); 129 | { 130 | l.get_global(c"FCVAR_ARCHIVE"); 131 | l.raw_seti(-2, 1); 132 | 133 | l.get_global(c"FCVAR_PROTECTED"); 134 | l.raw_seti(-2, 2); 135 | } 136 | l.push_string("Timeout for graceful shutdown of the mysql connections, in seconds"); 137 | 1 138 | }); 139 | 140 | if success { 141 | l.get_field(-1, c"GetInt"); 142 | let success = l.pcall_ignore(|| { 143 | l.push_value(-2); // push the convar 144 | 1 145 | }); 146 | if success { 147 | timeout = l.to_number(-1) as u32; 148 | l.pop(); // pop the number 149 | } 150 | l.pop(); // pop the object 151 | } 152 | 153 | timeout 154 | } 155 | -------------------------------------------------------------------------------- /sql/.gitignore: -------------------------------------------------------------------------------- 1 | /goobie-sql.lua 2 | -------------------------------------------------------------------------------- /sql/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script is generated by chatgpt, I know simple python lol 4 | 5 | import re 6 | import os 7 | import sys 8 | 9 | BASE_DIR = "lua" 10 | MAIN_FILE = "goobie-sql/goobie-sql.lua" 11 | OUTPUT_FILE = "goobie-sql.lua" 12 | COMMON_PATH = "goobie-sql/common.lua" # Special file to inline only once. 13 | 14 | common_defined = False # global flag to track if common.lua was inlined 15 | FULL_VERSION = "" 16 | 17 | def process_file(file_relative_path): 18 | full_path = os.path.join(BASE_DIR, file_relative_path) 19 | if not os.path.exists(full_path): 20 | print(f"Error: File not found: {full_path}") 21 | sys.exit(1) 22 | with open(full_path, "r") as f: 23 | content = f.read() 24 | 25 | # If processing the common file, replace version placeholders. 26 | if file_relative_path == COMMON_PATH: 27 | content = content.replace("FULL_VERSION_PLACEHOLDER", FULL_VERSION) 28 | 29 | # Pattern to capture optional assignment and the include statement. 30 | # Group 1: The full assignment string, e.g., "local common = " 31 | # Group 2: The variable name (e.g., "common") 32 | # Group 3: The file path inside include(...) 33 | pattern = r'(local\s+(\w+)\s*=\s*)?include\("([^"]+)"\)' 34 | 35 | def replace_include(match): 36 | global common_defined 37 | assignment_prefix = match.group(1) # e.g., "local common = " (or None) 38 | var_name = match.group(2) # e.g., "common" (or None) 39 | include_path = match.group(3) 40 | 41 | if include_path == COMMON_PATH: 42 | if not common_defined: 43 | processed = process_file(include_path) 44 | common_defined = True 45 | if not var_name: 46 | var_name = "_COMMON_TMP_" 47 | assignment_prefix = f"local {var_name} = " 48 | return f"{assignment_prefix}(function()\n{processed}\nend)(); local _COMMON_MAIN_ = {var_name};" 49 | else: 50 | if assignment_prefix: 51 | return f"{assignment_prefix}_COMMON_MAIN_" 52 | else: 53 | return "_COMMON_MAIN_" 54 | else: 55 | processed = process_file(include_path) 56 | if assignment_prefix: 57 | return f"{assignment_prefix}(function()\n{processed}\nend)()" 58 | else: 59 | return f"(function()\n{processed}\nend)()" 60 | 61 | new_content = re.sub(pattern, replace_include, content) 62 | return new_content 63 | 64 | def main(): 65 | global FULL_VERSION 66 | if len(sys.argv) < 2: 67 | print("Usage: python script.py ") 68 | sys.exit(1) 69 | FULL_VERSION = sys.argv[1] 70 | processed_main = process_file(MAIN_FILE) 71 | with open(OUTPUT_FILE, "w") as f: 72 | f.write(processed_main) 73 | print(f"Preprocessing complete. Output written to {OUTPUT_FILE}") 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/common.lua: -------------------------------------------------------------------------------- 1 | local string_format = string.format 2 | local string_gsub = string.gsub 3 | local string_char = string.char 4 | local string_byte = string.byte 5 | local table_insert = table.insert 6 | local type = type 7 | 8 | local common = { 9 | VERSION = "FULL_VERSION_PLACEHOLDER", 10 | NULL = {} 11 | } 12 | common.MAJOR_VERSION = tonumber(common.VERSION:match("(%d+)%.%d+")) 13 | 14 | local STATES = { 15 | CONNECTED = 0, 16 | CONNECTING = 1, 17 | NOT_CONNECTED = 2, 18 | DISCONNECTED = 3, 19 | } 20 | common.STATES = STATES 21 | 22 | local STATES_NAMES = {}; do 23 | for k, v in pairs(STATES) do 24 | STATES_NAMES[v] = k 25 | end 26 | end 27 | common.STATES_NAMES = STATES_NAMES 28 | 29 | do 30 | local hex = function(c) 31 | return string_format("%02X", string_byte(c)) 32 | end 33 | function common.StringToHex(text) 34 | return (string_gsub(text, ".", hex)) 35 | end 36 | 37 | local unhex = function(cc) 38 | return string_char(tonumber(cc, 16)) 39 | end 40 | function common.StringFromHex(text) 41 | return (string_gsub(text, "..", unhex)) 42 | end 43 | end 44 | 45 | local SetPrivate, GetPrivate; do 46 | local PRIVATE_KEY = "___PRIVATE___ANNOYING_KEY_TO_STOP_PLAYING_WITH___" 47 | 48 | function SetPrivate(conn, key, value) 49 | local private = conn[PRIVATE_KEY] 50 | if not private then 51 | private = {} 52 | conn[PRIVATE_KEY] = private 53 | end 54 | private[key] = value 55 | end 56 | 57 | ---@return any 58 | function GetPrivate(conn, key) 59 | local private = conn[PRIVATE_KEY] 60 | if not private then return nil end 61 | return private[key] 62 | end 63 | end 64 | common.SetPrivate, common.GetPrivate = SetPrivate, GetPrivate 65 | 66 | local ERROR_META = { 67 | __tostring = function(s) 68 | return string.format("(%s) %s", s.code or "?", s.message or "unknown error") 69 | end 70 | } 71 | common.ERROR_META = ERROR_META 72 | 73 | common.SQLError = function(msg) 74 | return setmetatable({ 75 | message = msg, 76 | }, ERROR_META) 77 | end 78 | 79 | common.CROSS_SYNTAXES = include("goobie-sql/cross_syntaxes.lua") 80 | 81 | local EMPTY_PARAMS = {} 82 | local EMPTY_OPS = { 83 | params = EMPTY_PARAMS, 84 | } 85 | 86 | ---@param query string|nil 87 | ---@param opts table|nil 88 | ---@param is_async boolean|nil 89 | ---@return table 90 | common.CheckQuery = function(query, opts, is_async) 91 | if type(query) ~= "string" then 92 | error("query must be a string", 4) 93 | end 94 | 95 | if opts == nil then 96 | return EMPTY_OPS 97 | end 98 | 99 | if type(opts) ~= "table" then 100 | error("opts must be a table", 4) 101 | end 102 | 103 | local params = opts.params 104 | if params == nil then 105 | opts.params = EMPTY_PARAMS 106 | elseif type(params) ~= "table" then 107 | error("params must be a table", 4) 108 | end 109 | 110 | if not opts.sync then 111 | if is_async and type(opts.callback) ~= "function" then 112 | error("callback must be a function", 4) 113 | end 114 | end 115 | 116 | return opts 117 | end 118 | 119 | local COMMON_META = {} 120 | 121 | function COMMON_META:StateName() return STATES_NAMES[self:State()] end 122 | 123 | function COMMON_META:IsConnected() return self:State() == STATES.CONNECTED end 124 | 125 | function COMMON_META:IsConnecting() return self:State() == STATES.CONNECTING end 126 | 127 | function COMMON_META:IsDisconnected() return self:State() == STATES.DISCONNECTED end 128 | 129 | function COMMON_META:IsNotConnected() return self:State() == STATES.NOT_CONNECTED end 130 | 131 | common.COMMON_META = COMMON_META 132 | 133 | local function errorf(err, ...) 134 | return error(string_format(err, ...)) 135 | end 136 | common.errorf = errorf 137 | 138 | local function errorlevelf(level, err, ...) 139 | return error(string_format(err, ...)) 140 | end 141 | common.errorlevelf = errorlevelf 142 | 143 | common.HandleNoEscape = function(v) 144 | local v_type = type(v) 145 | if v_type == "string" then 146 | return "'" .. v .. "'" 147 | elseif v_type == "number" then 148 | return v 149 | elseif v_type == "boolean" then 150 | return v and "TRUE" or "FALSE" 151 | else 152 | return errorf("invalid type '%s' was passed to escape '%s'", v_type, v) 153 | end 154 | end 155 | 156 | local PARAMS_PATTERN = "{[%s]*(%d+)[%s]*}" 157 | 158 | local HandleQueryParams; do 159 | local fquery_params, fquery_new_params, escape_function 160 | local has_matches = false 161 | local gsub_f = function(key, opts) 162 | local raw_value = fquery_params[tonumber(key)] 163 | if raw_value == nil then 164 | return errorf("missing parameter for query: %s", key) 165 | end 166 | 167 | has_matches = true 168 | 169 | if raw_value == common.NULL then 170 | return "NULL" 171 | end 172 | 173 | 174 | table_insert(fquery_new_params, raw_value) 175 | 176 | return (escape_function(raw_value)) 177 | end 178 | 179 | local EMPTY_QUERY_PARAMS = {} 180 | ---@return string 181 | ---@return table 182 | function HandleQueryParams(query, params, escape_func) 183 | fquery_new_params = {} 184 | fquery_params = params 185 | escape_function = escape_func 186 | has_matches = false 187 | 188 | -- We don't return the query immediately as that could cause hidden bugs. We must ensure that if the developer is using 189 | -- placeholders, they are checked for missing parameters. 190 | if fquery_params == nil then 191 | fquery_params = EMPTY_QUERY_PARAMS 192 | elseif type(fquery_params) ~= "table" then 193 | errorlevelf(4, "params must be a table, got %s", type(fquery_params)) 194 | end 195 | 196 | local new_query = (string_gsub(query, PARAMS_PATTERN, gsub_f)) 197 | 198 | -- if nothing matched, just return params as s 199 | if not has_matches then 200 | return query, params 201 | end 202 | 203 | return new_query, fquery_new_params 204 | end 205 | end 206 | common.HandleQueryParams = HandleQueryParams 207 | 208 | return common 209 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/cross_syntaxes.lua: -------------------------------------------------------------------------------- 1 | return { 2 | sqlite = { 3 | CROSS_NOW = "(CAST(strftime('%s', 'now') AS INTEGER))", 4 | -- INTEGER PRIMARY KEY auto increments in SQLite, see https://www.sqlite.org/autoinc.html 5 | CROSS_PRIMARY_AUTO_INCREMENTED = "INTEGER PRIMARY KEY", 6 | CROSS_COLLATE_BINARY = "COLLATE BINARY", 7 | CROSS_CURRENT_DATE = "DATE('now')", 8 | CROSS_OS_TIME_TYPE = "INT UNSIGNED NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INTEGER))", 9 | CROSS_INT_TYPE = "INTEGER", 10 | CROSS_JSON_TYPE = "TEXT", 11 | }, 12 | mysql = { 13 | CROSS_NOW = "(UNIX_TIMESTAMP())", 14 | CROSS_PRIMARY_AUTO_INCREMENTED = "BIGINT AUTO_INCREMENT PRIMARY KEY", 15 | CROSS_COLLATE_BINARY = "BINARY", 16 | CROSS_CURRENT_DATE = "CURDATE()", 17 | CROSS_OS_TIME_TYPE = "INT UNSIGNED NOT NULL DEFAULT (UNIX_TIMESTAMP())", 18 | CROSS_INT_TYPE = "BIGINT", 19 | CROSS_JSON_TYPE = "JSON", 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/goobie-sql.lua: -------------------------------------------------------------------------------- 1 | local common = include("goobie-sql/common.lua") 2 | local RunMigrations = include("goobie-sql/migrations.lua") 3 | 4 | local goobie_sql = { 5 | NULL = common.NULL, 6 | STATES = common.STATES, 7 | 8 | VERSION = common.VERSION, 9 | MAJOR_VERSION = common.MAJOR_VERSION, 10 | } 11 | 12 | local goobie_sqlite 13 | local goobie_mysql 14 | 15 | function goobie_sql.NewConn(opts, on_connected) 16 | if type(opts) ~= "table" then 17 | return error("opts must be a table") 18 | end 19 | 20 | local conn 21 | if opts.driver == "mysql" then 22 | if goobie_mysql == nil then 23 | goobie_mysql = include("goobie-sql/mysql/main.lua") 24 | if not goobie_mysql then 25 | return error("failed to load mysql binary module") 26 | end 27 | end 28 | conn = goobie_mysql.NewConn(opts) 29 | elseif opts.driver == "sqlite" then 30 | if goobie_sqlite == nil then 31 | goobie_sqlite = include("goobie-sql/sqlite/main.lua") 32 | end 33 | conn = goobie_sqlite.NewConn(opts) 34 | else 35 | return error("invalid driver '%s'", opts.driver) 36 | end 37 | 38 | conn.options = opts 39 | conn.on_error = opts.on_error 40 | 41 | if on_connected then 42 | conn:Start(on_connected) 43 | else 44 | conn:StartSync() 45 | end 46 | 47 | conn.RunMigrations = RunMigrations 48 | 49 | return conn 50 | end 51 | 52 | return goobie_sql 53 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/migrations.lua: -------------------------------------------------------------------------------- 1 | local fmt = string.format 2 | 3 | local function is_ascii(s) 4 | return not s:match("[\128-\255]") -- Match any byte outside the ASCII range (0-127) 5 | end 6 | 7 | local function preprocess(text, defines) 8 | for k, v in pairs(defines) do 9 | defines[k:upper()] = v 10 | end 11 | 12 | local output = {} 13 | local state_stack = {} 14 | local active = true 15 | 16 | for line in text:gmatch("([^\n]*)\n?") do 17 | local macro = line:match("^%s*%-%-@ifdef%s+(%w+)") 18 | if macro then 19 | macro = macro:upper() 20 | table.insert(state_stack, active) 21 | active = active and (defines[macro] == true) 22 | elseif line:match("^%s*%-%-@else%s*$") then 23 | if #state_stack == 0 then 24 | return error("Unexpected --@else without matching --@ifdef") 25 | end 26 | local parentActive = state_stack[#state_stack] 27 | active = parentActive and not active 28 | elseif line:match("^%s*%-%-@endif%s*$") then 29 | if #state_stack == 0 then 30 | return error("Unexpected --@endif without matching --@ifdef") 31 | end 32 | active = table.remove(state_stack) 33 | else 34 | if active then 35 | table.insert(output, line) 36 | end 37 | end 38 | end 39 | 40 | if #state_stack > 0 then 41 | return error("Missing --@endif: " .. #state_stack .. " --@ifdef directives were not closed") 42 | end 43 | 44 | return table.concat(output, "\n") 45 | end 46 | 47 | local function RunMigrations(conn, migrations, ...) 48 | local addon_name = conn.options.addon_name 49 | 50 | assert(type(migrations) == "table", "migrations must be an array sorted by version") 51 | assert(type(addon_name) == "string", "addon_name must be supplied to connection options") 52 | assert(addon_name ~= "", "addon_name cannot be empty") 53 | assert(is_ascii(addon_name), "addon_name must be ascii only") 54 | 55 | local TABLE_NAME = fmt("goobie_sql_migrations_version_%s", addon_name) 56 | 57 | local current_version = 0 58 | local old_version = 0 59 | 60 | do 61 | local err = conn:RunSync(fmt([[ 62 | CREATE TABLE IF NOT EXISTS %s ( 63 | `id` TINYINT PRIMARY KEY, 64 | `version` MEDIUMINT UNSIGNED NOT NULL 65 | ) 66 | ]], TABLE_NAME)) 67 | if err then 68 | return error(fmt("Failed to create migrations table: %s", err)) 69 | end 70 | end 71 | 72 | local first_run = false 73 | do 74 | local err, res = conn:FetchOneSync(fmt([[ 75 | SELECT `version` FROM %s 76 | ]], TABLE_NAME)) 77 | if err then 78 | return error(fmt("Failed to fetch migrations version: %s", err)) 79 | end 80 | -- if nothing was returned, then the table didn't exist 81 | if res then 82 | local version = tonumber(res.version) 83 | if type(version) == "number" then 84 | current_version = version 85 | else 86 | return error(fmt("Migrations table is corrupted, version is not a number")) 87 | end 88 | else 89 | first_run = true 90 | end 91 | end 92 | 93 | old_version = current_version 94 | 95 | -- make sure that migrations has UP, DOWN and version 96 | for _, migration in ipairs(migrations) do 97 | assert(type(migration) == "table", "migrations must be an array of tables") 98 | assert(type(migration.UP) == "function" or type(migration.UP) == "string", 99 | "migration `UP` must be a function or string") 100 | assert(type(migration.DOWN) == "function" or type(migration.DOWN) == "string", 101 | "migration `DOWN` must be a function or string") 102 | end 103 | 104 | local function process(query) 105 | local defines = {} 106 | if conn:IsMySQL() then 107 | defines["mysql"] = true 108 | else 109 | defines["sqlite"] = true 110 | end 111 | query = preprocess(query, defines) 112 | local err = conn:RunSync(query, { raw = true }) 113 | if err then 114 | return error(tostring(err)) 115 | end 116 | end 117 | 118 | local applied_migrations = {} 119 | local function revert_migrations(...) 120 | for idx, migration in ipairs(applied_migrations) do 121 | local success 122 | if type(migration.DOWN) == "function" then 123 | success = ProtectedCall(migration.DOWN, process, conn, ...) 124 | else 125 | success = ProtectedCall(process, migration.DOWN) 126 | end 127 | if not success then 128 | return error("failed to revert #" .. idx .. " migration") 129 | end 130 | end 131 | end 132 | 133 | for idx, migration in ipairs(migrations) do 134 | if idx <= current_version then 135 | goto _continue_ 136 | end 137 | 138 | local success 139 | if type(migration.UP) == "function" then 140 | success = ProtectedCall(migration.UP, process, conn, ...) 141 | else 142 | success = ProtectedCall(process, migration.UP) 143 | end 144 | if not success then 145 | revert_migrations(...) 146 | return error("failed to apply migration: " .. idx) 147 | end 148 | 149 | applied_migrations[idx] = migration 150 | current_version = idx 151 | ::_continue_:: 152 | end 153 | 154 | conn:RunSync(fmt([[ 155 | REPLACE INTO %s (`id`, `version`) VALUES (1, %d); 156 | ]], TABLE_NAME, current_version)) 157 | 158 | return old_version, current_version, first_run 159 | end 160 | 161 | return RunMigrations 162 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/mysql/main.lua: -------------------------------------------------------------------------------- 1 | local common = include("goobie-sql/common.lua") 2 | local ConnBegin = include("goobie-sql/mysql/txn.lua") 3 | 4 | local MAJOR_VERSION = common.MAJOR_VERSION 5 | local goobie_mysql; if MAJOR_VERSION then 6 | local goobie_mysql_version = "goobie_mysql_" .. MAJOR_VERSION 7 | if not util.IsBinaryModuleInstalled(goobie_mysql_version) then 8 | common.errorf( 9 | "'%s' module doesn't exist, get it from https://github.com/Srlion/goobie-sql/releases/tag/%s", 10 | goobie_mysql_version, common.VERSION) 11 | end 12 | 13 | require("goobie_mysql_" .. MAJOR_VERSION) 14 | goobie_mysql = _G["goobie_mysql_" .. MAJOR_VERSION] 15 | else 16 | _G["require"]("goobie_mysql") 17 | goobie_mysql = _G["goobie_mysql"] 18 | end 19 | if goobie_mysql.lua_loaded then return goobie_mysql end -- lua part loaded already 20 | goobie_mysql.lua_loaded = true 21 | 22 | local type = type 23 | local tostring = tostring 24 | local CheckQuery = common.CheckQuery 25 | local table_HasValue = table.HasValue 26 | local table_concat = table.concat 27 | local table_insert = table.insert 28 | local string_format = string.format 29 | local errorf = common.errorf 30 | local string_gsub = string.gsub 31 | 32 | local CROSS_SYNTAXES = common.CROSS_SYNTAXES.mysql 33 | 34 | goobie_mysql.ERROR_META = common.ERROR_META 35 | 36 | local Conn = goobie_mysql.CONN_META 37 | 38 | local QUERIES = { 39 | Run = Conn.Run, 40 | Execute = Conn.Execute, 41 | FetchOne = Conn.FetchOne, 42 | Fetch = Conn.Fetch, 43 | } 44 | 45 | for k, v in pairs(common.COMMON_META) do 46 | Conn[k] = v 47 | end 48 | 49 | local function ConnSyncOP(conn, op) 50 | local done 51 | local err, res 52 | op(function(e, r) 53 | done = true 54 | err, res = e, r 55 | end) 56 | while not done do 57 | conn:Poll() 58 | end 59 | return err, res 60 | end 61 | 62 | local function ConnQueueTask(conn, func, p1, p2, p3) 63 | if common.GetPrivate(conn, "locked") then 64 | local txn = common.GetPrivate(conn, "txn") 65 | if txn and txn.open and coroutine.running() == txn.co then 66 | return error("you can't run queries on a `connection` inside an open transaction's coroutine", 2) 67 | end 68 | local queue = common.GetPrivate(conn, "queue") 69 | queue[#queue + 1] = { func, p1, p2, p3 } 70 | else 71 | func(conn, p1, p2, p3) 72 | end 73 | end 74 | 75 | local function ConnProcessQueue(conn) 76 | if common.GetPrivate(conn, "locked") then return end -- we can't process if connection is query locked 77 | 78 | ---@type table 79 | local queue = common.GetPrivate(conn, "queue") 80 | local queue_len = #queue 81 | if queue_len == 0 then return end 82 | 83 | common.SetPrivate(conn, "queue", {}) -- make sure to clear the queue to avoid conflicts 84 | 85 | for i = 1, queue_len do 86 | local task = queue[i] 87 | -- we call QueueTask again because a task can be a Transaction Begin 88 | local func = task[1] 89 | ConnQueueTask(conn, func, task[2], task[3], task[4]) 90 | end 91 | end 92 | 93 | function Conn:IsMySQL() return true end 94 | 95 | function Conn:IsSQLite() return false end 96 | 97 | function Conn:StartSync() 98 | local err = ConnSyncOP(self, function(cb) 99 | self:Start(cb) 100 | end) 101 | if err then 102 | return error(tostring(err)) 103 | end 104 | end 105 | 106 | function Conn:DisconnectSync() 107 | local err = ConnSyncOP(self, function(cb) 108 | self:Disconnect(cb) 109 | end) 110 | return err 111 | end 112 | 113 | function Conn:PingSync() 114 | local done, err, res 115 | self:Ping(function(e, r) 116 | done = true 117 | err, res = e, r 118 | end) 119 | while not done do 120 | self:Poll() 121 | end 122 | return err, res 123 | end 124 | 125 | local ESCAPE_TYPES = { 126 | ["string"] = true, 127 | ["number"] = true, 128 | ["boolean"] = true, 129 | } 130 | local function escape_function(value) 131 | if ESCAPE_TYPES[type(value)] then 132 | return "?" 133 | else 134 | return errorf("invalid type '%s' was passed to escape '%s'", type(value), value) 135 | end 136 | end 137 | local function prepare_query(query, opts, is_async) 138 | opts = CheckQuery(query, opts, is_async) 139 | query = string_gsub(query, "{([%w_]+)}", CROSS_SYNTAXES) 140 | local params = opts.params 141 | if not opts.raw then -- raw queries can't be escaped in sqlx, hopefully they expose an escape function 142 | query, params = common.HandleQueryParams(query, params, escape_function) 143 | end 144 | opts.params = params 145 | return query, opts 146 | end 147 | 148 | local function create_query_method(query_type) 149 | local query_func = QUERIES[query_type] 150 | 151 | Conn[query_type] = function(self, query, opts) 152 | query, opts = prepare_query(query, opts, true) 153 | if opts.sync then 154 | return ConnSyncOP(self, function(cb) 155 | opts.callback = cb 156 | ConnQueueTask(self, query_func, query, opts) 157 | end) 158 | else 159 | ConnQueueTask(self, query_func, query, opts) 160 | end 161 | end 162 | 163 | Conn[query_type .. "Sync"] = function(self, query, opts) 164 | query, opts = prepare_query(query, opts) 165 | return ConnSyncOP(self, function(cb) 166 | opts.callback = cb 167 | ConnQueueTask(self, query_func, query, opts) 168 | end) 169 | end 170 | end 171 | 172 | create_query_method("Run") 173 | create_query_method("Execute") 174 | create_query_method("Fetch") 175 | create_query_method("FetchOne") 176 | 177 | -- someone could ask, why the hell is this function synchronous? because for obvious reasons, 178 | -- you use this function when setting up your server, so it's not a big deal if it's synchronous 179 | function Conn:TableExists(name) 180 | if type(name) ~= "string" then 181 | return error("table name must be a string") 182 | end 183 | local err, data = self:FetchOneSync("SHOW TABLES LIKE '" .. name .. "'") 184 | if err then 185 | return nil, err 186 | end 187 | return data ~= nil 188 | end 189 | 190 | do 191 | local query_count = 0 192 | local query_parts = {} 193 | 194 | local function insert_to_query(str) 195 | query_count = query_count + 1 196 | query_parts[query_count] = str 197 | end 198 | 199 | local function ConnUpsertQuery(conn, tbl_name, opts, sync) 200 | if type(tbl_name) ~= "string" then 201 | return error("table name must be a string") 202 | end 203 | 204 | query_count = 0 205 | 206 | -- mysql doesn't use primary keys, so we don't need to check for them to keep consistency with sqlite 207 | if not opts.primary_keys then 208 | return error("upsert query must have primary_keys") 209 | end 210 | 211 | local inserts = opts.inserts 212 | local updates = opts.updates 213 | local no_escape_columns = opts.no_escape_columns 214 | 215 | local params = { nil, nil, nil, nil, nil, nil } 216 | local values = { nil, nil, nil, nil, nil, nil } 217 | 218 | -- INSERT INTO `tbl_name`(`column1`, ...) VALUES(?, ?, ...) ON DUPLICATE KEY UPDATE `column1`=VALUES(`column1`), ... 219 | insert_to_query("INSERT INTO`") 220 | insert_to_query(tbl_name) 221 | insert_to_query("`(") 222 | 223 | for column, value in pairs(inserts) do 224 | insert_to_query("`" .. column .. "`") 225 | insert_to_query(",") 226 | if no_escape_columns and table_HasValue(no_escape_columns, column) then 227 | table_insert(values, common.HandleNoEscape(value)) 228 | else 229 | table_insert(values, "?") 230 | table_insert(params, value) 231 | end 232 | end 233 | query_count = query_count - 1 -- remove last comma 234 | 235 | insert_to_query(")VALUES(") 236 | insert_to_query(table_concat(values, ",")) 237 | insert_to_query(")ON DUPLICATE KEY UPDATE") 238 | 239 | -- basically, if there are no updates, we just update the first column with itself 240 | if updates == nil or #updates == 0 then 241 | local next_key = next(inserts) 242 | updates = { next_key } 243 | end 244 | 245 | for i = 1, #updates do 246 | local column = updates[i] 247 | insert_to_query(string_format("`%s`=VALUES(`%s`)", column, column)) 248 | insert_to_query(",") 249 | end 250 | query_count = query_count - 1 -- remove last comma 251 | 252 | local query = table_concat(query_parts, nil, 1, query_count) 253 | 254 | if opts.return_query then 255 | return query, params 256 | end 257 | 258 | opts.params = params 259 | 260 | if sync then 261 | local err, res = conn:ExecuteSync(query, opts) 262 | return err, res 263 | else 264 | local err, res = conn:Execute(query, opts) 265 | return err, res 266 | end 267 | end 268 | 269 | function Conn:UpsertQuery(tbl_name, opts) 270 | return ConnUpsertQuery(self, tbl_name, opts, false) 271 | end 272 | 273 | function Conn:UpsertQuerySync(tbl_name, opts) 274 | return ConnUpsertQuery(self, tbl_name, opts, true) 275 | end 276 | end 277 | 278 | function Conn:Begin(callback) 279 | return ConnBegin(self, callback, false) 280 | end 281 | 282 | function Conn:BeginSync(callback) 283 | return ConnBegin(self, callback, true) 284 | end 285 | 286 | local RealNewConn = goobie_mysql.NewConn 287 | function goobie_mysql.NewConn(opts) 288 | local conn = RealNewConn(opts) 289 | common.SetPrivate(conn, "queue", {}) 290 | common.SetPrivate(conn, "ConnProcessQueue", ConnProcessQueue) 291 | return conn 292 | end 293 | 294 | return goobie_mysql 295 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/mysql/txn.lua: -------------------------------------------------------------------------------- 1 | local common = include("goobie-sql/common.lua") 2 | 3 | local coroutine = coroutine 4 | 5 | local CheckQuery = common.CheckQuery 6 | 7 | local Txn = {} 8 | local Txn_MT = { __index = Txn } 9 | 10 | -- mysql does not support begin/commit/rollback using prepared statements 11 | local IS_RAW = { raw = true } 12 | 13 | local function NewTransaction(conn, co, traceback) 14 | return setmetatable({ 15 | conn = conn, 16 | conn_id = conn:ID(), 17 | co = co, 18 | traceback = traceback, 19 | open = true, 20 | }, Txn_MT) 21 | end 22 | 23 | local TxnQuery, TxnFinalize 24 | 25 | local function TxnResume(txn, ...) 26 | local co = txn.co 27 | local err 28 | 29 | local co_status = coroutine.status(co) 30 | if co_status == "dead" then 31 | if txn.open then 32 | err = "transaction was left open!" .. txn.traceback 33 | end 34 | else 35 | local success, co_err = coroutine.resume(co, ...) 36 | if success then 37 | if coroutine.status(co) == "dead" and txn.open then 38 | err = "transaction was left open!" .. txn.traceback 39 | end 40 | else 41 | err = co_err .. "\n" .. debug.traceback(co) 42 | end 43 | end 44 | 45 | if err then 46 | ErrorNoHalt(err, "\n") 47 | TxnFinalize(txn, "rollback", true) 48 | end 49 | end 50 | 51 | function TxnQuery(txn, query_type, query, opts) 52 | opts = CheckQuery(query, opts) 53 | 54 | if not txn.open then 55 | return error("transaction is closed") 56 | end 57 | 58 | local conn = txn.conn 59 | -- we need to set locked to false to make sure queries are not queued 60 | -- it's not an issue if it errors or not because TxnResume will handle it anyway 61 | 62 | opts.callback = function(err, res) 63 | TxnResume(txn, err, res) 64 | end 65 | 66 | common.SetPrivate(conn, "locked", false) 67 | conn[query_type](conn, query, opts) 68 | common.SetPrivate(conn, "locked", true) 69 | 70 | return coroutine.yield() 71 | end 72 | 73 | function TxnFinalize(txn, action, failed) 74 | if not txn.open then 75 | return 76 | end 77 | 78 | local conn = txn.conn 79 | common.SetPrivate(conn, "locked", false) 80 | 81 | local err 82 | -- if the connection dropped/lost/reconnected, we don't want to send a query 83 | -- because we are not in a transaction anymore 84 | if conn:IsConnected() and txn.conn_id == conn:ID() then 85 | if failed then 86 | conn:Run("ROLLBACK;", IS_RAW) -- we don't care about the result 87 | else 88 | if action == "commit" then 89 | err = TxnQuery(txn, "Run", "COMMIT;", IS_RAW) 90 | elseif action == "rollback" then 91 | err = TxnQuery(txn, "Run", "ROLLBACK;", IS_RAW) 92 | end 93 | end 94 | 95 | common.SetPrivate(conn, "locked", false) -- TxnQuery will set it back to true 96 | end 97 | 98 | txn.open = false 99 | 100 | -- cleanup 101 | 102 | common.SetPrivate(conn, "txn", nil) 103 | txn.conn = nil 104 | txn.co = nil 105 | common.SetPrivate(conn, "locked", false) 106 | common.GetPrivate(conn, "ConnProcessQueue")(conn) 107 | 108 | return err 109 | end 110 | 111 | function Txn:IsOpen() return self.open end 112 | 113 | function Txn:Ping() 114 | if not self.open then 115 | return error("transaction is closed") 116 | end 117 | return self.conn:Ping(function(err, latency) 118 | TxnResume(self, err, latency) 119 | end) 120 | end 121 | 122 | function Txn:Run(query, opts) 123 | return TxnQuery(self, "Run", query, opts) 124 | end 125 | 126 | function Txn:Execute(query, opts) 127 | return TxnQuery(self, "Execute", query, opts) 128 | end 129 | 130 | function Txn:FetchOne(query, opts) 131 | return TxnQuery(self, "FetchOne", query, opts) 132 | end 133 | 134 | function Txn:Fetch(query, opts) 135 | return TxnQuery(self, "Fetch", query, opts) 136 | end 137 | 138 | function Txn:Commit() 139 | return TxnFinalize(self, "commit") 140 | end 141 | 142 | function Txn:Rollback() 143 | return TxnFinalize(self, "rollback") 144 | end 145 | 146 | local function ConnBegin(conn, callback, sync) 147 | if type(callback) ~= "function" then 148 | return error("callback must be a function") 149 | end 150 | 151 | local traceback = debug.traceback("", 2) 152 | local callback_done = false 153 | conn:Run("START TRANSACTION;", { 154 | raw = true, 155 | callback = function(err) 156 | callback_done = true 157 | 158 | common.SetPrivate(conn, "locked", true) 159 | 160 | local co = coroutine.create(callback) 161 | local txn = NewTransaction(conn, co, traceback) 162 | common.SetPrivate(conn, "txn", txn) 163 | 164 | if err then 165 | txn.open = false 166 | TxnResume(txn, err) 167 | else 168 | TxnResume(txn, nil, txn) 169 | end 170 | 171 | -- this is a nice way to make it easier to use sync transactions lol 172 | if sync then 173 | while txn.open do 174 | conn:Poll() 175 | end 176 | end 177 | end, 178 | }) 179 | 180 | if sync then 181 | while not callback_done do 182 | conn:Poll() 183 | end 184 | end 185 | end 186 | 187 | 188 | return ConnBegin 189 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/sqlite/main.lua: -------------------------------------------------------------------------------- 1 | local common = include("goobie-sql/common.lua") 2 | local ConnBeginSync = include("goobie-sql/sqlite/txn.lua") 3 | 4 | local STATES = common.STATES 5 | 6 | local type = type 7 | local tostring = tostring 8 | local tonumber = tonumber 9 | local setmetatable = setmetatable 10 | local table_insert = table.insert 11 | local string_format = string.format 12 | local table_HasValue = table.HasValue 13 | local table_concat = table.concat 14 | local errorf = common.errorf 15 | local CheckQuery = common.CheckQuery 16 | local string_gsub = string.gsub 17 | 18 | local CROSS_SYNTAXES = common.CROSS_SYNTAXES.sqlite 19 | 20 | local goobie_sqlite = {} 21 | 22 | local Conn = {} 23 | for k, v in pairs(common.COMMON_META) do 24 | Conn[k] = v 25 | end 26 | 27 | function Conn:IsMySQL() return false end 28 | 29 | function Conn:IsSQLite() return true end 30 | 31 | function Conn:State() return common.GetPrivate(self, "state") end 32 | 33 | -- we delay async start in sqlite to be close as possible to mysql behaviour 34 | function Conn:Start(callback) 35 | if type(callback) ~= "function" then 36 | return error("callback needs to be a function") 37 | end 38 | 39 | if self:State() == STATES.CONNECTING then 40 | return 41 | end 42 | 43 | common.SetPrivate(self, "state", STATES.CONNECTING) 44 | 45 | timer.Simple(0, function() 46 | common.SetPrivate(self, "state", STATES.CONNECTED) 47 | callback() 48 | end) 49 | end 50 | 51 | function Conn:StartSync() 52 | common.SetPrivate(self, "state", STATES.CONNECTED) 53 | end 54 | 55 | function Conn:Disconnect(callback) 56 | if type(callback) ~= "function" then 57 | return error("callback needs to be a function") 58 | end 59 | common.SetPrivate(self, "state", STATES.DISCONNECTED) 60 | callback() 61 | end 62 | 63 | function Conn:DisconnectSync() 64 | common.SetPrivate(self, "state", STATES.DISCONNECTED) 65 | end 66 | 67 | function Conn:ID() return 1 end 68 | 69 | function Conn:Host() return "localhost" end 70 | 71 | function Conn:Port() return 0 end 72 | 73 | function Conn:Ping(callback) 74 | if type(callback) ~= "function" then 75 | return error("callback needs to be a function") 76 | end 77 | callback(nil, 0) 78 | end 79 | 80 | function Conn:PingSync() 81 | return nil, 0 82 | end 83 | 84 | local sqlite_SQLStr = sql.SQLStr 85 | local escape_function = function(value) 86 | local value_type = type(value) 87 | if value_type == "string" then 88 | return (sqlite_SQLStr(value)) 89 | elseif value_type == "number" then 90 | return tostring(value) 91 | elseif value_type == "boolean" then 92 | return value and "TRUE" or "FALSE" 93 | else 94 | return errorf("invalid type '%s' was passed to escape '%s'", value_type, value) 95 | end 96 | end 97 | local function prepare_query(query, opts, is_async) 98 | opts = CheckQuery(query, opts, is_async) 99 | query = string_gsub(query, "{([%w_]+)}", CROSS_SYNTAXES) 100 | local params = opts.params 101 | if not opts.raw then 102 | query, params = common.HandleQueryParams(query, params, escape_function) 103 | end 104 | opts.params = params 105 | return query, opts 106 | end 107 | 108 | local sqlite_Query = sql.Query 109 | local sqlite_LastError = sql.LastError 110 | local function raw_query(query) 111 | local res = sqlite_Query(query) 112 | if res == false then 113 | local last_error = sqlite_LastError() 114 | local err = common.SQLError(last_error) 115 | return err 116 | end 117 | return nil, res 118 | end 119 | 120 | local function ConnProcessQuery(conn, query, opts, async, exec_func) 121 | query, opts = prepare_query(query, opts, async) 122 | if opts.sync then 123 | async = false 124 | end 125 | local err, res = exec_func(query) 126 | if err then 127 | local on_error = conn.on_error 128 | if on_error then 129 | ProtectedCall(on_error, err) 130 | end 131 | end 132 | if async then 133 | if opts.callback then 134 | opts.callback(err, res) 135 | end 136 | else 137 | return err, res 138 | end 139 | end 140 | 141 | function Conn:RunSync(query, opts) 142 | return ConnProcessQuery(self, query, opts, false, raw_query) 143 | end 144 | 145 | function Conn:Run(query, opts) 146 | return ConnProcessQuery(self, query, opts, true, raw_query) 147 | end 148 | 149 | do 150 | local function internal_execute(query) 151 | local err = raw_query(query) 152 | if err then return err end 153 | local info = sqlite_Query("SELECT last_insert_rowid() AS `last_insert_id`, changes() AS `rows_affected`;") 154 | info = info[1] 155 | local res = { 156 | last_insert_id = tonumber(info.last_insert_id), 157 | rows_affected = tonumber(info.rows_affected), 158 | } 159 | return nil, res 160 | end 161 | 162 | function Conn:ExecuteSync(query, opts) 163 | return ConnProcessQuery(self, query, opts, false, internal_execute) 164 | end 165 | 166 | function Conn:Execute(query, opts) 167 | return ConnProcessQuery(self, query, opts, true, internal_execute) 168 | end 169 | end 170 | 171 | do 172 | local function internal_fetch(query) 173 | local err, res = raw_query(query) 174 | if err then return err end 175 | return nil, res or {} 176 | end 177 | 178 | function Conn:FetchSync(query, opts) 179 | return ConnProcessQuery(self, query, opts, false, internal_fetch) 180 | end 181 | 182 | function Conn:Fetch(query, opts) 183 | return ConnProcessQuery(self, query, opts, true, internal_fetch) 184 | end 185 | end 186 | 187 | do 188 | local function internal_fetch_one(query) 189 | local err, res = raw_query(query) 190 | if err then return err end 191 | return nil, res and res[1] or nil 192 | end 193 | 194 | function Conn:FetchOneSync(query, opts) 195 | return ConnProcessQuery(self, query, opts, false, internal_fetch_one) 196 | end 197 | 198 | function Conn:FetchOne(query, opts) 199 | return ConnProcessQuery(self, query, opts, true, internal_fetch_one) 200 | end 201 | end 202 | 203 | function Conn:TableExists(name) 204 | if type(name) ~= "string" then 205 | return error("table name must be a string") 206 | end 207 | local err, res = raw_query("SELECT name FROM sqlite_master WHERE name=" .. sqlite_SQLStr(name) .. " AND type='table'") 208 | if err then 209 | return nil, err 210 | end 211 | return res ~= nil 212 | end 213 | 214 | do 215 | local query_count = 0 216 | local query_parts = {} 217 | 218 | local function insert_to_query(str) 219 | query_count = query_count + 1 220 | query_parts[query_count] = str 221 | end 222 | 223 | local function ConnUpsertQuery(conn, tbl_name, opts, sync) 224 | if type(tbl_name) ~= "string" then 225 | return error("table name must be a string") 226 | end 227 | 228 | query_count = 0 229 | 230 | local primary_keys = opts.primary_keys 231 | local inserts = opts.inserts 232 | local updates = opts.updates 233 | local no_escape_columns = opts.no_escape_columns 234 | local binary_columns = opts.binary_columns 235 | 236 | local values = { nil, nil, nil, nil, nil, nil } 237 | 238 | insert_to_query("INSERT INTO`") 239 | insert_to_query(tbl_name) 240 | insert_to_query("`(") 241 | 242 | for column, value in pairs(inserts) do 243 | insert_to_query("`" .. column .. "`") 244 | insert_to_query(",") 245 | if no_escape_columns and table_HasValue(no_escape_columns, column) then 246 | table_insert(values, common.HandleNoEscape(value)) 247 | elseif binary_columns and table_HasValue(binary_columns, column) then 248 | value = common.StringToHex(value) 249 | table_insert(values, "X'" .. value .. "'") 250 | else 251 | table_insert(values, sqlite_SQLStr(value)) 252 | end 253 | end 254 | query_count = query_count - 1 -- remove last comma 255 | 256 | insert_to_query(")VALUES(") 257 | insert_to_query(table_concat(values, ",")) 258 | insert_to_query(")ON CONFLICT(") 259 | 260 | for i = 1, #primary_keys do 261 | insert_to_query("`" .. primary_keys[i] .. "`") 262 | insert_to_query(",") 263 | end 264 | query_count = query_count - 1 -- remove last comma 265 | 266 | if updates == nil or #updates == 0 then 267 | insert_to_query(")DO NOTHING") 268 | else 269 | insert_to_query(")DO UPDATE SET ") 270 | 271 | for i = 1, #updates do 272 | local column = updates[i] 273 | insert_to_query(string_format("`%s`=excluded.`%s`", column, column)) 274 | insert_to_query(",") 275 | end 276 | 277 | query_count = query_count - 1 -- remove last comma 278 | end 279 | 280 | local query = table_concat(query_parts, nil, 1, query_count) 281 | 282 | if opts.return_query then 283 | return query, {} 284 | end 285 | 286 | if sync then 287 | local err, res = conn:ExecuteSync(query, opts) 288 | return err, res 289 | else 290 | local err, res = conn:Execute(query, opts) 291 | return err, res 292 | end 293 | end 294 | 295 | function Conn:UpsertQuery(tbl_name, opts) 296 | return ConnUpsertQuery(self, tbl_name, opts, false) 297 | end 298 | 299 | function Conn:UpsertQuerySync(tbl_name, opts) 300 | return ConnUpsertQuery(self, tbl_name, opts, true) 301 | end 302 | end 303 | 304 | function Conn:BeginSync(callback) 305 | return ConnBeginSync(self, callback) 306 | end 307 | 308 | Conn.Begin = Conn.BeginSync 309 | 310 | function goobie_sqlite.NewConn(opts) 311 | local conn = setmetatable({}, { 312 | __index = Conn, 313 | __tostring = function() 314 | return "Goobie SQLite Connection" 315 | end 316 | }) 317 | common.SetPrivate(conn, "state", STATES.NOT_CONNECTED) 318 | return conn 319 | end 320 | 321 | return goobie_sqlite 322 | -------------------------------------------------------------------------------- /sql/lua/goobie-sql/sqlite/txn.lua: -------------------------------------------------------------------------------- 1 | local setmetatable = setmetatable 2 | 3 | local Txn = {} 4 | local Txn_MT = { __index = Txn } 5 | 6 | local function NewTransaction(conn) 7 | local txn = setmetatable({ 8 | open = true, 9 | conn = conn, 10 | options = conn.options, 11 | }, Txn_MT) 12 | return txn 13 | end 14 | 15 | function Txn:IsOpen() return self.open end 16 | 17 | function Txn:PingSync() 18 | return self.conn:PingSync() 19 | end 20 | 21 | function Txn:Ping(callback) 22 | return self.conn:Ping(callback) 23 | end 24 | 25 | function Txn:Run(query, opts) 26 | if not self:IsOpen() then 27 | return error("transaction is closed") 28 | end 29 | return self.conn:RunSync(query, opts) 30 | end 31 | 32 | function Txn:Execute(query, opts) 33 | if not self:IsOpen() then 34 | return error("transaction is closed") 35 | end 36 | return self.conn:ExecuteSync(query, opts) 37 | end 38 | 39 | function Txn:Fetch(query, opts) 40 | if not self:IsOpen() then 41 | return error("transaction is closed") 42 | end 43 | return self.conn:FetchSync(query, opts) 44 | end 45 | 46 | function Txn:FetchOne(query, opts) 47 | if not self:IsOpen() then 48 | return error("transaction is closed") 49 | end 50 | opts = opts or {} 51 | opts.sync = true 52 | return self.conn:FetchOneSync(query, opts) 53 | end 54 | 55 | function Txn:TableExists(name) 56 | if not self:IsOpen() then 57 | return error("transaction is closed") 58 | end 59 | return self.conn:TableExists(name) 60 | end 61 | 62 | function Txn:UpsertQuery(tbl_name, opts) 63 | if not self:IsOpen() then 64 | return error("transaction is closed") 65 | end 66 | return self.conn:UpsertQuerySync(tbl_name, opts) 67 | end 68 | 69 | function Txn:Commit() 70 | if not self:IsOpen() then 71 | return error("transaction is closed") 72 | end 73 | self.open = false 74 | local err = self.conn:RunSync("COMMIT TRANSACTION") 75 | if err then 76 | self.conn:RunSync("ROLLBACK TRANSACTION") 77 | end 78 | return err 79 | end 80 | 81 | function Txn:Rollback() 82 | if not self:IsOpen() then 83 | return error("transaction is closed") 84 | end 85 | self.open = false 86 | local err = self.conn:RunSync("ROLLBACK TRANSACTION") 87 | return err 88 | end 89 | 90 | local function ConnBeginSync(conn, callback) 91 | local status, err 92 | 93 | local txn = NewTransaction(conn) 94 | -- if creating the transaction fails, do not rollback 95 | local should_rollback = true 96 | -- this probably will only error when you try to begin a transaction inside another transaction 97 | err = conn:RunSync("BEGIN TRANSACTION") 98 | if err then 99 | txn.open = false 100 | should_rollback = false 101 | end 102 | 103 | status, err = pcall(callback, err, txn) 104 | if status ~= true then 105 | if should_rollback and txn:IsOpen() then 106 | txn:Rollback() 107 | end 108 | ErrorNoHaltWithStack(err) 109 | return 110 | end 111 | 112 | if txn:IsOpen() then 113 | ErrorNoHaltWithStack("transactions was left open!\n") 114 | txn:Rollback() 115 | end 116 | end 117 | 118 | return ConnBeginSync 119 | -------------------------------------------------------------------------------- /sql/tests.lua: -------------------------------------------------------------------------------- 1 | local goobie_sql = include("goobie-sql/goobie-sql.lua") 2 | 3 | local TestSuite = {} 4 | TestSuite.__index = TestSuite 5 | 6 | function TestSuite.New() 7 | return setmetatable({ 8 | tests = {} 9 | }, TestSuite) 10 | end 11 | 12 | function TestSuite:Add(name, fn) 13 | table.insert(self.tests, { name = name, fn = fn }) 14 | end 15 | 16 | function TestSuite:Run(on_start, after_each, on_end, conn) 17 | local i = 1 18 | 19 | local function runNextTest() 20 | if i > #self.tests then 21 | on_end(conn) 22 | print("All tests complete.") 23 | return 24 | end 25 | 26 | local test = self.tests[i] 27 | i = i + 1 28 | print("Running test: " .. test.name) 29 | local callbackCalled = false 30 | 31 | local function nextCallback() 32 | if callbackCalled then return end 33 | callbackCalled = true 34 | after_each(conn) 35 | runNextTest() 36 | end 37 | 38 | local success, err = pcall(test.fn, nextCallback, conn) 39 | if not success then 40 | print("Test '" .. test.name .. "' failed with error: " .. err) 41 | nextCallback() 42 | end 43 | end 44 | 45 | on_start(conn) 46 | 47 | runNextTest() 48 | end 49 | 50 | local suite = TestSuite.New() 51 | 52 | suite:Add("ConnID", function(next, conn) 53 | assert(conn:ID() == 1, "ID should be 1") 54 | next() 55 | end) 56 | 57 | suite:Add("ConnHost", function(next, conn) 58 | assert(type(conn:Host()) == "string", "Host should be a string") 59 | next() 60 | end) 61 | 62 | suite:Add("ConnPort", function(next, conn) 63 | assert(type(conn:Port()) == "number", "Port should be a number") 64 | next() 65 | end) 66 | 67 | suite:Add("ConnPing", function(next, conn) 68 | conn:Ping(function(err, latency) 69 | assert(err == nil, "Ping should succeed without error") 70 | assert(type(latency) == "number", "Latency should be a number") 71 | next() 72 | end) 73 | end) 74 | 75 | suite:Add("ConnPingSync", function(next, conn) 76 | local err, latency = conn:PingSync() 77 | assert(err == nil, "PingSync should succeed without error") 78 | assert(type(latency) == "number", "Latency should be a number") 79 | next() 80 | end) 81 | 82 | suite:Add("ConnRun", function(next, conn) 83 | conn:Run("INSERT INTO test_table (value) VALUES ({1})", { 84 | params = { "test" }, 85 | callback = function(err) 86 | assert(err == nil, "Run should insert without error") 87 | next() 88 | end 89 | }) 90 | end) 91 | 92 | suite:Add("ConnRunSync", function(next, conn) 93 | local err = conn:RunSync("INSERT INTO test_table (value) VALUES ({1})", { 94 | params = { "test" } 95 | }) 96 | assert(err == nil, "RunSync should insert without error") 97 | next() 98 | end) 99 | 100 | suite:Add("ConnExecute", function(next, conn) 101 | conn:Execute("INSERT INTO test_table (value) VALUES ({1})", { 102 | params = { "test" }, 103 | callback = function(err, res) 104 | assert(err == nil, "Execute should insert without error") 105 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 106 | assert(res.rows_affected == 1, "Rows affected should be 1") 107 | next() 108 | end 109 | }) 110 | end) 111 | 112 | suite:Add("ConnExecuteSync", function(next, conn) 113 | local err, res = conn:ExecuteSync("INSERT INTO test_table (value) VALUES ({1})", { 114 | params = { "test" } 115 | }) 116 | assert(err == nil, "ExecuteSync should insert without error") 117 | assert(res.rows_affected == 1, "Rows affected should be 1") 118 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 119 | next() 120 | end) 121 | 122 | suite:Add("ConnFetch", function(next, conn) 123 | conn:RunSync("INSERT INTO test_table (value) VALUES ({1})", { 124 | params = { "test" } 125 | }) 126 | conn:Fetch("SELECT * FROM test_table", { 127 | callback = function(err, res) 128 | assert(err == nil, "Fetch should succeed without error") 129 | assert(#res == 1, "Should return one row") 130 | assert(res[1].value == "test", "Value should be 'test'") 131 | next() 132 | end 133 | }) 134 | end) 135 | 136 | suite:Add("ConnFetchSync", function(next, conn) 137 | conn:RunSync("INSERT INTO test_table (value) VALUES ({1})", { 138 | params = { "test" } 139 | }) 140 | local err, res = conn:FetchSync("SELECT * FROM test_table") 141 | assert(err == nil, "FetchSync should succeed without error") 142 | assert(#res == 1, "Should return one row") 143 | assert(res[1].value == "test", "Value should be 'test'") 144 | next() 145 | end) 146 | 147 | suite:Add("ConnFetchOne", function(next, conn) 148 | conn:RunSync("INSERT INTO test_table (value) VALUES ({1})", { 149 | params = { "test" } 150 | }) 151 | conn:FetchOne("SELECT * FROM test_table", { 152 | callback = function(err, res) 153 | assert(err == nil, "FetchOne should succeed without error") 154 | assert(res.value == "test", "Value should be 'test'") 155 | next() 156 | end 157 | }) 158 | end) 159 | 160 | suite:Add("ConnFetchOneSync", function(next, conn) 161 | conn:RunSync("INSERT INTO test_table (value) VALUES ({1})", { 162 | params = { "test" } 163 | }) 164 | local err, res = conn:FetchOneSync("SELECT * FROM test_table") 165 | assert(err == nil, "FetchOneSync should succeed without error") 166 | assert(res.value == "test", "Value should be 'test'") 167 | next() 168 | end) 169 | 170 | suite:Add("ConnTableExists", function(next, conn) 171 | local exists, err = conn:TableExists("test_table") 172 | assert(exists, "Table 'test_table' should exist") 173 | assert(err == nil, "No error expected") 174 | next() 175 | end) 176 | 177 | suite:Add("UpsertQuery", function(next, conn) 178 | conn:UpsertQuery("test_table", { 179 | primary_keys = { "id" }, 180 | inserts = { 181 | id = 1, 182 | value = "test", 183 | }, 184 | updates = { 185 | value = "test2" 186 | }, 187 | callback = function(err, res) 188 | assert(err == nil, "UpsertQuery should succeed without error") 189 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 190 | assert(res.rows_affected == 1, "Rows affected should be 1") 191 | next() 192 | end 193 | }) 194 | end) 195 | 196 | suite:Add("UpsertQuerySync", function(next, conn) 197 | local err, res = conn:UpsertQuerySync("test_table", { 198 | primary_keys = { "id" }, 199 | inserts = { 200 | id = 1, 201 | value = "test", 202 | }, 203 | updates = { 204 | value = "test2" 205 | }, 206 | }) 207 | assert(err == nil, "UpsertQuerySync should succeed without error") 208 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 209 | assert(res.rows_affected == 1, "Rows affected should be 1") 210 | next() 211 | end) 212 | 213 | suite:Add("BeginCommit", function(next, conn) 214 | conn:Begin(function(err, txn) 215 | assert(err == nil, "Begin should succeed without error") 216 | assert(txn:IsOpen(), "Transaction should be open") 217 | 218 | local res 219 | 220 | err, res = txn:Execute("INSERT INTO test_table (value) VALUES ('test')") 221 | assert(err == nil, "Run should insert without error") 222 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 223 | assert(res.rows_affected == 1, "Rows affected should be 1") 224 | 225 | err, res = txn:FetchOne("SELECT * FROM test_table") 226 | assert(err == nil, "FetchOne should succeed without error") 227 | assert(res.value == "test", "Value should be 'test'") 228 | 229 | err = txn:Commit() 230 | assert(err == nil, "Commit should succeed without error") 231 | assert(txn:IsOpen() == false, "Transaction should be closed") 232 | 233 | err, res = conn:FetchOneSync("SELECT * FROM test_table") 234 | assert(err == nil, "FetchOne should succeed without error") 235 | assert(res.value == "test", "Value should be 'test'") 236 | 237 | next() 238 | end) 239 | end) 240 | 241 | suite:Add("BeginRollback", function(next, conn) 242 | conn:Begin(function(err, txn) 243 | assert(err == nil, "Begin should succeed without error") 244 | assert(txn:IsOpen(), "Transaction should be open") 245 | 246 | local res 247 | 248 | err, res = txn:Execute("INSERT INTO test_table (value) VALUES ('test')") 249 | assert(err == nil, "Run should insert without error") 250 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 251 | assert(res.rows_affected == 1, "Rows affected should be 1") 252 | 253 | err, res = txn:FetchOne("SELECT * FROM test_table") 254 | assert(err == nil, "FetchOne should succeed without error") 255 | assert(res.value == "test", "Value should be 'test'") 256 | 257 | err = txn:Rollback() 258 | assert(err == nil, "Rollback should succeed without error") 259 | assert(txn:IsOpen() == false, "Transaction should be closed") 260 | 261 | err, res = conn:FetchOneSync("SELECT * FROM test_table") 262 | assert(err == nil, "FetchOne should succeed without error") 263 | assert(res == nil, "Value should be nil") 264 | 265 | next() 266 | end) 267 | end) 268 | 269 | suite:Add("BeginSyncCommit", function(next, conn) 270 | conn:BeginSync(function(err, txn) 271 | assert(err == nil, "BeginSync should succeed without error") 272 | assert(txn:IsOpen(), "Transaction should be open") 273 | 274 | local res 275 | 276 | err, res = txn:Execute("INSERT INTO test_table (value) VALUES ('test')") 277 | assert(err == nil, "Run should insert without error") 278 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 279 | assert(res.rows_affected == 1, "Rows affected should be 1") 280 | 281 | err, res = txn:FetchOne("SELECT * FROM test_table") 282 | assert(err == nil, "FetchOne should succeed without error") 283 | assert(res.value == "test", "Value should be 'test'") 284 | 285 | err = txn:Commit() 286 | assert(err == nil, "Commit should succeed without error") 287 | assert(txn:IsOpen() == false, "Transaction should be closed") 288 | 289 | err, res = conn:FetchOneSync("SELECT * FROM test_table") 290 | assert(err == nil, "FetchOne should succeed without error") 291 | assert(res.value == "test", "Value should be 'test'") 292 | 293 | next() 294 | end) 295 | end) 296 | 297 | suite:Add("BeginSyncRollback", function(next, conn) 298 | conn:BeginSync(function(err, txn) 299 | assert(err == nil, "BeginSync should succeed without error") 300 | assert(txn:IsOpen(), "Transaction should be open") 301 | 302 | local res 303 | 304 | err, res = txn:Execute("INSERT INTO test_table (value) VALUES ('test')") 305 | assert(err == nil, "Run should insert without error") 306 | assert(res.last_insert_id == 1, "Last insert ID should be 1") 307 | assert(res.rows_affected == 1, "Rows affected should be 1") 308 | 309 | err, res = txn:FetchOne("SELECT * FROM test_table") 310 | assert(err == nil, "FetchOne should succeed without error") 311 | assert(res.value == "test", "Value should be 'test'") 312 | 313 | err = txn:Rollback() 314 | assert(err == nil, "Rollback should succeed without error") 315 | assert(txn:IsOpen() == false, "Transaction should be closed") 316 | 317 | err, res = conn:FetchOneSync("SELECT * FROM test_table") 318 | assert(err == nil, "FetchOne should succeed without error") 319 | assert(res == nil, "Value should be nil") 320 | end) 321 | 322 | next() 323 | end) 324 | 325 | print("\n\n\n\n\n\n") 326 | 327 | local function on_start(conn) 328 | assert(conn:IsConnected(), "Connection should be connected after StartSync") 329 | local createQuery = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, value TEXT)" 330 | if conn:IsMySQL() then 331 | createQuery = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY AUTO_INCREMENT, value TEXT)" 332 | end 333 | local opts = { params = {} } 334 | local err = conn:RunSync(createQuery, opts) 335 | assert(not err, "Failed to create table") 336 | end 337 | 338 | local function after_each(conn) 339 | conn:RunSync("DELETE FROM test_table") 340 | if conn:IsMySQL() then 341 | -- mysql does not reset the auto increment counter after a delete 342 | conn:RunSync("TRUNCATE TABLE test_table") 343 | end 344 | end 345 | 346 | local function on_end(conn) 347 | conn:RunSync("DROP TABLE test_table") 348 | end 349 | 350 | 351 | local sqlite_conn = goobie_sql.NewConn({ driver = "sqlite", addon_name = "test" }) 352 | local mysql_conn = goobie_sql.NewConn({ driver = "mysql", uri = "mysql://USER:PASS@IP/DB", addon_name = "test" }) 353 | 354 | MsgC("Running: ", Color(0, 255, 0, 255), "SQLite tests", "\n") 355 | suite:Run(on_start, after_each, on_end, sqlite_conn) 356 | 357 | MsgC("Running: ", Color(255, 0, 0, 255), "MySQL tests", "\n") 358 | suite:Run(on_start, after_each, on_end, mysql_conn) 359 | --------------------------------------------------------------------------------