NAME

EV::MariaDB - Async MariaDB/MySQL client using libmariadb and EV

SYNOPSIS

use EV;
use EV::MariaDB;

my $m = EV::MariaDB->new(
    host       => 'localhost',
    user       => 'root',
    password   => '',
    database   => 'test',
    on_connect => sub { print "connected\n" },
    on_error   => sub { warn "error: $_[0]\n" },
);

# simple query (with column metadata)
$m->query("select * from users", sub {
    my ($rows, $err, $fields) = @_;
    if ($err) { warn $err; return }
    print join(", ", @$fields), "\n";  # column names
    for my $row (@$rows) {
        print join(", ", @$row), "\n";
    }
});

# prepared statement
$m->prepare("select * from users where id = ?", sub {
    my ($stmt, $perr) = @_;
    die $perr if $perr;
    $m->execute($stmt, [42], sub {
        my ($rows, $err) = @_;
        warn $err if $err;
        $m->close_stmt($stmt, sub { });
    });
});

# pipelined queries (all sent before reading results)
for my $id (1..100) {
    $m->q("select * from t where id = $id", sub {
        my ($rows, $err) = @_;
        # callbacks fire in order
    });
}

# streaming row-by-row (no full-result buffering)
$m->query_stream("select * from big_table", sub {
    my ($row, $err) = @_;
    if ($err)          { warn $err; return }
    if (!defined $row) { print "done\n"; return }   # EOF
    # process $row (arrayref)
});

EV::run;

DESCRIPTION

EV::MariaDB is an asynchronous MariaDB/MySQL client that integrates with the EV event loop. It uses the MariaDB Connector/C non-blocking API to perform all database operations without blocking the event loop.

Key features:

  • Fully asynchronous connect, query, and prepared statement execution

  • Query pipelining via mysql_send_query/mysql_read_query_result for high throughput

  • Prepared statements with automatic buffer management

  • Column metadata (field names) returned with query results

  • Streaming row-by-row results via query_stream

  • Async transaction control (commit, rollback, autocommit)

  • Connection utility operations (ping, reset, reset_connection, change_user, select_db, set_charset)

  • BLOB/TEXT streaming via send_long_data

  • Async graceful close via close_async

  • Multi-result set support for multi-statement queries

CONSTRUCTOR

new

my $m = EV::MariaDB->new(%args);

Creates a new EV::MariaDB object. If host or user is provided, connects immediately (asynchronously).

Connection parameters:

host => $hostname

Server hostname. Default: localhost. Note: localhost may connect via Unix socket; use 127.0.0.1 to force TCP.

port => $port

Server port. Default: 3306.

user => $username

Username for authentication.

password => $password

Password for authentication.

database => $dbname

Default database. Also accepts db as an alias.

unix_socket => $path

Path to Unix domain socket.

Callbacks:

on_connect => sub { }

Called once the connection is established. Receives no arguments.

on_error => sub { my ($message) = @_ }

Called on connection-level errors (handshake failure, lost connection, unexpected protocol state). Default: sub { die @_ }.

Exceptions thrown inside either handler are caught and re-emitted as warnings — they cannot escape into the event loop.

Connection options:

connect_timeout => $seconds
read_timeout => $seconds
write_timeout => $seconds
compress => 1

Enable protocol compression.

multi_statements => 1

Allow multiple SQL statements per query string. Note: only the first statement's result set is returned to the callback; secondary result sets are consumed and discarded. Errors in secondary statements are delivered via the on_error handler.

found_rows => 1

Set the CLIENT_FOUND_ROWS flag. Makes UPDATE return the number of matched rows instead of changed rows. Useful for upsert patterns where you need to know if a row existed regardless of whether it was modified.

charset => $name

Character set name (e.g., utf8mb4). Controls both result encoding and how string parameters are interpreted by the server. To round-trip Perl Unicode strings, set this to utf8 or utf8mb4 — see "UNICODE".

init_command => $sql

SQL statement executed automatically after connecting.

ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher, ssl_verify_server_cert

SSL/TLS connection options. The first five take a string (path or cipher list); ssl_verify_server_cert takes a boolean. See MYSQL_OPT_SSL_* options for semantics.

utf8 => 1

