{"id":26467468,"url":"https://github.com/srlion/goobie-sql","last_synced_at":"2026-01-07T02:16:29.302Z","repository":{"id":259003898,"uuid":"868399238","full_name":"Srlion/goobie-sql","owner":"Srlion","description":"Easily connect to MySQL in Garry's Mod using Rust binary!","archived":false,"fork":false,"pushed_at":"2025-03-11T10:06:47.000Z","size":103,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-14T10:01:44.493Z","etag":null,"topics":["async","binary","fast","garrysmod","gmod","gmod-mysql","graceful","graceful-shutdown","mysql","rust","rust-lang","rustlang","simple","sql","transactions"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Srlion.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-06T09:35:08.000Z","updated_at":"2025-03-13T14:02:35.000Z","dependencies_parsed_at":"2025-01-11T01:33:41.156Z","dependency_job_id":"306dafb2-2b11-4268-b76e-b03e5d52e572","html_url":"https://github.com/Srlion/goobie-sql","commit_stats":null,"previous_names":["srlion/goobie-mysql"],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Srlion%2Fgoobie-sql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Srlion%2Fgoobie-sql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Srlion%2Fgoobie-sql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Srlion%2Fgoobie-sql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Srlion","download_url":"https://codeload.github.com/Srlion/goobie-sql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244450043,"owners_count":20454590,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","binary","fast","garrysmod","gmod","gmod-mysql","graceful","graceful-shutdown","mysql","rust","rust-lang","rustlang","simple","sql","transactions"],"created_at":"2025-03-19T14:51:56.054Z","updated_at":"2025-12-24T03:58:41.830Z","avatar_url":"https://github.com/Srlion.png","language":"Lua","readme":"# Goobie SQL\n\nA simple, lightweight, and fast MySQL/SQLite library for Garry's Mod.\n\n\u003e **⚠️ Note:** The latest version requires the next Garry's Mod update (available on dev/x86-64 branch).\n\n## 📋 Table of Contents\n\n- [🚀 Features](#-features)\n- [📦 Installation](#-installation)\n- [🛠️ Quick Setup](#️-quick-setup)\n- [📚 API Reference](#-api-reference)\n  - [Connection Setup](#connection-setup)\n  - [Connection Management](#connection-management)\n  - [Query Methods](#query-methods)\n  - [Transactions](#transactions)\n  - [Migrations](#migrations)\n  - [Cross Syntaxes](#cross-syntaxes)\n- [🔧 Configuration Options](#-configuration-options)\n- [📄 Examples](#-examples)\n\n## 🚀 Features\n\n- ✅ **Dual Database Support** - MySQL and SQLite\n- ⚡ **Async \u0026 Sync** - Both asynchronous and synchronous queries\n- 🔄 **Easy Transactions** - Simple transaction handling with coroutines\n- 📊 **Migration System** - Built-in database migrations\n- 📦 **Single File** - One-file library for easy integration\n\n## 📦 Installation\n\n1. Download the latest `goobie-sql.lua` from [GitHub Releases](https://github.com/Srlion/goobie-sql/releases/latest)\n\n2. **For MySQL users:** Also download `gmsv_goobie_mysql_x_x_x.dll` and extract to:\n   ```\n   garrysmod/lua/bin/gmsv_goobie_mysql_x_x_x.dll\n   ```\n\n3. Add `goobie-sql.lua` to your addon's `thirdparty` folder\n\n## 🛠️ Quick Setup\n\n### SQLite (Recommended for beginners)\n```lua\nlocal goobie_sql = include(\"myaddon/thirdparty/goobie-sql.lua\")\nlocal conn = goobie_sql.NewConn({\n    driver = \"sqlite\",\n})\n```\n\n### MySQL\n```lua\nlocal goobie_sql = include(\"myaddon/thirdparty/goobie-sql.lua\")\nlocal conn = goobie_sql.NewConn({\n    driver = \"mysql\",\n    uri = \"mysql://USERNAME:PASSWORD@HOST/DATABASE\",\n})\n```\n\n## 📚 API Reference\n\n### Connection Setup\n\n#### `goobie_sql.NewConn(options)`\nCreates and starts a connection automatically. For async initialization, pass a callback as the second argument.\n\n**Parameters:**\n- `options` (table) - Connection configuration options\n\n**Example:**\n```lua\nlocal conn = goobie_sql.NewConn({\n    driver = \"mysql\", -- \"mysql\" or \"sqlite\"\n    \n    -- Error handling callback\n    on_error = function(err, trace)\n        print(\"Database error:\", err)\n    end,\n\n    -- MySQL Options (choose URI or individual options)\n    uri = \"mysql://user:pass@host:port/db\", -- Recommended\n    -- OR\n    host = \"127.0.0.1\",\n    port = 3306,\n    username = \"root\",\n    password = \"password\",\n    database = \"mydb\",\n    \n    -- Additional MySQL settings\n    charset = \"utf8mb4\",\n    collation = \"utf8mb4_unicode_ci\",\n    timezone = \"UTC\",\n    statement_cache_capacity = 100,\n    socket = \"/tmp/mysql.sock\",\n})\n```\n\n### Connection Management\n\n| Method | Description | Returns |\n|--------|-------------|---------|\n| [`Conn:Start(callback)`](#connstart) | Connect asynchronously | - |\n| [`Conn:StartSync()`](#connstartsync) | Connect synchronously | throws on error |\n| [`Conn:Disconnect(callback)`](#conndisconnect) | Disconnect asynchronously | - |\n| [`Conn:DisconnectSync()`](#conndisconnectsync) | Disconnect synchronously | `err` |\n| [`Conn:State()`](#connstate) | Get connection state | `number` |\n| [`Conn:StateName()`](#connstatename) | Get connection state name | `string` |\n| [`Conn:ID()`](#connid) | Get connection ID | `number` |\n| [`Conn:Host()`](#connhost) | Get host | `string` |\n| [`Conn:Port()`](#connport) | Get port | `number` |\n| [`Conn:Ping(callback)`](#connping) | Ping database async | - |\n| [`Conn:PingSync()`](#connpingsync) | Ping database sync | `err, latency` |\n\n### Query Methods\n\n| Method | Type | Description | Returns |\n|--------|------|-------------|---------|\n| [`Conn:Run(query, opts)`](#connrun) | Async | Execute query (no result) | - |\n| [`Conn:RunSync(query, opts)`](#connrunsync) | Sync | Execute query (no result) | `err` |\n| [`Conn:Execute(query, opts)`](#connexecute) | Async | Execute with metadata | - |\n| [`Conn:ExecuteSync(query, opts)`](#connexecutesync) | Sync | Execute with metadata | `err, result` |\n| [`Conn:Fetch(query, opts)`](#connfetch) | Async | Fetch multiple rows | - |\n| [`Conn:FetchSync(query, opts)`](#connfetchsync) | Sync | Fetch multiple rows | `err, rows` |\n| [`Conn:FetchOne(query, opts)`](#connfetchone) | Async | Fetch single row | - |\n| [`Conn:FetchOneSync(query, opts)`](#connfetchonesync) | Sync | Fetch single row | `err, row` |\n| [`Conn:UpsertQuery(table, opts)`](#connupsertquery) | Async | Insert or update | - |\n| [`Conn:UpsertQuerySync(table, opts)`](#connupsertquerysync) | Sync | Insert or update | `err, result` |\n\n#### Query Options\n```lua\n{\n    params = {\"value1\", \"value2\"}, -- Parameters for placeholders {1}, {2}\n    callback = function(err, res) end, -- Async callback\n    raw = false -- Set true for multi-statement queries (no params)\n}\n```\n\n### Transactions\n\nUse `Begin()` or `BeginSync()` for database transactions. Inside transactions, queries return results directly (no callbacks).\n\n```lua\n-- Async transaction\nconn:Begin(function(err, txn)\n    if err then return end\n    \n    local err, res = txn:Execute(\"INSERT INTO users (name) VALUES ('John')\")\n    if err then\n        txn:Rollback() -- Must rollback explicitly on error\n        return\n    end\n    \n    local err = txn:Commit()\n    print(\"Transaction complete, open:\", txn:IsOpen()) -- false\nend)\n\n-- Sync transaction\nlocal err, txn = conn:BeginSync()\nif not err then\n    local err, res = txn:Execute(\"INSERT INTO users (name) VALUES ('Jane')\")\n    if err then\n        txn:Rollback()\n    else\n        txn:Commit()\n    end\nend\n```\n\n### Migrations\n\nRun database migrations with version tracking:\n\n```lua\nlocal conn = goobie_sql.NewConn({\n    driver = \"sqlite\",\n    addon_name = \"my_addon\", -- Required for migration tracking\n})\n\nlocal current_version, first_run = conn:RunMigrations({\n    -- Migration 1: String format\n    {\n        UP = [[\n            CREATE TABLE users (\n                id {CROSS_PRIMARY_AUTO_INCREMENTED},\n                name TEXT NOT NULL,\n                created_at {CROSS_OS_TIME_TYPE}\n            );\n        ]],\n        DOWN = \"DROP TABLE users;\"\n    },\n    \n    -- Migration 2: Function format\n    {\n        UP = function(process, conn)\n            process(\"ALTER TABLE users ADD COLUMN email TEXT;\")\n        end,\n        DOWN = function(process, conn)\n            process(\"ALTER TABLE users DROP COLUMN email;\")\n        end\n    }\n})\n\nprint(\"Database version:\", current_version, \"First run:\", first_run)\n```\n\n#### Conditional Migrations\n```lua\n{\n    UP = [[\n        CREATE TABLE test (\n        --@ifdef SQLITE\n            id INTEGER PRIMARY KEY,\n        --@else\n            id BIGINT AUTO_INCREMENT PRIMARY KEY,\n        --@endif\n            name TEXT\n        );\n    ]]\n}\n```\n\n### Cross Syntaxes\n\nWrite database-agnostic queries using cross-syntax placeholders:\n\n| Placeholder | SQLite | MySQL |\n|-------------|---------|-------|\n| `{CROSS_NOW}` | `(CAST(strftime('%s', 'now') AS INTEGER))` | `(UNIX_TIMESTAMP())` |\n| `{CROSS_PRIMARY_AUTO_INCREMENTED}` | `INTEGER PRIMARY KEY` | `BIGINT AUTO_INCREMENT PRIMARY KEY` |\n| `{CROSS_COLLATE_BINARY}` | `COLLATE BINARY` | `BINARY` |\n| `{CROSS_CURRENT_DATE}` | `DATE('now')` | `CURDATE()` |\n| `{CROSS_OS_TIME_TYPE}` | `INT UNSIGNED NOT NULL DEFAULT (...)` | `INT UNSIGNED NOT NULL DEFAULT (...)` |\n| `{CROSS_INT_TYPE}` | `INTEGER` | `BIGINT` |\n| `{CROSS_JSON_TYPE}` | `TEXT` | `JSON` |\n\n**Example:**\n```lua\nconn:RunSync([[\n    SELECT * FROM users WHERE created_at \u003e {CROSS_NOW}\n]])\n```\n\n## 🔧 Configuration Options\n\n### Error Object Structure\n```lua\n{\n    message = \"Error description\",\n    code = 1234, -- MySQL error code (optional)\n    sqlstate = \"42000\" -- SQL state code (optional)\n}\n-- Has __tostring metamethod for easy printing\n```\n\n### UpsertQuery Options\n```lua\nlocal opts = {\n    primary_keys = {\"id\"}, -- Unique/primary keys that could conflict\n    inserts = {            -- Values to insert\n        id = 1,\n        name = \"John\",\n        email = \"john@example.com\"\n    },\n    updates = {\"name\", \"email\"}, -- Columns to update on conflict\n    binary_columns = {\"data\"},   -- Binary columns (SQLite specific)\n    callback = function(err, res) end -- Async callback\n}\n\nconn:UpsertQuery(\"users\", opts)\n-- OR\nlocal err, res = conn:UpsertQuerySync(\"users\", opts)\n```\n\n## 📄 Examples\n\n### Basic Query Examples\n```lua\n-- Simple insert\nconn:Execute(\"INSERT INTO users (name) VALUES ({1})\", {\n    params = {\"Alice\"},\n    callback = function(err, res)\n        if err then\n            print(\"Error:\", err)\n        else\n            print(\"Inserted ID:\", res.last_insert_id)\n            print(\"Rows affected:\", res.rows_affected)\n        end\n    end\n})\n\n-- Fetch multiple rows\nconn:Fetch(\"SELECT * FROM users WHERE age \u003e {1}\", {\n    params = {18},\n    callback = function(err, rows)\n        if not err then\n            for i, row in ipairs(rows) do\n                print(\"User:\", row.name, \"Age:\", row.age)\n            end\n        end\n    end\n})\n\n-- Fetch single row\nconn:FetchOne(\"SELECT * FROM users WHERE id = {1}\", {\n    params = {1},\n    callback = function(err, user)\n        if not err and user then\n            print(\"Found user:\", user.name)\n        end\n    end\n})\n```\n\n### Synchronous Examples\n```lua\n-- Synchronous queries (easier for simple operations)\nlocal err = conn:RunSync(\"DELETE FROM users WHERE inactive = 1\")\nif err then\n    print(\"Delete failed:\", err)\nend\n\nlocal err, users = conn:FetchSync(\"SELECT * FROM users LIMIT 10\")\nif not err then\n    print(\"Found\", #users, \"users\")\nend\n```\n\n### Advanced Transaction Example\n```lua\nconn:Begin(function(err, txn)\n    if err then return end\n    \n    -- Transfer money between accounts\n    local err, sender = txn:FetchOne(\"SELECT balance FROM accounts WHERE id = {1}\", {\n        params = {sender_id}\n    })\n    if err or not sender or sender.balance \u003c amount then\n        txn:Rollback()\n        return\n    end\n    \n    -- Deduct from sender\n    local err = txn:Execute(\"UPDATE accounts SET balance = balance - {1} WHERE id = {2}\", {\n        params = {amount, sender_id}\n    })\n    if err then\n        txn:Rollback()\n        return\n    end\n    \n    -- Add to receiver\n    local err = txn:Execute(\"UPDATE accounts SET balance = balance + {1} WHERE id = {2}\", {\n        params = {amount, receiver_id}\n    })\n    if err then\n        txn:Rollback()\n        return\n    end\n    \n    -- Commit transaction\n    local err = txn:Commit()\n    if not err then\n        print(\"Transfer completed successfully!\")\n    end\nend)\n```\n\n### Complete Setup Example\n```lua\n-- Complete example with error handling and migrations\nlocal goobie_sql = include(\"myaddon/thirdparty/goobie-sql.lua\")\n\nlocal conn = goobie_sql.NewConn({\n    driver = \"mysql\",\n    uri = \"mysql://user:pass@localhost/gamedb\",\n    addon_name = \"my_gamemode\",\n    \n    on_error = function(err, trace)\n        print(\"[DB Error]\", err.message)\n        if err.code then\n            print(\"Error code:\", err.code)\n        end\n    end\n})\n\n-- Run migrations\nlocal version, first_run = conn:RunMigrations({\n    {\n        UP = [[\n            CREATE TABLE players (\n                steam_id VARCHAR(32) PRIMARY KEY,\n                name VARCHAR(64) NOT NULL,\n                playtime {CROSS_INT_TYPE} DEFAULT 0,\n                created_at {CROSS_OS_TIME_TYPE}\n            );\n        ]],\n        DOWN = \"DROP TABLE players;\"\n    },\n    {\n        UP = \"ALTER TABLE players ADD COLUMN last_seen {CROSS_OS_TIME_TYPE};\",\n        DOWN = \"ALTER TABLE players DROP COLUMN last_seen;\"\n    }\n})\n\nif first_run then\n    print(\"Database initialized for the first time!\")\nelse\n    print(\"Database updated to version\", version)\nend\n\n-- Use the connection\nconn:UpsertQuery(\"players\", {\n    primary_keys = {\"steam_id\"},\n    inserts = {\n        steam_id = \"STEAM_1:0:123456\",\n        name = \"PlayerName\",\n        playtime = 0\n    },\n    updates = {\"name\", \"last_seen\"}\n})\n```\n\n---\n\n## 📞 Support\n\n- **Documentation:** [MySQL URI Format](https://docs.rs/sqlx/latest/sqlx/mysql/struct.MySqlConnectOptions.html)\n- **Issues:** [GitHub Issues](https://github.com/Srlion/goobie-sql/issues)\n- **Releases:** [GitHub Releases](https://github.com/Srlion/goobie-sql/releases)\n\n\u003e 💡 **Pro Tip:** Ping connections sparingly! Check [this article](https://www.percona.com/blog/checking-for-a-live-database-connection-considered-harmful/) on why frequent connection pinging can be harmful.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrlion%2Fgoobie-sql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsrlion%2Fgoobie-sql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrlion%2Fgoobie-sql/lists"}