{"id":13529016,"url":"https://github.com/speed2exe/myzql","last_synced_at":"2025-04-19T11:55:51.976Z","repository":{"id":202942745,"uuid":"672293790","full_name":"speed2exe/myzql","owner":"speed2exe","description":"MySQL and MariaDB  driver in native Zig","archived":false,"fork":false,"pushed_at":"2024-09-12T14:32:00.000Z","size":379,"stargazers_count":23,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-10-19T02:30:26.424Z","etag":null,"topics":["driver","mariadb","mysql","sql","zig","zig-library","zig-package"],"latest_commit_sha":null,"homepage":"","language":"Zig","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/speed2exe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2023-07-29T15:22:58.000Z","updated_at":"2024-09-12T17:21:49.000Z","dependencies_parsed_at":"2023-12-31T06:33:46.030Z","dependency_job_id":"8c3af7da-49d8-45ef-a168-12345c7f3ce0","html_url":"https://github.com/speed2exe/myzql","commit_stats":{"total_commits":268,"total_committers":2,"mean_commits":134.0,"dds":0.03358208955223885,"last_synced_commit":"575686d1ea762f3054a2e2c27125b79af4359353"},"previous_names":["speed2exe/myzql"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/speed2exe%2Fmyzql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/speed2exe%2Fmyzql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/speed2exe%2Fmyzql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/speed2exe%2Fmyzql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/speed2exe","download_url":"https://codeload.github.com/speed2exe/myzql/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229333194,"owners_count":18056670,"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":["driver","mariadb","mysql","sql","zig","zig-library","zig-package"],"created_at":"2024-08-01T07:00:31.464Z","updated_at":"2025-04-19T11:55:51.949Z","avatar_url":"https://github.com/speed2exe.png","language":"Zig","funding_links":[],"categories":["Connectors","Zig","Data \u0026 Science"],"sub_categories":["Database"],"readme":"# MyZql\n- MySQL and MariaDB driver in native zig\n\n## Status\n- Beta\n\n## Version Compatibility\n| MyZQL       | Zig                       |\n|-------------|---------------------------|\n| 0.0.9.1     | 0.12.0                    |\n| 0.13.2      | 0.13.0                    |\n| 0.14.0      | 0.14.0                    |\n| main        | 0.14.0                    |\n\n## Features\n- Native Zig code, no external dependencies\n- TCP protocol\n- Prepared Statement\n- Structs from query result\n- Data insertion\n- MySQL DateTime and Time support\n\n## Requirements\n- MySQL/MariaDB 5.7.5 and up\n\n## TODOs\n- Config from URL\n- Connection Pooling\n- TLS support\n\n## Add as dependency to your Zig project\n- `build.zig`\n```zig\n    //...\n    const myzql_dep = b.dependency(\"myzql\", .{});\n    const myzql = myzql_dep.module(\"myzql\");\n    exe.addModule(\"myzql\", myzql);\n    //...\n```\n\n- `build.zig.zon`\n```zon\n    // ...\n    .dependencies = .{\n      .myzql = .{\n        // choose a tag according to \"Version Compatibility\" table\n        .url = \"https://github.com/speed2exe/myzql/archive/refs/tags/0.13.2.tar.gz\",\n        .hash = \"1220582ea45580eec6b16aa93d2a9404467db8bc1d911806d367513aa40f3817f84c\",\n      }\n    },\n    // ...\n```\n\n## Usage\n- Project integration example: [Usage](https://github.com/speed2exe/myzql-example)\n\n### Connection\n```zig\nconst myzql = @import(\"myzql\");\nconst Conn = myzql.conn.Conn;\n\npub fn main() !void {\n    // Setting up client\n    var client = try Conn.init(\n        allocator,\n        \u0026.{\n            .username = \"some-user\",   // default: \"root\"\n            .password = \"password123\", // default: \"\"\n            .database = \"customers\",   // default: \"\"\n\n            // Current default value.\n            // Use std.net.getAddressList if you need to look up ip based on hostname\n            .address =  std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 3306),\n            // ...\n        },\n    );\n    defer client.deinit();\n\n    // Connection and Authentication\n    try client.ping();\n}\n```\n\n## Querying\n```zig\n\nconst OkPacket = protocol.generic_response.OkPacket;\n\npub fn main() !void {\n    // ...\n    // You can do a text query (text protocol) by using `query` method on `Conn`\n    const result = try c.query(\"CREATE DATABASE testdb\");\n\n    // Query results can have a few variant:\n    // - ok:   OkPacket     =\u003e query is ok\n    // - err:  ErrorPacket  =\u003e error occurred\n    // In this example, res will either be `ok` or `err`.\n    // We are using the convenient method `expect` for simplified error handling.\n    // If the result variant does not match the kind of result you have specified,\n    // a message will be printed and you will get an error instead.\n    const ok: OkPacket = try result.expect(.ok);\n\n    // Alternatively, you can also handle results manually for more control.\n    // Here, we do a switch statement to handle all possible variant or results.\n    switch (result.value) {\n        .ok =\u003e |ok| {},\n\n        // `asError` is also another convenient method to print message and return as zig error.\n        // You may also choose to inspect individual fields for more control.\n        .err =\u003e |err| return err.asError(),\n    }\n}\n```\n\n## Querying returning rows (Text Results)\n- If you want to have query results to be represented by custom created structs,\nthis is not the section, scroll down to \"Executing prepared statements returning results\" instead.\n```zig\nconst myzql = @import(\"myzql\");\nconst QueryResult = myzql.result.QueryResult;\nconst ResultSet = myzql.result.ResultSet;\nconst ResultRow = myzql.result.ResultRow;\nconst TextResultRow = myzql.result.TextResultData;\nconst ResultSetIter = myzql.result.ResultSetIter;\nconst TableTexts = myzql.result.TableTexts;\nconst TextElemIter = myzql.result.TextElemIter;\n\npub fn main() !void {\n    const result = try c.queryRows(\"SELECT * FROM customers.purchases\");\n\n    // This is a query that returns rows, you have to collect the result.\n    // you can use `expect(.rows)` to try interpret query result as ResultSet(TextResultRow)\n    const rows: ResultSet(TextResultRow) = try query_res.expect(.rows);\n\n    // Allocation free interators\n    const rows_iter: ResultRowIter(TextResultRow) = rows.iter();\n    { // Option 1: Iterate through every row and elem\n        while (try rows_iter.next()) |row| { // ResultRow(TextResultRow)\n            var elems_iter: TextElemIter = row.iter();\n            while (elems_iter.next()) |elem| { // ?[] const u8\n                std.debug.print(\"{?s} \", .{elem});\n            }\n        }\n    }\n    { // Option 2: Iterating over rows, collecting elements into []const ?[]const u8\n        while (try rows_iter.next()) |row| {\n            const text_elems: TextElems = try row.textElems(allocator);\n            defer text_elems.deinit(allocator); // elems are valid until deinit is called\n            const elems: []const ?[]const u8 = text_elems.elems;\n            std.debug.print(\"elems: {any}\\n\", .{elems});\n        }\n    }\n\n    // You can also use `collectTexts` method to collect all rows.\n    // Under the hood, it does network call and allocations, until EOF or error\n    // Results are valid until `deinit` is called on TableTexts.\n    const rows: ResultSet(TextResultRow) = try query_res.expect(.rows);\n    const table = try rows.tableTexts(allocator);\n    defer table.deinit(allocator); // table is valid until deinit is called\n    std.debug.print(\"table: {any}\\n\", .{table.table});\n}\n\n```\n\n### Data Insertion\n- Let's assume that you have a table of this structure:\n```sql\nCREATE TABLE test.person (\n    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n    name VARCHAR(255),\n    age INT\n)\n```\n\n```zig\nconst myzql = @import(\"myzql\");\nconst QueryResult = myzql.result.QueryResult;\nconst PreparedStatement = myzql.result.PreparedStatement;\nconst OkPacket = myzql.protocol.generic_response.OkPacket;\n\npub fn main() void {\n    // In order to do a insertion, you would first need to do a prepared statement.\n    // Allocation is required as we need to store metadata of parameters and return type\n    const prep_res = try c.prepare(allocator, \"INSERT INTO test.person (name, age) VALUES (?, ?)\");\n    defer prep_res.deinit(allocator);\n    const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n\n    // Data to be inserted\n    const params = .{\n        .{ \"John\", 42 },\n        .{ \"Sam\", 24 },\n    };\n    inline for (params) |param| {\n        const exe_res = try c.execute(\u0026prep_stmt, param);\n        const ok: OkPacket = try exe_res.expect(.ok); // expecting ok here because there's no rows returned\n        const last_insert_id: u64 = ok.last_insert_id;\n        std.debug.print(\"last_insert_id: {any}\\n\", .{last_insert_id});\n    }\n\n    // Currently only tuples are supported as an argument for insertion.\n    // There are plans to include named structs in the future.\n}\n```\n\n### Executing prepared statements returning results as structs\n```zig\nconst ResultSetIter = myzql.result.ResultSetIter;\nconst QueryResult = myzql.result.QueryResult;\nconst BinaryResultRow = myzql.result.BinaryResultRow;\nconst TableStructs = myzql.result.TableStructs;\nconst ResultSet = myzql.result.ResultSet;\n\nfn main() !void {\n    const prep_res = try c.prepare(allocator, \"SELECT name, age FROM test.person\");\n    defer prep_res.deinit(allocator);\n    const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n\n    // This is the struct that represents the columns of a single row.\n    const Person = struct {\n        name: []const u8,\n        age: u8,\n    };\n\n    // Execute query and get an iterator from results\n    const res: QueryResult(BinaryResultRow) = try c.executeRows(\u0026prep_stmt, .{});\n    const rows: ResultSet(BinaryResultRow) = try res.expect(.rows);\n    const iter: ResultSetIter(BinaryResultRow) = rows.iter();\n\n    { // Iterating over rows, scanning into struct or creating struct\n        const query_res = try c.executeRows(\u0026prep_stmt, .{}); // no parameters because there's no ? in the query\n        const rows: ResultSet(BinaryResultRow) = try query_res.expect(.rows);\n        const rows_iter = rows.iter();\n        while (try rows_iter.next()) |row| {\n            { // Option 1: scanning into preallocated person\n                var person: Person = undefined;\n                try row.scan(\u0026person);\n                person.greet();\n                // Important: if any field is a string, it will be valid until the next row is scanned\n                // or next query. If your rows return have strings and you want to keep the data longer,\n                // use the method below instead.\n            }\n            { // Option 2: passing in allocator to create person\n                const person_ptr = try row.structCreate(Person, allocator);\n\n                // Important: please use BinaryResultRow.structDestroy\n                // to destroy the struct created by BinaryResultRow.structCreate\n                // if your struct contains strings.\n                // person is valid until BinaryResultRow.structDestroy is called.\n                defer BinaryResultRow.structDestroy(person_ptr, allocator);\n                person_ptr.greet();\n            }\n        }\n    }\n\n    { // collect all rows into a table ([]const Person)\n        const query_res = try c.executeRows(\u0026prep_stmt, .{}); // no parameters because there's no ? in the query\n        const rows: ResultSet(BinaryResultRow) = try query_res.expect(.rows);\n        const rows_iter = rows.iter();\n        const person_structs = try rows_iter.tableStructs(Person, allocator);\n        defer person_structs.deinit(allocator); // data is valid until deinit is called\n        std.debug.print(\"person_structs: {any}\\n\", .{person_structs.struct_list.items});\n    }\n}\n```\n\n### Temporal Types Support (DateTime, Time)\n- Example of using DateTime and Time MySQL column types.\n- Let's assume you already got this table set up:\n```sql\nCREATE TABLE test.temporal_types_example (\n    event_time DATETIME(6) NOT NULL,\n    duration TIME(6) NOT NULL\n)\n```\n\n\n```zig\n\nconst DateTime = myzql.temporal.DateTime;\nconst Duration = myzql.temporal.Duration;\n\nfn main() !void {\n    { // Insert\n        const prep_res = try c.prepare(allocator, \"INSERT INTO test.temporal_types_example VALUES (?, ?)\");\n        defer prep_res.deinit(allocator);\n        const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n\n        const my_time: DateTime = .{\n            .year = 2023,\n            .month = 11,\n            .day = 30,\n            .hour = 6,\n            .minute = 50,\n            .second = 58,\n            .microsecond = 123456,\n        };\n        const my_duration: Duration = .{\n            .days = 1,\n            .hours = 23,\n            .minutes = 59,\n            .seconds = 59,\n            .microseconds = 123456,\n        };\n        const params = .{.{ my_time, my_duration }};\n        inline for (params) |param| {\n            const exe_res = try c.execute(\u0026prep_stmt, param);\n            _ = try exe_res.expect(.ok);\n        }\n    }\n\n    { // Select\n        const DateTimeDuration = struct {\n            event_time: DateTime,\n            duration: Duration,\n        };\n        const prep_res = try c.prepare(allocator, \"SELECT * FROM test.temporal_types_example\");\n        defer prep_res.deinit(allocator);\n        const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n        const res = try c.executeRows(\u0026prep_stmt, .{});\n        const rows: ResultSet(BinaryResultRow) = try res.expect(.rows);\n        const rows_iter = rows.iter();\n\n        const structs = try rows_iter.tableStructs(DateTimeDuration, allocator);\n        defer structs.deinit(allocator);\n        std.debug.print(\"structs: {any}\\n\", .{structs.struct_list.items}); // structs.rows: []const DateTimeDuration\n        // Do something with structs\n    }\n}\n```\n\n### Arrays Support\n- Assume that you have the SQL table:\n```sql\nCREATE TABLE test.array_types_example (\n    name VARCHAR(16) NOT NULL,\n    mac_addr BINARY(6)\n)\n```\n\n```zig\nfn main() !void {\n    { // Insert\n        const prep_res = try c.prepare(allocator, \"INSERT INTO test.array_types_example VALUES (?, ?)\");\n        defer prep_res.deinit(allocator);\n        const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n\n        const params = .{\n            .{ \"John\", \u0026[_]u8 { 0xFE } ** 6 },\n            .{ \"Alice\", null }\n        };\n        inline for (params) |param| {\n            const exe_res = try c.execute(\u0026prep_stmt, param);\n            _ = try exe_res.expect(.ok);\n        }\n    }\n\n    { // Select\n        const Client = struct {\n            name: [16:1]u8,\n            mac_addr: ?[6]u8,\n        };\n        const prep_res = try c.prepare(allocator, \"SELECT * FROM test.array_types_example\");\n        defer prep_res.deinit(allocator);\n        const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n        const res = try c.executeRows(\u0026prep_stmt, .{});\n        const rows: ResultSet(BinaryResultRow) = try res.expect(.rows);\n        const rows_iter = rows.iter();\n\n        const structs = try rows_iter.tableStructs(DateTimeDuration, allocator);\n        defer structs.deinit(allocator);\n        std.debug.print(\"structs: {any}\\n\", .{structs.struct_list.items}); // structs.rows: []const Client\n        // Do something with structs\n    }\n}\n```\n- Arrays will be initialized by their sentinel value. In this example, the value of the `name` field corresponding to `John`'s row will be `[16:1]u8 { 'J', 'o', 'h', 'n', 1, 1, 1, ... }`\n- If the array doesn't have a sentinel value, it will be zero-initialized.\n- Insufficiently sized arrays will silently truncate excess data\n\n### `BoundedArray` Support\n- Assume that you have the SQL table:\n```sql\nCREATE TABLE test.bounded_array_types_example (\n    name VARCHAR(16) NOT NULL,\n    address VARCHAR(128)\n)\n```\n\n```zig\nconst std = @import(\"std\");\n\nfn main() !void {\n    { // Insert\n        const prep_res = try c.prepare(allocator, \"INSERT INTO test.bounded_array_types_example VALUES (?, ?)\");\n        defer prep_res.deinit(allocator);\n        const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n\n        const params = .{\n            .{ \"John\", \"5 Rosewood Avenue Maryville, TN 37803\"},\n            .{ \"Alice\", null }\n        };\n        inline for (params) |param| {\n            const exe_res = try c.execute(\u0026prep_stmt, param);\n            _ = try exe_res.expect(.ok);\n        }\n    }\n\n    { // Select\n        const Client = struct {\n            name: std.BoundedArray(u8, 16),\n            address: ?std.BoundedArray(u8, 128),\n        };\n        const prep_res = try c.prepare(allocator, \"SELECT * FROM test.bounded_array_types_example\");\n        defer prep_res.deinit(allocator);\n        const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);\n        const res = try c.executeRows(\u0026prep_stmt, .{});\n        const rows: ResultSet(BinaryResultRow) = try res.expect(.rows);\n        const rows_iter = rows.iter();\n\n        const structs = try rows_iter.tableStructs(Client, allocator);\n        defer structs.deinit(allocator);\n        std.debug.print(\"structs: {any}\\n\", .{structs.struct_list.items}); // structs.rows: []const Client\n        // Do something with structs\n    }\n}\n```\n\n- Insufficiently sized `BoundedArray`s will silently truncate excess data\n\n## Unit Tests\n- `zig test src/myzql.zig`\n\n## Integration Tests\n- Start up mysql/mariadb in docker:\n```bash\n# MySQL\ndocker run --name some-mysql --env MYSQL_ROOT_PASSWORD=password -p 3306:3306 -d mysql\n```bash\n# MariaDB\ndocker run --name some-mariadb --env MARIADB_ROOT_PASSWORD=password -p 3306:3306 -d mariadb\n```\n- Run all the test: In root directory of project:\n```bash\nzig build -Dtest-filer='...' integration_test\n```\n\n## Philosophy\n### Correctness\nFocused on correct representation of server client protocol.\n### Low-level and High-level APIs\nLow-level apis should contain all functionality you need.\nHigh-level apis are built on top of low-level ones for convenience and developer ergonomics.\n\n### Binary Column Types support\n- MySQL Colums Types to Zig Values\n```\n- Null -\u003e ?T\n- Int -\u003e u64, u32, u16, u8\n- Float -\u003e f32, f64\n- String -\u003e []u8, []const u8, enum\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspeed2exe%2Fmyzql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspeed2exe%2Fmyzql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspeed2exe%2Fmyzql/lists"}