speed2exe/myzql
speed2exe/myzql has 82 GitHub stars and 10 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-11 15:05 UTC. Last push 2026-11-03.
Comment ces chiffres sont mesurés
MySQL and MariaDB driver in native Zig
What you can do
- Native Zig code, no external dependencies
- TCP and Unix socket connections
- TLS for TCP connections (server certificate verification, custom CA)
- Prepared Statement
Progression mesurée des étoiles
- in 24h
- 0
- in 7 days
- Mise à jour retardée
- in 15 days
- Mise à jour retardée
- in 1 month
- Mise à jour retardée
Variations nettes sur des périodes qui se chevauchent, pas des totaux quotidiens. Les mesures absentes restent vides. Les étoiles indiquent l’intérêt, pas la qualité.
README.md
OriginalMyZql
- MySQL and MariaDB driver in native zig
Status
- Beta
Version Compatibility
| MyZQL | Zig |
|---|---|
| 0.0.9.1 | 0.12.0 |
| 0.13.2 | 0.13.0 |
| 0.14.0 | 0.14.0 |
| 0.15.1 | 0.15.1 |
| 0.16.1 | 0.16.0 |
| 0.17.0 | 0.17.0 |
| main | 0.17.0 |
Features
- Native Zig code, no external dependencies
- TCP and Unix socket connections
- TLS for TCP connections (server certificate verification, custom CA)
- Prepared Statement
- Structs from query result
- Data insertion
- MySQL DateTime and Time support
- Thread-safe connection pool
- Custom log handler per config, every message bound to the config that emitted it
Requirements
- MySQL/MariaDB 5.7.5 and up
- For TLS connections only: a Zig standard library whose TLS client handles the
certificate_requestmessage. MySQL and MariaDB always ask for a client certificate (with an empty list of acceptable CA names), which the standard library of Zig 0.17.0 and the current master answer witherror.TlsUnexpectedMessage. Until a fix is released upstream, applycontrib/zig-std-tls-client-certificate-request.patchto the source tree of your Zig installation and rebuild:
patch -p1 -d /path/to/zig --input path/to/myzql/contrib/zig-std-tls-client-certificate-request.patch
- Connections without TLS, and every other feature, work with an unpatched Zig.
Add as dependency to your Zig project
Fetch dependency
zig fetch --save git+https://github.com/speed2exe/myzql#0.16.1
or
build.zig.zon
// ...
.dependencies = .{
.myzql = .{
// choose a tag according to "Version Compatibility" table
.url = "https://github.com/speed2exe/myzql/archive/refs/tags/0.16.1.tar.gz",
.hash = "1220582ea45580eec6b16aa93d2a9404467db8bc1d911806d367513aa40f3817f84c",
}
},
// ...
Import in your project
build.zig
//...
const myzql_dep = b.dependency("myzql", .{});
const myzql = myzql_dep.module("myzql");
exe.root_module.addImport("myzql", myzql);
//...
Usage
- Project integration example: Usage
Connection
const std = @import("std");
const myzql = @import("myzql");
const Conn = myzql.conn.Conn;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
// TCP connection (default)
var client = try Conn.init(
allocator,
io,
&.{
.username = "some-user", // default: "root"
.password = "password123", // default: ""
.database = "customers", // default: ""
// Default: 127.0.0.1:3306
.address = .{ .ip = std.Io.net.IpAddress.parseLiteral("127.0.0.1:3306") catch unreachable },
},
);
defer client.deinit(allocator, io);
try client.ping(io);
}
Unix Socket Connection
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
var client = try Conn.init(
allocator,
io,
&.{
.password = "password123",
.address = .{ .unix = try std.Io.net.UnixAddress.init("/var/run/mysqld/mysqld.sock") },
},
);
defer client.deinit(allocator, io);
try client.ping(io);
}
TLS Connection
- TLS encrypts TCP connections. Enable it with
sslon the config, or with thessl=true(tls=true) URL parameter, see Connection from URL. - The standard library of Zig 0.17.0 and of the current master cannot answer
the
certificate_requestmessage that MySQL and MariaDB send during the TLS handshake, so TLS needs the patch of Requirements.
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
var client = try Conn.init(
allocator,
io,
&.{
.username = "some-user",
.password = "password123",
.database = "customers",
.address = .{ .host = .{ .name = try std.Io.net.HostName.init("db.example.com"), .port = 3306 } },
.ssl = true, // default: false
.ssl_verify = true, // default: true
.ssl_ca = "/etc/ssl/certs/db-ca.pem", // default: null (CA certificates of the system)
},
);
defer client.deinit(allocator, io);
try client.ping(io);
}
- The same connection as a URL:
var url_config = try Config.fromUrl(allocator, "mysql://some-user:[email protected]:3306/customers?ssl=true&sslCa=%2Fetc%2Fssl%2Fcerts%2Fdb-ca.pem");
- A connection is either encrypted from the start or not encrypted at all:
ssl=truenever falls back to cleartext, a server without TLS support fails the connection witherror.ServerDoesNotSupportTls. - With
ssl_verifyenabled (the default) the certificate must be signed by a trusted CA and be issued for the host name or IP address of the connection (the same name is sent as SNI).ssl_careplaces the CA certificates of the system with the certificates of a PEM file, which is what self-signed servers need. ssl_verify=falsestill encrypts the traffic, but the server is not authenticated, which makes the connection vulnerable to man-in-the-middle attacks. It is meant for servers whose certificate cannot be fixed, not for production.- Unix domain sockets are already a local channel, so
sslis ignored for them (a warning is logged) and the connection stays unencrypted. - Accounts with
REQUIRE SSLwork, and authentication works with every supported auth plugin: over a secure channelcaching_sha2_passwordandsha256_passwordsend the password instead of requesting the RSA public key of the server, andmysql_native_passwordworks unchanged. - Closing a connection sends the TLS
close_notifyalert before the socket is closed. - Connections of a pool are established and reused like plain ones; the pool
copies
ssl_cainto memory owned by the pool, so it does not need to outlivePool.init.
Connection from URL
Config.fromUrlparses a MySQL connection URL and returns aUrlConfig.UrlConfigowns the decoded strings that theConfigrefers to, so it must be kept alive for the whole lifetime of the connection.Pool.initcopies the config into pool-owned memory, so a pool created from aUrlConfigno longer depends on it.
const myzql = @import("myzql");
const Config = myzql.config.Config;
const Conn = myzql.conn.Conn;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
// mysql://[user[:password]@][host[:port]][/database][?parameter=value&...]
var url_config = try Config.fromUrl(allocator, "mysql://some-user:password123@localhost:3306/customers?charset=utf8mb4");
defer url_config.deinit(allocator);
var client = try Conn.init(allocator, io, &url_config.value);
defer client.deinit(allocator, io);
try client.ping(io);
}
mysql://andmariadb://are accepted.- Every component may be percent-encoded, e.g.
mysql://us%40r:p%40ss@localhost/db. - Host names such as
localhostare resolved when the connection is established, while IP addresses (including bracketed IPv6 literals like[::1]) are used directly. - Leave the host empty and add a
socketparameter to connect over a unix domain socket:
var url_config = try Config.fromUrl(allocator, "mysql://root:password123@/customers?socket=/var/run/mysqld/mysqld.sock");
- Supported parameters (keys are case-insensitive):
| Parameter | Config field | Value |
|---|---|---|
socket, unix_socket |
address |
unix domain socket path |
charset |
collation |
character set (e.g. utf8mb4) or collation (e.g. utf8mb4_bin) name |
clientFoundRows, client_found_rows |
client_found_rows |
true, false, 1 or 0 |
multiStatements, multi_statements |
multi_statements |
true, false, 1 or 0 |
ssl, tls |
ssl |
true, false, 1 or 0 |
sslVerify, ssl_verify |
ssl_verify |
true, false, 1 or 0 |
sslCa, ssl_ca |
ssl_ca |
path to a PEM file with CA certificates |
- A parameter with an empty value keeps the default, so
?charset=placeholders can be left in URL templates. - Unknown parameters and invalid values are rejected (
error.UnknownUrlParameter,error.InvalidUrlParameterValue,error.UnknownCharset) instead of being silently ignored. - TLS is enabled with
ssl=true(ortls=true), e.g.mysql://user:[email protected]/customers?ssl=true. Verification,sslCaand the other parameters are described in TLS Connection.
Querying
const OkPacket = myzql.protocol.generic_response.OkPacket;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
// ...
// You can do a text query (text protocol) by using `query` method on `Conn`
const result = try client.query(io, "CREATE DATABASE testdb");
// Query results can have a few variant:
// - ok: OkPacket => query is ok
// - err: ErrorPacket => error occurred
// In this example, result will either be `ok` or `err` (or `rows` if the query returns a result set).
// We are using the convenient method `expect` for simplified error handling.
// If the result variant does not match the kind of result you have specified,
// a message will be printed and you will get an error instead.
const ok: OkPacket = try result.expect(.ok);
// Alternatively, you can also handle results manually for more control.
// Here, we do a switch statement to handle all possible variant or results.
switch (result.value) {
.ok => |ok| {},
// `asError` is also another convenient method to print message and return as zig error.
// You may also choose to inspect individual fields for more control.
.err => |err| return err.asError(),
}
}
Querying returning rows (Text Results)
- If you want to have query results to be represented by custom created structs, this is not the section, scroll down to "Executing prepared statements returning results" instead.
- Text protocol rows are obtained by calling
queryand expecting the.rowsvariant.
const myzql = @import("myzql");
const ResultSet = myzql.result.ResultSet;
const TextResultRow = myzql.result.TextResultRow;
const TextElems = myzql.result.TextElems;
const TextElemIter = myzql.result.TextElemIter;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
const result = try client.query(io, "SELECT * FROM customers.purchases");
// This is a query that returns rows, you have to collect the result.
// you can use `expect(.rows)` to try interpret query result as ResultSet(TextResultRow)
const rows: ResultSet(TextResultRow) = try result.expect(.rows);
// Alternatively, if you only need the first row, you can use `rows.first(io)`.
// It returns `?T` (`null` if there are no rows) and consumes the row.
// Allocation-free iterator over rows.
// Note: all rows must be consumed (iterated to `null`) before issuing another query.
const rows_iter = rows.iter();
while (try rows_iter.next(io)) |row| { // TextResultRow
// Option 1: Iterate through every element in the row
var elems_iter: TextElemIter = row.iter();
while (elems_iter.next()) |elem| { // ?[]const u8
std.debug.print("{?s} ", .{elem});
}
// Option 2: Collect all elements in the row into a slice
const text_elems: TextElems = try row.textElems(allocator);
defer text_elems.deinit(allocator); // elems are valid until deinit is called
const elems: []const ?[]const u8 = text_elems.elems;
std.debug.print("elems: {any}\n", .{elems});
}
}
// You can also use `tableTexts` to collect all rows at once.
// Under the hood, it does network calls and allocations, until EOF or error.
// Results are valid until `deinit` is called on TableTexts.
const result = try client.query(io, "SELECT * FROM customers.purchases");
const rows: ResultSet(TextResultRow) = try result.expect(.rows);
const table = try rows.tableTexts(allocator, io);
defer table.deinit(allocator); // table is valid until deinit is called
std.debug.print("table: {any}\n", .{table.table});
Data Insertion
- Let's assume that you have a table of this structure:
CREATE TABLE test.person (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
age INT
)
const myzql = @import("myzql");
const QueryResult = myzql.result.QueryResult;
const PreparedStatement = myzql.result.PreparedStatement;
const OkPacket = myzql.protocol.generic_response.OkPacket;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
// In order to do a insertion, you would first need to do a prepared statement.
// Allocation is required as we need to store metadata of parameters and return type
const prep_res = try client.prepare(allocator, io, "INSERT INTO test.person (name, age) VALUES (?, ?)");
defer prep_res.deinit(allocator);
const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);
// Data to be inserted
const params = .{
.{ "John", 42 },
.{ "Sam", 24 },
};
inline for (params) |param| {
const exe_res = try client.execute(io, &prep_stmt, param);
const ok: OkPacket = try exe_res.expect(.ok); // expecting ok here because there's no rows returned
const last_insert_id: u64 = ok.last_insert_id;
std.debug.print("last_insert_id: {any}\n", .{last_insert_id});
}
// Currently only tuples are supported as an argument for insertion.
// There are plans to include named structs in the future.
}
Executing prepared statements returning results as structs
const QueryResultRows = myzql.result.QueryResultRows;
const BinaryResultRow = myzql.result.BinaryResultRow;
const ResultSet = myzql.result.ResultSet;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
const prep_res = try client.prepare(allocator, io, "SELECT name, age FROM test.person");
defer prep_res.deinit(allocator);
const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);
// This is the struct that represents the columns of a single row.
const Person = struct {
name: []const u8,
age: u8,
};
{ // Iterating over rows, scanning into struct or creating struct
const query_res = try client.execute(io, &prep_stmt, .{}); // no parameters because there's no ? in the query
const rows: ResultSet(BinaryResultRow) = try query_res.expect(.rows);
const rows_iter = rows.iter();
while (try rows_iter.next(io)) |row| {
{ // Option 1: scanning into preallocated person
var person: Person = undefined;
try row.scan(&person);
std.debug.print("{s} is {d} years old\n", .{ person.name, person.age });
// Important: if any field is a string, it will be valid until the next row is scanned
// or next query. If your rows return have strings and you want to keep the data longer,
// use the method below instead.
}
{ // Option 2: passing in allocator to create person
const person_ptr = try row.structCreate(Person, allocator);
// Important: please use BinaryResultRow.structDestroy
// to destroy the struct created by BinaryResultRow.structCreate
// if your struct contains strings.
// person is valid until BinaryResultRow.structDestroy is called.
defer BinaryResultRow.structDestroy(person_ptr, allocator);
std.debug.print("{s} is {d} years old\n", .{ person_ptr.name, person_ptr.age });
}
}
}
{ // collect all rows into a table ([]const Person)
const query_res = try client.execute(io, &prep_stmt, .{}); // no parameters because there's no ? in the query
const rows: ResultSet(BinaryResultRow) = try query_res.expect(.rows);
const rows_iter = rows.iter();
const person_structs = try rows_iter.tableStructs(Person, allocator, io);
defer person_structs.deinit(allocator); // data is valid until deinit is called
std.debug.print("person_structs: {any}\n", .{person_structs.struct_list.items});
}
}
Temporal Types Support (DateTime, Time)
- Example of using DateTime and Time MySQL column types.
- Let's assume you already got this table set up:
CREATE TABLE test.temporal_types_example (
event_time DATETIME(6) NOT NULL,
duration TIME(6) NOT NULL
)
const DateTime = myzql.temporal.DateTime;
const Duration = myzql.temporal.Duration;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
{ // Insert
const prep_res = try client.prepare(allocator, io, "INSERT INTO test.temporal_types_example VALUES (?, ?)");
defer prep_res.deinit(allocator);
const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);
const my_time: DateTime = .{
.year = 2023,
.month = 11,
.day = 30,
.hour = 6,
.minute = 50,
.second = 58,
.microsecond = 123456,
};
const my_duration: Duration = .{
.days = 1,
.hours = 23,
.minutes = 59,
.seconds = 59,
.microseconds = 123456,
};
const params = .{.{ my_time, my_duration }};
inline for (params) |param| {
const exe_res = try client.execute(io, &prep_stmt, param);
_ = try exe_res.expect(.ok);
}
}
{ // Select
const DateTimeDuration = struct {
event_time: DateTime,
duration: Duration,
};
const prep_res = try client.prepare(allocator, io, "SELECT * FROM test.temporal_types_example");
defer prep_res.deinit(allocator);
const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);
const res = try client.execute(io, &prep_stmt, .{});
const rows: ResultSet(BinaryResultRow) = try res.expect(.rows);
const rows_iter = rows.iter();
const structs = try rows_iter.tableStructs(DateTimeDuration, allocator, io);
defer structs.deinit(allocator);
std.debug.print("structs: {any}\n", .{structs.struct_list.items}); // structs.struct_list.items: []const DateTimeDuration
// Do something with structs
}
}
Arrays Support
- Assume that you have the SQL table:
CREATE TABLE test.array_types_example (
name VARCHAR(16) NOT NULL,
mac_addr BINARY(6)
)
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
{ // Insert
const prep_res = try client.prepare(allocator, io, "INSERT INTO test.array_types_example VALUES (?, ?)");
defer prep_res.deinit(allocator);
const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);
const params = .{
.{ "John", &[_]u8 { 0xFE } ** 6 },
.{ "Alice", null }
};
inline for (params) |param| {
const exe_res = try client.execute(io, &prep_stmt, param);
_ = try exe_res.expect(.ok);
}
}
{ // Select
const Client = struct {
name: [16:1]u8,
mac_addr: ?[6]u8,
};
const prep_res = try client.prepare(allocator, io, "SELECT * FROM test.array_types_example");
defer prep_res.deinit(allocator);
const prep_stmt: PreparedStatement = try prep_res.expect(.stmt);
const res = try client.execute(io, &prep_stmt, .{});
const rows: ResultSet(BinaryResultRow) = try res.expect(.rows);
const rows_iter = rows.iter();
const structs = try rows_iter.tableStructs(Client, allocator, io);
defer structs.deinit(allocator);
std.debug.print("structs: {any}\n", .{structs.struct_list.items}); // structs.struct_list.items: []const Client
// Do something with structs
}
}
- Arrays will be initialized by their sentinel value. In this example, the value of the
namefield corresponding toJohn's row will be[16:1]u8 { 'J', 'o', 'h', 'n', 1, 1, 1, ... } - If the array doesn't have a sentinel value, it will be zero-initialized.
- Insufficiently sized arrays will silently truncate excess data
Connection Pool
myzql.pool.Poolis a thread-safe connection pool.- Connections are created lazily up to
max_size(default: 10). acquirereturnserror.PoolExhaustedwhen all connections are in use.- Idle connections are health-checked (pinged) before being handed out.
const myzql = @import("myzql");
const Config = myzql.config.Config;
const Pool = myzql.pool.Pool;
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
const config: Config = .{ .database = "test" };
var pool = try Pool.init(allocator, &config, .{ .max_size = 10 });
defer pool.deinit(io);
const managed_conn = try pool.acquireManaged(io);
defer managed_conn.deinit(io);
const result = try managed_conn.query(io, "SELECT 1");
_ = try result.expect(.rows);
}
Logging
- The logger is a field of
Config, so logging is configured per config rather than per connection: every message the library emits on behalf of a connection is handed to the logger of the config that connection was created with, together with that config. - If you do not set a logger,
myzql.logger.default_loggeris used, which writes every message withstd.log, so nothing changes if you do not opt in. - Set
loggertonull(myzql.logger.silent) to turn logging off: nothing is emitted for connections created from that config, and messages are not even formatted. - Set
loggerto route the messages of a config somewhere else. The handler receives the config that emitted the message, so messages of configs sharing one sink can still be told apart.
const std = @import("std");
const myzql = @import("myzql");
const Conn = myzql.conn.Conn;
const Config = myzql.config.Config;
const LogSink = struct {
messages: std.atomic.Value(usize) = .init(0),
fn logFn(
context: ?*anyopaque,
config: ?*const Config,
level: std.log.Level,
message: []const u8,
) void {
const sink: *LogSink = @ptrCast(@alignCast(context.?));
_ = sink.messages.fetchAdd(1, .monotonic);
// `config` is the config the message was emitted for. Its fields (e.g.
// the database) tell the clients apart, so one sink serves many of them.
// It is null only for a message that has no config behind it, e.g. one
// emitted by a value that was built by hand.
const database = if (config) |c| c.database else "";
switch (level) {
.err => std.log.scoped(.myzql).err("[{s}] {s}", .{ database, message }),
.warn => std.log.scoped(.myzql).warn("[{s}] {s}", .{ database, message }),
.info => std.log.scoped(.myzql).info("[{s}] {s}", .{ database, message }),
.debug => std.log.scoped(.myzql).debug("[{s}] {s}", .{ database, message }),
}
}
};
pub fn main() !void {
var threaded: std.Io.Threaded = .init(std.heap.page_allocator, .{});
defer threaded.deinit();
const io: std.Io = threaded.io();
const allocator = std.heap.page_allocator;
var sink: LogSink = .{};
var config: Config = .{
.database = "customers",
.password = "password123",
.logger = .{ .context = &sink, .logFn = LogSink.logFn },
};
// messages of this connection go to `sink`
var client = try Conn.init(allocator, io, &config);
defer client.deinit(allocator, io);
// a copy of the config with logging turned off: connections created from it
// stay quiet
var quiet_config = config;
quiet_config.logger = myzql.logger.silent;
var quiet_client = try Conn.init(allocator, io, &quiet_config);
defer quiet_client.deinit(allocator, io);
}
- The handler type is
myzql.logger.Logger:logFn: fn (context: ?*anyopaque, config: ?*const Config, level: std.log.Level, message: []const u8) void.contextis passed back unchanged, which is how one sink serves many configs.configis the config the message belongs to, so its fields identify the client that emitted it. It isnullonly when a message has no config behind it, e.g. one emitted by a value that was built by hand.logFndefaults tomyzql.logger.defaultLogFn, sologger = .{}(aLoggerwith only acontext, or with no fields at all) still writes withstd.log. Leaving the field alone already gives youmyzql.logger.default_logger, i.e.Config{}.logger.?.logFn == myzql.logger.defaultLogFn.
nullis not "unset", it is the way to disable logging; leaving the field out is "unset" and keeps the default logger.messageis already formatted and has no trailing newline.- Messages are formatted in a fixed stack buffer: anything longer than
myzql.logger.max_message_len(1024 bytes) is truncated, never dropped. - The handler is called synchronously on the thread that emitted the message, so it must be thread-safe when connections are shared (see Connection Pool).
- The config must outlive the connections created from it, since a connection logs through the config it was created with.
- Connections created by a
Poollog through the pool's own copy of the config, soconfigis the copy owned by the pool, not the one passed toPool.init. - Only the messages of this library are routed,
std.logcalls of your own code are untouched; the default handler (std.log) keeps the previous behavior.
Unit Tests
zig build unit_test
Integration Tests
- Start up mysql/mariadb in docker:
# MySQL (TCP)
docker run --name some-mysql --env MYSQL_ROOT_PASSWORD=password -p 3306:3306 -d mysql
# MySQL (Unix socket)
mkdir -p /tmp/mysql
docker run --name some-mysql-unix --env MYSQL_ROOT_PASSWORD=password -v /tmp/mysql:/var/run/mysqld -d mysql
# MariaDB
docker run --name some-mariadb --env MARIADB_ROOT_PASSWORD=password -p 3306:3306 -d mariadb
- Run all the tests from the root directory of the project:
zig build integration_test --summary all
- Run with a filter:
zig build -Dtest-filter="..." integration_test
- Run unix socket tests:
zig build integration_test -Dunix-socket-path=/tmp/mysql/mysqld.sock -Dtest-filter="unix socket" --summary all
- Skip the heavy stress test (16M rows, useful for CI):
zig build integration_test -Dskip-stress=true --summary all
- Run the TLS tests. The script needs
dockerandopenssl: it generates a CA and the server certificates into a temporary directory, builds the image ofintegration_tests/tls/Dockerfileand starts three servers (MySQL with TLS, MySQL with a certificate for another host name and MariaDB without TLS):
./integration_tests/tls/docker-test.sh --summary all
The ports of the servers can be changed with MYZQL_TLS_PORT (default 3306),
MYZQL_WRONG_HOST_PORT (default 3308) and MYZQL_NOTLS_PORT (default
3307). With an unpatched Zig the TLS connections fail with
error.TlsUnexpectedMessage, see Requirements.
- To run the TLS tests against servers that are already running:
zig build integration_test -Dskip-stress=true \
-Dtls-ca-path=/path/to/ca.pem \
-Dtls-wrong-host-port=3308 \
-Dnotls-port=3307 \
-Dtest-filter="tls - " --summary all
-Dtls-ca-path is the CA that signed the certificate of the server on 3306,
-Dtls-wrong-host-port is a TLS server whose certificate is not valid for
127.0.0.1 and -Dnotls-port is a server without TLS support. Without
-Dtls-ca-path the TLS tests are skipped, so the servers are only needed for
the two commands above. The test of the unix socket behaviour additionally
needs -Dunix-socket-path.
Philosophy
Correctness
Focused on correct representation of server client protocol.
Low-level and High-level APIs
Low-level apis should contain all functionality you need. High-level apis are built on top of low-level ones for convenience and developer ergonomics.
Binary Column Types support
- MySQL Column Types to Zig Values
- Null -> ?T
- Int -> u64, u32, u16, u8, i64, i32, i16, i8
- Float -> f32, f64
- String -> []u8, []const u8, enum
- Date/Datetime/Timestamp -> DateTime
- Time -> Duration
- String/Bytes -> [N]u8, [N:0]u8 (sentinel-filled, zero-filled if no sentinel, or truncated if too small)
Explorer des projets similaires
Asabeneh/30-Days-Of-Python
Le défi de programmation 30 Days of Python est un guide pas à pas pour apprendre le langage de programmation Python en 30 jours. Ce défi peut prendre plus de 100 jours. Avancez à votre rythme. Ces vidéos peuvent aussi vous aider : https://www.youtube.com/channel/UC7PNRuno1rzYPb1xLa4yktw
78,560 étoilesgrafana/grafana
Plateforme ouverte et composable d’observabilité et de visualisation de données. Visualise les métriques, les journaux et les traces issus de plusieurs sources, comme Prometheus, Loki, Elasticsearch, InfluxDB, Postgres et bien d’autres.
77,830 étoilesredis/redis
Pour les développeurs qui créent des applications en temps réel pilotées par les données, Redis est le cache, le serveur de structures de données et le moteur de requêtes documentaires et vectorielles préféré, le plus rapide et le plus riche en fonctionnalités.
77,395 étoilesapache/superset
Apache Superset est une plateforme de visualisation et d'exploration de données.
75,473 étoiles