When enabled, result strings from columns with a UTF-8 charset are automatically flagged with Perl's internal UTF-8 flag (SvUTF8_on). Applies to text queries, prepared statements, and streaming results. Without this option, all result values are returned as raw byte strings (matching DBD::mysql's default).

Column names in $fields are UTF-8-flagged when the connection charset is utf8 or utf8mb4, regardless of this option.

Requires the connection charset to be utf8 or utf8mb4 for correct behaviour. See "UNICODE".

Event loop:

loop => $ev_loop

EV loop to use. Default: EV::default_loop.

METHODS

All asynchronous methods take a callback as the last argument. The callback convention is ($result, $error): on success $error is undef; on failure $result is undef and $error contains the error message.

Methods divide into two scheduling classes:

Queueable

query can be called at any time the object is alive — before connect completes, while a utility op is running, or while other queries are already in flight. Calls are pipelined and their callbacks fire in FIFO order.

Exclusive

Every other async method (prepare, execute, close_stmt, stmt_reset, ping, select_db, change_user, reset_connection, set_charset, commit, rollback, autocommit, query_stream, close_async, send_long_data) requires the connection to be idle. It dies with "cannot start operation while pipeline results are pending" if any queued query has not yet delivered its result, or with "another operation is in progress" if another exclusive op is running. Schedule these from inside the last queued query's callback, or after a previous exclusive op completes.

connect

$m->connect($host, $user, $password, $database, $port, $unix_socket);

Connects to the server. Called automatically by new when host or user is provided; use this directly for deferred connection:

my $m = EV::MariaDB->new(
    on_connect => sub { ... },
    on_error   => sub { ... },
);
$m->connect('localhost', 'root', '', 'test', 3306);

$port defaults to 3306. $password, $database, and $unix_socket may be empty strings or undef when not needed. Dies with "already connected" or "connection already in progress" if invoked twice on the same object.

query

$m->query($sql, sub { my ($result, $err, $fields) = @_ });

Executes a SQL query. The callback receives:

  • For SELECT: ($rows, undef, $fields), where $rows is an arrayref of row arrayrefs and $fields is an arrayref of column name strings.

  • For DML (insert/update/delete): ($affected_rows, undef).

  • On error: (undef, $error_message).

Queries are pipelined (see "PIPELINING"): consecutive query calls are dispatched as a batch and their callbacks fire in FIFO order. Safe to call before connect completes or while an exclusive op (ping, select_db, ...) is in flight — the query is buffered until the connection is idle. Dies with "not connected" if no connection exists (never connected, or already closed via finish).

By default, result strings are returned as raw bytes. Set utf8 => 1 in the constructor to flag UTF-8 columns automatically; otherwise decode with "decode_utf8" in Encode. See "UNICODE".

prepare

$m->prepare($sql, sub { my ($stmt, $err) = @_ });

Prepares a server-side statement. The callback receives ($stmt, undef) on success or (undef, $error) on failure. Pass the opaque $stmt handle to execute, bind_params, send_long_data, stmt_reset, and close_stmt.

A prepared statement is invalidated by reset, reset_connection, change_user, and finish. Re-prepare after any of these.

execute

$m->execute($stmt, \@params, sub { my ($result, $err, $fields) = @_ });

Executes a prepared statement with the given parameters. Parameter types are detected from the SV: integers bind as MYSQL_TYPE_LONGLONG (BIGINT) with the unsigned flag tracking SvUOK, floats as MYSQL_TYPE_DOUBLE, everything else as MYSQL_TYPE_STRING. Pass undef for NULL. The callback receives results in the same shape as "query".

Pass undef instead of \@params to skip parameter binding and re-use parameters set by a prior bind_params/send_long_data.

close_stmt

$m->close_stmt($stmt, sub { my ($ok, $err) = @_ });

Closes a prepared statement, freeing server and client resources (including bound parameter buffers). Should be called when the handle is no longer needed, to free server-side state promptly; otherwise cleanup happens at object destruction.

Already-invalidated handles (after reset/reset_connection/ change_user) are accepted: the callback fires synchronously with (1, undef) and the wrapper is freed.

stmt_reset

$m->stmt_reset($stmt, sub { my ($ok, $err) = @_ });

Resets a prepared statement (clears errors, unbinds parameters) without closing it. Croaks "statement handle is no longer valid (connection was reset)" on a handle invalidated by reset/reset_connection/change_user.

ping

