MySQL Native Driver Plugin API
The MySQL Native Driver Plugin API is a feature of MySQL Native
Driver, or mysqlnd. Mysqlnd
plugins operate in the layer between PHP applications and the MySQL
server. This is comparable to MySQL Proxy. MySQL Proxy operates on a
layer between any MySQL client application, for example, a PHP
application and, the MySQL server. Mysqlnd plugins
can undertake typical MySQL Proxy tasks such as load balancing,
monitoring and performance optimizations. Due to the different
architecture and location, mysqlnd plugins do not
have some of MySQL Proxy's disadvantages. For example, with plugins,
there is no single point of failure, no dedicated proxy server to
deploy, and no new programming language to learn (Lua).
A mysqlnd plugin can be thought of as an extension
to mysqlnd. Plugins can intercept the majority of
mysqlnd functions. The mysqlnd
functions are called by the PHP MySQL extensions such as
ext/mysql, ext/mysqli, and
PDO_MYSQL. As a result, it is possible for a
mysqlnd plugin to intercept all calls made to these
extensions from the client application.
Internal mysqlnd function calls can also be
intercepted, or replaced. There are no restrictions on manipulating
mysqlnd internal function tables. It is possible to
set things up so that when certain mysqlnd
functions are called by the extensions that use
mysqlnd, the call is directed to the appropriate
function in the mysqlnd plugin. The ability to
manipulate mysqlnd internal function tables in this
way allows maximum flexibility for plugins.
Mysqlnd plugins are in fact PHP Extensions, written
in C, that use the mysqlnd plugin API (which is
built into MySQL Native Driver, mysqlnd). Plugins
can be made 100% transparent to PHP applications. No application
changes are needed because plugins operate on a different layer. The
mysqlnd plugin can be thought of as operating in a
layer below mysqlnd.
The following list represents some possible applications of
mysqlnd plugins.
Load Balancing
Read/Write Splitting. An example of this is the PECL/mysqlnd_ms
(Master Slave) extension. This extension splits read/write queries
for a replication setup.
Failover
Round-Robin, least loaded
Monitoring
Query Logging
Query Analysis
Query Auditing. An example of this is the PECL/mysqlnd_sip (SQL
Injection Protection) extension. This extension inspects queries
and executes only those that are allowed according to a ruleset.
Performance
Caching. An example of this is the PECL/mysqlnd_qc (Query Cache)
extension.
Throttling
Sharding. An example of this is the PECL/mysqlnd_mc (Multi
Connect) extension. This extension will attempt to split a SELECT
statement into n-parts, using SELECT ... LIMIT part_1, SELECT
LIMIT part_n. It sends the queries to distinct MySQL servers and
merges the result at the client.
MySQL Native Driver Plugins Available
There are a number of mysqlnd plugins already available. These
include:
PECL/mysqlnd_mc - Multi Connect
plugin.
PECL/mysqlnd_ms - Master Slave
plugin.
PECL/mysqlnd_qc - Query Cache
plugin.
PECL/mysqlnd_pscache - Prepared
Statement Handle Cache plugin.
PECL/mysqlnd_sip - SQL Injection
Protection plugin.
PECL/mysqlnd_uh - User Handler
plugin.
A comparison of mysqlnd plugins with MySQL ProxyMysqlnd plugins and MySQL Proxy are different
technologies using different approaches. Both are valid tools for
solving a variety of common tasks such as load balancing, monitoring,
and performance enhancements. An important difference is that MySQL
Proxy works with all MySQL clients, whereas
mysqlnd plugins are specific to PHP applications.
As a PHP Extension, a mysqlnd plugin gets
installed on the PHP application server, along with the rest of PHP.
MySQL Proxy can either be run on the PHP application server or can be
installed on a dedicated machine to handle multiple PHP application
servers.
Deploying MySQL Proxy on the application server has two advantages:
No single point of failure
Easy to scale out (horizontal scale out, scale by client)
MySQL Proxy (and mysqlnd plugins) can solve
problems easily which otherwise would have required changes to
existing applications.
However, MySQL Proxy does have some disadvantages:
MySQL Proxy is a new component and technology to master and deploy.
MySQL Proxy requires knowledge of the Lua scripting language.
MySQL Proxy can be customized with C and Lua programming. Lua is the
preferred scripting language of MySQL Proxy. For most PHP experts Lua
is a new language to learn. A mysqlnd plugin can
be written in C. It is also possible to write plugins in PHP using
PECL/mysqlnd_uh.
MySQL Proxy runs as a daemon - a background process. MySQL Proxy can
recall earlier decisions, as all state can be retained. However, a
mysqlnd plugin is bound to the request-based
lifecycle of PHP. MySQL Proxy can also share one-time computed
results among multiple application servers. A
mysqlnd plugin would need to store data in a
persistent medium to be able to do this. Another daemon would need to
be used for this purpose, such as Memcache. This gives MySQL Proxy an
advantage in this case.
MySQL Proxy works on top of the wire protocol. With MySQL Proxy you
have to parse and reverse engineer the MySQL Client Server Protocol.
Actions are limited to those that can be achieved by manipulating the
communication protocol. If the wire protocol changes (which happens
very rarely) MySQL Proxy scripts would need to be changed as well.
Mysqlnd plugins work on top of the C API, which
mirrors the libmysqlclient client and Connector/C APIs.
This C API is basically a wrapper around the MySQL Client Server
protocol, or wire protocol, as it is sometimes called. You can
intercept all C API calls. PHP makes use of the C API, therefore you
can hook all PHP calls, without the need to program at the level of
the wire protocol.
Mysqlnd implements the wire protocol. Plugins can
therefore parse, reverse engineer, manipulate and even replace the
communication protocol. However, this is usually not required.
As plugins allow you to create implementations that use two levels (C
API and wire protocol), they have greater flexibility than MySQL
Proxy. If a mysqlnd plugin is implemented using
the C API, any subsequent changes to the wire protocol do not require
changes to the plugin itself.
Obtaining the mysqlnd plugin API
The mysqlnd plugin API is simply part of the MySQL
Native Driver PHP extension, ext/mysqlnd.
Development started on the mysqlnd plugin API in
December 2009. It is developed as part of the PHP source repository,
and as such is available to the public either via Git, or through
source snapshot downloads.
The following table shows PHP versions and the corresponding
mysqlnd version contained within.
The bundled mysqlnd version per PHP releasePHP VersionMySQL Native Driver version5.3.05.0.55.3.15.0.55.3.25.0.75.3.35.0.75.3.45.0.7
Plugin developers can determine the mysqlnd
version through accessing MYSQLND_VERSION, which
is a string of the format mysqlnd 5.0.7-dev - 091210 -
$Revision: 300535, or through
MYSQLND_VERSION_ID, which is an integer such as
50007. Developers can calculate the version number as follows:
During development, developers should refer to the
mysqlnd version number for compatibility and
version tests, as several iterations of mysqlnd
could occur during the lifetime of a PHP development branch with a
single PHP version number.
MySQL Native Driver Plugin Architecture
This section provides an overview of the mysqlnd
plugin architecture.
MySQL Native Driver Overview
Before developing mysqlnd plugins, it is useful to
know a little of how mysqlnd itself is organized.
Mysqlnd consists of the following modules:
The mysqlnd organization chart, per moduleModules Statisticsmysqlnd_statistics.cConnectionmysqlnd.cResultsetmysqlnd_result.cResultset Metadatamysqlnd_result_meta.cStatementmysqlnd_ps.cNetworkmysqlnd_net.cWire protocolmysqlnd_wireprotocol.c
C Object Oriented Paradigm
At the code level, mysqlnd uses a C pattern for
implementing object orientation.
In C you use a struct to represent an object.
Members of the struct represent object properties. Struct members
pointing to functions represent methods.
Unlike with other languages such as C++ or Java, there are no fixed
rules on inheritance in the C object oriented paradigm. However,
there are some conventions that need to be followed that will be
discussed later.
The PHP Life Cycle
When considering the PHP life cycle there are two basic cycles:
PHP engine startup and shutdown cycle
Request cycle
When the PHP engine starts up it will call the module initialization
(MINIT) function of each registered extension. This allows each
module to setup variables and allocate resources that will exist for
the lifetime of the PHP engine process. When the PHP engine shuts
down it will call the module shutdown (MSHUTDOWN) function of each
extension.
During the lifetime of the PHP engine it will receive a number of
requests. Each request constitutes another life cycle. On each
request the PHP engine will call the request initialization function
of each extension. The extension can perform any variable setup and
resource allocation required for request processing. As the request
cycle ends the engine calls the request shutdown (RSHUTDOWN) function
of each extension so the extension can perform any cleanup required.
How a plugin works
A mysqlnd plugin works by intercepting calls made
to mysqlnd by extensions that use
mysqlnd. This is achieved by obtaining the
mysqlnd function table, backing it up, and
replacing it by a custom function table, which calls the functions of
the plugin as required.
The following code shows how the mysqlnd function
table is replaced:
query = MYSQLND_METHOD(my_conn_class, query);
}
]]>
Connection function table manipulations must be done during Module
Initialization (MINIT). The function table is a global shared
resource. In an multi-threaded environment, with a TSRM build, the
manipulation of a global shared resource during the request
processing will almost certainly result in conflicts.
Do not use any fixed-size logic when manipulating the
mysqlnd function table: new methods may be added
at the end of the function table. The function table may change at
any time in the future.
Calling parent methods
If the original function table entries are backed up, it is still
possible to call the original function table entries - the parent
methods.
In some cases, such as for
Connection::stmt_init(), it is vital to call the
parent method prior to any other activity in the derived method.
Extending properties
A mysqlnd object is represented by a C struct. It
is not possible to add a member to a C struct at run time. Users of
mysqlnd objects cannot simply add properties to
the objects.
Arbitrary data (properties) can be added to a
mysqlnd objects using an appropriate function of
the
mysqlnd_plugin_get_plugin_<object>_data()
family. When allocating an object mysqlnd reserves
space at the end of the object to hold a void *
pointer to arbitrary data. mysqlnd reserves space
for one void * pointer per plugin.
The following table shows how to calculate the position of the
pointer for a specific plugin:
Pointer calculations for mysqlndMemory addressContents0Beginning of the mysqlnd object C structnEnd of the mysqlnd object C structn + (m x sizeof(void*))void* to object data of the m-th plugin
If you plan to subclass any of the mysqlnd object
constructors, which is allowed, you must keep this in mind!
The following code shows extending properties:
persistent);
(*props)->query_counter = 0;
}
return props;
}
]]>
The plugin developer is responsible for the management of plugin data
memory.
Use of the mysqlnd memory allocator is recommended
for plugin data. These functions are named using the convention:
mnd_*loc(). The mysqlnd
allocator has some useful features, such as the ability to use a
debug allocator in a non-debug build.
When and how to subclassWhen to subclass?Each instance has its own private function table?How to subclass?Connection (MYSQLND)MINITNomysqlnd_conn_get_methods()Resultset (MYSQLND_RES)MINIT or laterYesmysqlnd_result_get_methods() or object method function table
manipulationResultset Meta (MYSQLND_RES_METADATA)MINITNomysqlnd_result_metadata_get_methods()Statement (MYSQLND_STMT)MINITNomysqlnd_stmt_get_methods()Network (MYSQLND_NET)MINIT or laterYesmysqlnd_net_get_methods() or object method function table manipulationWire protocol (MYSQLND_PROTOCOL)MINIT or laterYesmysqlnd_protocol_get_methods() or object method function table
manipulation
You must not manipulate function tables at any time later than MINIT
if it is not allowed according to the above table.
Some classes contain a pointer to the method function table. All
instances of such a class will share the same function table. To
avoid chaos, in particular in threaded environments, such function
tables must only be manipulated during MINIT.
Other classes use copies of a globally shared function table. The
class function table copy is created together with the object. Each
object uses its own function table. This gives you two options: you
can manipulate the default function table of an object at MINIT, and
you can additionally refine methods of an object without impacting
other instances of the same class.
The advantage of the shared function table approach is performance.
There is no need to copy a function table for each and every object.
Constructor statusTypeAllocation, construction, resetCan be modified?CallerConnection (MYSQLND)mysqlnd_init()Nomysqlnd_connect()Resultset(MYSQLND_RES)
Allocation:
Connection::result_init()
Reset and re-initialized during:
Result::use_result()
Result::store_result
Yes, but call parent!
Connection::list_fields()
Statement::get_result()
Statement::prepare() (Metadata only)
Statement::resultMetaData()
Resultset Meta (MYSQLND_RES_METADATA)Connection::result_meta_init()Yes, but call parent!Result::read_result_metadata()Statement (MYSQLND_STMT)Connection::stmt_init()Yes, but call parent!Connection::stmt_init()Network (MYSQLND_NET)mysqlnd_net_init()NoConnection::init()Wire protocol (MYSQLND_PROTOCOL)mysqlnd_protocol_init()NoConnection::init()
It is strongly recommended that you do not entirely replace a
constructor. The constructors perform memory allocations. The memory
allocations are vital for the mysqlnd plugin API
and the object logic of mysqlnd. If you do not
care about warnings and insist on hooking the constructors, you
should at least call the parent constructor before doing anything in
your constructor.
Regardless of all warnings, it can be useful to subclass
constructors. Constructors are the perfect place for modifying the
function tables of objects with non-shared object tables, such as
Resultset, Network, Wire Protocol.
Destruction statusTypeDerived method must call parent?DestructorConnectionyes, after method executionfree_contents(), end_psession()Resultsetyes, after method executionfree_result()Resultset Metayes, after method executionfree()Statementyes, after method executiondtor(), free_stmt_content()Networkyes, after method executionfree()Wire protocolyes, after method executionfree()
The destructors are the appropriate place to free properties,
mysqlnd_plugin_get_plugin_<object>_data().
The listed destructors may not be equivalent to the actual
mysqlnd method freeing the object itself. However,
they are the best possible place for you to hook in and free your
plugin data. As with constructors you may replace the methods
entirely but this is not recommended. If multiple methods are listed
in the above table you will need to hook all of the listed methods
and free your plugin data in whichever method is called first by
mysqlnd.
The recommended method for plugins is to simply hook the methods,
free your memory and call the parent implementation immediately
following this.
Due to a bug in PHP versions 5.3.0 to 5.3.3, plugins do not
associate plugin data with a persistent connection. This is because
ext/mysql and ext/mysqli do
not trigger all the necessary mysqlndend_psession() method calls and the plugin may
therefore leak memory. This has been fixed in PHP 5.3.4.
The mysqlnd plugin API
The following is a list of functions provided in the
mysqlnd plugin API:
mysqlnd_plugin_register()
mysqlnd_plugin_count()
mysqlnd_plugin_get_plugin_connection_data()
mysqlnd_plugin_get_plugin_result_data()
mysqlnd_plugin_get_plugin_stmt_data()
mysqlnd_plugin_get_plugin_net_data()
mysqlnd_plugin_get_plugin_protocol_data()
mysqlnd_conn_get_methods()
mysqlnd_result_get_methods()
mysqlnd_result_meta_get_methods()
mysqlnd_stmt_get_methods()
mysqlnd_net_get_methods()
mysqlnd_protocol_get_methods()
There is no formal definition of what a plugin is and how a plugin
mechanism works.
Components often found in plugins mechanisms are:
A plugin manager
A plugin API
Application services (or modules)
Application service APIs (or module APIs)
The mysqlnd plugin concept employs these features,
and additionally enjoys an open architecture.
No Restrictions
A plugin has full access to the inner workings of
mysqlnd. There are no security limits or
restrictions. Everything can be overwritten to implement friendly or
hostile algorithms. It is recommended you only deploy plugins from a
trusted source.
As discussed previously, plugins can use pointers freely. These
pointers are not restricted in any way, and can point into another
plugin's data. Simple offset arithmetic can be used to read another
plugin's data.
It is recommended that you write cooperative plugins, and that you
always call the parent method. The plugins should always cooperate
with mysqlnd itself.
Issues: an example of chaining and cooperationExtensionmysqlnd.query() pointercall stack if calling parentext/mysqlndmysqlnd.query()mysqlnd.queryext/mysqlnd_cachemysqlnd_cache.query()
mysqlnd_cache.query()
mysqlnd.query
ext/mysqlnd_monitormysqlnd_monitor.query()
mysqlnd_monitor.query()
mysqlnd_cache.query()
mysqlnd.query
In this scenario, a cache (ext/mysqlnd_cache) and
a monitor (ext/mysqlnd_monitor) plugin are loaded.
Both subclass Connection::query(). Plugin
registration happens at MINIT using the logic
shown previously. PHP calls extensions in alphabetical order by
default. Plugins are not aware of each other and do not set extension
dependencies.
By default the plugins call the parent implementation of the query
method in their derived version of the method.
PHP Extension Recap
This is a recap of what happens when using an example plugin,
ext/mysqlnd_plugin, which exposes the
mysqlnd C plugin API to PHP:
Any PHP MySQL application tries to establish a connection to
192.168.2.29
The PHP application will either use ext/mysql,
ext/mysqli or PDO_MYSQL. All
three PHP MySQL extensions use mysqlnd to
establish the connection to 192.168.2.29.
Mysqlnd calls its connect method, which has been
subclassed by ext/mysqlnd_plugin.
ext/mysqlnd_plugin calls the userspace hook
proxy::connect() registered by the user.
The userspace hook changes the connection host IP from 192.168.2.29
to 127.0.0.1 and returns the connection established by
parent::connect().
ext/mysqlnd_plugin performs the equivalent of
parent::connect(127.0.0.1) by calling the
original mysqlnd method for establishing a
connection.
ext/mysqlnd establishes a connection and returns
to ext/mysqlnd_plugin.
ext/mysqlnd_plugin returns as well.
Whatever PHP MySQL extension had been used by the application, it
receives a connection to 127.0.0.1. The PHP MySQL extension itself
returns to the PHP application. The circle is closed.
Getting started building a mysqlnd plugin
It is important to remember that a mysqlnd plugin
is itself a PHP extension.
The following code shows the basic structure of the MINIT function
that will be used in the typical mysqlnd plugin:
query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
]]>
Task analysis: from C to userspace
Process:
PHP: user registers plugin callback
PHP: user calls any PHP MySQL API to connect to MySQL
C: ext/*mysql* calls mysqlnd method
C: mysqlnd ends up in ext/mysqlnd_plugin
C: ext/mysqlnd_plugin
Calls userspace callback
Or original mysqlnd method, if userspace
callback not set
You need to carry out the following:
Write a class "mysqlnd_plugin_connection" in C
Accept and register proxy object through
"mysqlnd_plugin_set_conn_proxy()"
Call userspace proxy methods from C (optimization -
zend_interfaces.h)
Userspace object methods can either be called using
call_user_function() or you can operate at a level
closer to the Zend Engine and use
zend_call_method().
Optimization: calling methods from C using
zend_call_method
The following code snippet shows the prototype for the
zend_call_method function, taken from
zend_interfaces.h.
Zend API supports only two arguments. You may need more, for example:
To get around this problem you will need to make a copy of
zend_call_method() and add a facility for
additional parameters. You can do this by creating a set of
MY_ZEND_CALL_METHOD_WRAPPER macros.
Calling PHP userspace
This code snippet shows the optimized method for calling a userspace
function from C:
Calling userspace: simple arguments
Calling userspace: structs as arguments
The first argument of many mysqlnd methods is a C
"object". For example, the first argument of the connect() method is
a pointer to MYSQLND. The struct MYSQLND
represents a mysqlnd connection object.
The mysqlnd connection object pointer can be
compared to a standard I/O file handle. Like a standard I/O file
handle a mysqlnd connection object shall be linked
to the userspace using the PHP resource variable type.
From C to userspace and back
PHP users must be able to call the parent implementation of an
overwritten method.
As a result of subclassing it is possible to refine only selected
methods and you can choose to have "pre" or "post" hooks.
Buildin class:
mysqlnd_plugin_connection::connect()