$m->ping(sub { my ($ok, $err) = @_ });

Checks if the connection is alive.

select_db

$m->select_db($dbname, sub { my ($ok, $err) = @_ });

Changes the default database. The new name is cached so a subsequent reset reconnects to it; the cache is rolled back if the operation fails.

change_user

$m->change_user($user, $password, $db_or_undef, sub { my ($ok, $err) = @_ });

Changes the authenticated user and optionally the database. Pass undef for $db to keep the current database. The new credentials are cached for reset; the cache is rolled back if the change fails.

Note: The server discards all prepared statements as part of this operation — see "reset_connection" for details.

reset_connection

$m->reset_connection(sub { my ($ok, $err) = @_ });

Resets session state (variables, temporary tables, etc.) without reconnecting. Equivalent to COM_RESET_CONNECTION.

Note: The server discards all prepared statements as part of this operation. Every statement handle held by Perl code is automatically marked closed; subsequent execute/stmt_reset calls on those handles croak "statement handle is no longer valid (connection was reset)". The same applies to change_user. Re-prepare any statements you need after the operation completes.

set_charset

$m->set_charset($charset, sub { my ($ok, $err) = @_ });

Changes the connection character set asynchronously (e.g., utf8mb4). The new charset is cached for reset; the cache is rolled back if the change fails.

commit

$m->commit(sub { my ($ok, $err) = @_ });

Commits the current transaction.

rollback

$m->rollback(sub { my ($ok, $err) = @_ });

Rolls back the current transaction.

autocommit

$m->autocommit($mode, sub { my ($ok, $err) = @_ });

Enables or disables autocommit mode. $mode is interpreted as a boolean: any truthy value enables, any falsy value disables.

query_stream

$m->query_stream($sql, sub {
    my ($row, $err) = @_;
    if ($err) { warn $err; return }
    if (!defined $row) { print "done\n"; return }
    # process $row (arrayref)
});

Executes a SELECT query and streams results row-by-row using mysql_use_result/mysql_fetch_row. The callback is invoked:

  • once per row with ($row), where $row is an arrayref

  • once at EOF with (undef)

  • on error with (undef, $error_message)

Unlike query, rows are not buffered — suitable for very large result sets. No other queries can be queued while streaming is active.

close_async

$m->close_async(sub { my ($ok, $err) = @_ });

Gracefully closes the connection asynchronously (COM_QUIT without blocking the event loop). is_connected returns false once the callback has fired. Use finish for an immediate synchronous close.

send_long_data

$m->send_long_data($stmt, $param_idx, $data, sub { my ($ok, $err) = @_ });

Sends long parameter data (BLOB/TEXT) for a prepared statement. $param_idx is zero-based. May be called multiple times for the same parameter to stream data in chunks. Must be preceded by bind_params and followed by execute with undef for params:

$m->prepare("insert into t values (?, ?)", sub {
    my ($stmt) = @_;
    $m->bind_params($stmt, [1, ""]);   # bind all params first
    $m->send_long_data($stmt, 1, $blob_chunk, sub {
        $m->execute($stmt, undef, sub { # undef = keep bound params
            ...
        });
    });
});

bind_params

$m->bind_params($stmt, \@params);

Synchronously binds parameters to a prepared statement without executing it. Required before send_long_data. Types are detected the same way as in execute.

Dies with "another operation is in progress" or "cannot bind while pipeline results are pending" when invoked on a busy connection.

reset

$m->reset;

Disconnects and reconnects using the most recent credentials (as updated by change_user/select_db/set_charset). Cancels all pending operations and invalidates every prepared statement handle. Dies with "no previous connection to reset" if connect has never been called. Aliased as reconnect.

finish

$m->finish;

Closes the connection synchronously and cancels all pending operations (their callbacks fire with an error). Aliased as disconnect. Use close_async to close without blocking.

escape

my $escaped = $m->escape($string);

Escapes a string for safe interpolation into SQL, respecting the connection's character set. Warns if the input has Perl's UTF-8 flag set but the connection charset is not utf8/utf8mb4. Synchronous; dies with "not connected" if disconnected, or "connection is closing" while close_async is in flight.

skip_pending

$m->skip_pending;

Cancels every pending, queued, and in-flight operation, invoking their callbacks with (undef, "skipped"). If sent queries are still awaiting results (or an exclusive op is in flight), the underlying connection is also closed — call reset afterwards to reconnect. Queries that were merely queued are cancelled without disturbing the connection.

on_connect

$m->on_connect(sub { ... });   # set
my $cb = $m->on_connect;       # get
$m->on_connect(undef);         # clear

Accessor for the connect handler. With a CODE ref, replaces the current handler. With undef (or any non-CODE value), clears it. With no argument, returns the current handler (or undef if unset). The handler is fired again after every successful reset/reconnect.

on_error

$m->on_error(sub { my ($msg) = @_ });   # set
my $cb = $m->on_error;                  # get
$m->on_error(undef);                    # clear

Accessor for the error handler. Same get/set/clear semantics as on_connect. After clearing, connection-level errors are silently dropped.

ACCESSORS

All accessors are synchronous and safe to call at any time; those that require a live connection return undef (for SV returns) or 0/-1 (for numeric returns) when disconnected.

is_connected

True if a connection is established (and not currently in handshake).

error_message

Last error message, or undef when there is none. Aliased as errstr.

error_number

Last error number, or 0 when there is none. Aliased as errno.

sqlstate

SQLSTATE code (5-character string) for the last error. "00000" when there is no error.

insert_id

AUTO_INCREMENT value generated by the last insert.

affected_rows

Affected rows from the last DML operation. Returns undef on error or when disconnected. With found_rows => 1, UPDATE returns matched rows instead of changed rows.

warning_count

Number of warnings from the last query.

info

Additional info string from the last query (e.g., rows matched for UPDATE), or undef.

server_version

Server version as a packed integer MAJOR * 10000 + MINOR * 100 + PATCH (e.g., 110206 for 11.2.6).

server_info

Server version string.

thread_id

Server-side connection (thread) id.

host_info

String describing the connection type and host.

character_set_name

Current connection character set.

socket

File descriptor of the connection socket, or -1 when disconnected.

pending_count

Number of operations queued or in flight.

CLASS METHODS

lib_version
EV::MariaDB->lib_version;

Client library version as an integer.

lib_info
EV::MariaDB->lib_info;

Client library version string.

ALIASES

q          -> query
prep       -> prepare
reconnect  -> reset
disconnect -> finish
errstr     -> error_message
errno      -> error_number

PIPELINING

When multiple queries are submitted before the event loop processes I/O, EV::MariaDB pipelines them: queries are dispatched to the server in a single batch, then results are read back in order. This eliminates per-query round-trip latency and can yield 2–3× higher throughput than sequential execution.

# all 100 queries are pipelined
for (1..100) {
    $m->q("select $_", sub { ... });
}

Up to 64 queries are kept in flight simultaneously; further queries queue locally and are dispatched as earlier results drain.

Only query (and its alias q) participates in pipelining. Prepared statements and utility ops are exclusive (see "METHODS").

UNICODE

EV::MariaDB supports full Unicode (including 4-byte characters such as emoji) when the connection charset is utf8mb4.

Setup

my $m = EV::MariaDB->new(
    charset => 'utf8mb4',
    utf8    => 1,
    ...
);

charset sets the connection character set used by the server. utf8 controls Perl-side string flagging: when enabled, result strings from UTF-8 columns are returned with Perl's internal UTF-8 flag set so length, regex, and other character operations behave correctly.

Reading

With utf8 => 1, text query, prepared-statement, and streaming results are UTF-8-flagged per column based on the column's charset. Binary and non-UTF-8 columns are returned as raw bytes. Column names in $fields are UTF-8-flagged whenever the connection charset is utf8 or utf8mb4, regardless of this option.

Without utf8 => 1, all values are byte strings — decode with "decode_utf8" in Encode.

Writing

No special handling is needed. Perl strings (UTF-8-flagged or not) are sent as their underlying byte representation via SvPV. As long as the connection charset matches the encoding of the bytes (i.e., charset => 'utf8mb4' for UTF-8 data), the server stores them correctly. This applies to both text queries (query, escape) and prepared-statement parameters (execute, bind_params).

SEE ALSO

EV, Alien::MariaDB, DBD::MariaDB, AnyEvent::MySQL. MariaDB Connector/C non-blocking API: https://mariadb.com/docs/connector-c/api-functions/non-blocking.

AUTHOR

vividsnow

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.