summaryrefslogtreecommitdiff
path: root/ext/dbx/howto_extend_dbx.html
blob: 9b1ba6e4f3a996d3fb2f46d782596938cec9ed7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
<html>
<head>
<title>HOWTO extend dbx</title>

</head>

<body marginwidth="0" marginheight="0">
<style type="text/css">
<!-- 

body, p, td, input, select, a, h1, h2, h3, h4, h5, h6, marquee, blink
                { font-family: Verdana,sans-serif; font-size: 10pt; color: #000000; }
h1              { font-size: 16pt; font-weight: bold; }
h2              { font-size: 14pt; font-weight: bold; }
h3              { font-size: 12pt; font-weight: bold; }
h4              { font-size: 10pt; font-weight: bold; }
h5              { font-size: 8pt;}
h6              { font-size: 6pt;}

body            { background-color: #F0F0F0; }
a               { color: #000088; }

.title          { font-size: 14pt; font-weight: bold; }
.fn-title       { font-size: 12pt; font-weight: bold; }
.fn-phpversion  { font-size: 10pt; font-weight: normal; }
.fn-def         { margin-top: 8px; margin-bottom: 8px; background-color: #99BBDD; }
.fn-defname     { font-weight: bold; }
.fn-name        { font-size: 12pt; font-weight: bold; }
.fn-param       { font-weight: bold; }
.example        { margin-top: 12px; font-weight: bold; }
.text           { margin: 8px; }
.code           { margin: 16px; font-family: Courier; background-color: #CCCCCC; }
.bold           { font-weight: bold; }

.indent         { margin-left: 16px; }
.newpage        { border-top: 2px solid #000088; margin-bottom: 8px; }
.tab            { margin-left: 16px; }


-->
</style>





<a name="top"></a>
<div class="title">
How-to code support for another database<br>
</div>
<div class="text">
Every supported database module must be loaded by PHP before it can be used. Every supported database module must be added to the dbx-module before it can be used. Currently there is support for MySQL, PostgreSQL, Microsoft SQL Server, Frontbase, Sybase-CT and ODBC, but it is not difficult to add support for more databases.<br>
<br>
The dbx module is found in de PHP ext/dbx folder. The support-code is found in the same folder <br>
<br>
To add support for module 'blabla' the following steps must be taken: <br>
1. the dbx.c source file must be extended to recognize module 'blabla' and switch to the 'blabla' functions.<br>
2. the files dbx_blabla.h and dbx_blabla.c must be created and edited to produce the required response.<br>
3. add the files from step 2 to the project.<br>
4. compile.<br>
5. enjoy.<br>
<br>
You may need a bit of help for step 1 and 2. If you need help for step 3 or 4, you shouldn't try to attempt this probably :-). If you need help with step 5 you're in big trouble ;o)<br>
Help for step 1 and 2 is given below, bold text in code indicate the important bits.<br>
</div>
<p>
<a href="index.html">home</a><br>
<div class="newpage"></div>

<div class="fn-title"><a name="step1"></a>
1. the dbx.c source file must be extended<br>
</div>
<div class="text">
Define a module identifier and assign it a unique number. Include your header file here as well.<br>
<pre class="code">
// defines for supported databases
#define DBX_UNKNOWN 0
#define DBX_MYSQL 1
#define DBX_ODBC 2
<span class="bold">#define DBX_BLABLA 3</span>
// includes for supported databases
#include "dbx.h"
#include "dbx_mysql.h"
#include "dbx_odbc.h"
<span class="bold">#include "dbx_blabla.h"</span>
</pre>
Add code to the module_identifier_exists function so DBX_BLABLA will be recognized:<br>
<pre class="code">
int module_identifier_exists(long module_identifier) {
    switch (module_identifier) {
        case DBX_MYSQL: return module_exists("mysql");
        case DBX_ODBC: return module_exists("odbc");
        <span class="bold">case DBX_BLABLA: return module_exists("blabla");</span>
        }
    return 0;
    }
</pre>
Add code to the get_module_identifier function so your extension will be recognized:<br>
<pre class="code">
int get_module_identifier(char * module_name) {
    if (!strcmp("mysql", module_name)) return DBX_MYSQL;
    if (!strcmp("odbc", module_name)) return DBX_ODBC;
    <span class="bold">if (!strcmp("blabla", module_name)) return DBX_BLABLA;</span>
    return DBX_UNKNOWN;
    }
</pre>
Add code for exposing the DBX_BLABLA constant to the world:<br>
<pre class="code">
ZEND_MINIT_FUNCTION(dbx)
{
/*/	REGISTER_INI_ENTRIES(); /*/

    REGISTER_LONG_CONSTANT("DBX_MYSQL", DBX_MYSQL, CONST_CS | CONST_PERSISTENT);
    REGISTER_LONG_CONSTANT("DBX_ODBC", DBX_ODBC, CONST_CS | CONST_PERSISTENT);
    <span class="bold">REGISTER_LONG_CONSTANT("DBX_BLABLA", DBX_BLABLA CONST_CS | CONST_PERSISTENT);</span>

    [...]

    return SUCCESS;
    }
</pre>
Add code for inclusion in the phpinfo() function (optional, but recommended):<br>
<pre class="code">
ZEND_MINFO_FUNCTION(dbx)
{
    php_info_print_table_start();
    php_info_print_table_row(2, "dbx support", "enabled");
    php_info_print_table_row(2, "dbx support for MySQL", "enabled");
    php_info_print_table_row(2, "dbx support for ODBC", "enabled");
    <span class="bold">php_info_print_table_row(2, "dbx support for BlaBla", "enabled");</span>
    php_info_print_table_end();
    DISPLAY_INI_ENTRIES();
}
</pre>
Finally, for the implementation of all switch_dbx_XXXXX functions, copy a 'case'-line for every function that you support (should be all functions!). Here is an example for only the switch_dbx_connect function:<br>
<pre class="code">
int switch_dbx_connect(zval **rv, zval **host, zval **db, zval **username, zval **password, INTERNAL_FUNCTION_PARAMETERS, zval **dbx_module) {
    // returns connection handle as resource on success or 0 as long on failure
    switch ((*dbx_module)-&gt;value.lval) {
        case DBX_MYSQL: return dbx_mysql_connect(rv, host, db, username, password, INTERNAL_FUNCTION_PARAM_PASSTHRU);
        case DBX_ODBC: return dbx_odbc_connect(rv, host, db, username, password, INTERNAL_FUNCTION_PARAM_PASSTHRU);
        <span class="bold">case DBX_BLABLA: return dbx_blabla_connect(rv, host, db, username, password, INTERNAL_FUNCTION_PARAM_PASSTHRU);</span>
        }
    zend_error(E_WARNING, "dbx_connect: not supported in this module");
    return 0;
    }
</pre>
This should be done for all switch_dbx_XXXXX functions. They are listed below:<br>
<pre class="code">
int <a href="#connect">switch_dbx_connect(...)</a>;
int <a href="#pconnect">switch_dbx_pconnect(...)</a>;
int <a href="#close">switch_dbx_close(...)</a>;
int <a href="#query">switch_dbx_query(...)</a>;
int <a href="#getcolumncount">switch_dbx_getcolumncount(...)</a>;
int <a href="#getcolumnname">switch_dbx_getcolumnname(...)</a>;
int <a href="#getcolumntype">switch_dbx_getcolumntype(...)</a>;
int <a href="#getrow">switch_dbx_getrow(...)</a>;
int <a href="#error">switch_dbx_error(...)</a>;
</pre>
This concludes the changes for the dbx.c file. All that is needed now is to actually code the dbx_blabla_connect and other functions, which we will see in the following step.<br>
</div>
<p>
<a href="#top">top</a><br>
<div class="newpage"></div>

<div class="fn-title"><a name="step1"></a>
2. the files dbx_blabla.h and dbx_blabla.c<br>
</div>
<div class="text">
The dbx_blabla.h and dbx_blabla.c file are created in the folder /ext/dbx.<br>
The easiest method is to just copy dbx_mysql.h en dbx_mysql.c, open both files, and do a search and replace ('blabla' for 'mysql' and 'BLABLA' for 'MYSQL'). Yes, case-sensitive.<br>
For the .h file, that's all. <br>
For the .c file, the fun has just started :-)<br>
In the .c is the actual realization of the database abstraction, where a call to a standard function is translated into one or more database-specific calls. For mysql, a dbx_connect translates to a mysql_connect followed by a mysql_select_db. Refer to the dbx_mysql.c and dbx_odbc.c files regularly for examples!<br>
In dbx.h one macro and one function are defined to make the calling of external module functions and returning of the results easier: dbx_call_any_function and MOVE_RETURNED_TO_RV.<br>
<p>
 The details of what each of the functions do, what parameters they get, and what parameters they should return are discussed below. But first, the dbx_mysql_connect function is presented and explained, so you get an idea of how things work.<br>
<pre class="code">
int dbx_mysql_connect(zval **rv, zval **host, zval **db, zval **username, zval **password, INTERNAL_FUNCTION_PARAMETERS) {
    // returns connection handle as resource on success or 0 as long on 
    // failure
    int number_of_arguments;
    zval **arguments[3];
    zval * returned_zval=NULL;
    zval * select_db_zval=NULL;

    number_of_arguments=3;
    arguments[0]=host;
    arguments[1]=username;
    arguments[2]=password;
    dbx_call_any_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, <span class="bold">"mysql_connect"</span>, &returned_zval, number_of_arguments, arguments);
    if (!returned_zval || returned_zval-&gt;type!=IS_RESOURCE) {
        if (returned_zval) zval_ptr_dtor(&returned_zval);
        return 0;
        }
    MOVE_RETURNED_TO_RV(rv, returned_zval);

    number_of_arguments=2;
    arguments[0]=db;
    arguments[1]=rv;
    dbx_call_any_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, <span class="bold">"mysql_select_db"</span>, &select_db_zval, number_of_arguments, arguments);
    zval_ptr_dtor(&select_db_zval);

    return 1;
    }
</pre>
First of all, all functions return 0 on failure and 1 on success. These values are used in the dbx-routines, they are never actually given back to the PHP-script writer that calls the dbx_connect function.<br>
The actual value that is of interest to the caller is returned in the <span class="bold">rv</span> parameter. In this case it is a connection handle (or link identifier, in mysql-speak), that is also returned if the database selection doesn't succeed. <br>
The parameters that are of interest to the function are located between the <span class="bold">rv</span> and <span class="bold">INTERNAL_FUNCTION_PARAMETERS</span> parameters, in this case it is a <span class="bold">host</span> name, a <span class="bold">db</span> name, a <span class="bold">username</span> and a <span class="bold">password</span>. These are the values that the user specifies if he calls dbx_connect(); These parameters are used in the calls to the mysql-database functions. The user actually also specifies a module-name, that decides which connect-function should be called. Here, he specified 'mysql'.<br>
To actually call a mysql module function, you can use <span class="bold">dbx_call_any_function</span> where you specify the function name (it is used twice in dbx_mysql_connect, see <span class="bold">'mysql_connect'</span> and <span class="bold">'mysql_select_db'</span>, they are printed bold in the code). The value that is returned from the function will be stored in the next argument, a zval * (e.g. <span class="bold">returned_zval</span>) parameter that you must declare locally. To actually return such a parameter, use the <span class="bold">MOVE_RETURNED_TO_RV(rv, returned_zval)</span> macro, which copies the values to <span class="bold">rv</span> and frees anything that may be left in <span class="bold">returned_zval</span>. Parameters that must be passed to the mysql-function are stored in the <span class="bold">arguments</span> array, which must be large enough to hold all parameters to the function-call that requires the most parameters (in this case, mysql_connect expects 3 parameters, mysql_select_db expects two parameters, so the <span class="bold">arguments</span> array is defined 'zval **arguments[<span class="bold">3</span>]'). The <span class="bold">number_of_arguments</span> parameter is set to the actual number of arguments that the function-call requires. As you can see it is initialized to 3, for the first call to mysql_connect. Then it is set to 2, for the call to mysql_select_db. If you call a function that retrieves a value, and you don't return it with MOVE_RETURNED_TO_RV, then you must free the value using <span class="bold">zval_ptr_dtor</span>, as can be seen right after the call to mysql_select_db. This can also be seen directly after the call to mysql_connect, if somehow this function failed or didn't return a resource (on a successful connect mysql_connect returns a resource) the returned value is freed as well (and 0 is returned because the connection failed).<br>
<p>
OK, now the description of all functions that you should implement, and what is expected of them...<br>
<a name="connect"></a><pre class="code">
int <span class="fn-name">dbx_blabla_connect</span>(zval **rv, zval **<span class="fn-param">host</span>, zval **<span class="fn-param">db</span>, zval **<span class="fn-param">username</span>, zval **<span class="fn-param">password</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on connect-failure and 1 on success
// rv: connection handle as resource on success or nothing on failure
</pre>
dbx_blabla_connect creates a connection to a database on a specified host, using username and password for authentication. This may be done by connecting to a server and selecting a database (as mysql does), or connecting to a specific database directly (as in ODBC). <br>
What must be returned (in <span class="bold">rv</span>) is the link identifier that is returned from the blabla_connect function, in it's native form so the end-user can use $db-&gt;handle to call other blabla_* functions that expect this parameter.<br>
What must be returned from the function is a 1 on success and a 0 on failure. Remember that a failed database selection can still return a 1 because the connection succeeded!<br>
The host (string) is the name of the machine the server is run on, but it may be empty if a database name is enough to establish a connection.<br>
The db (string) is the name of the database to select, or, for e.g. ODBC, the identifier that is needed to actually select the database.<br>
The username (string) and password (string) are used for authentication.<br>
<a name="pconnect"></a><pre class="code">
int <span class="fn-name">dbx_blabla_pconnect</span>(zval **rv, zval **<span class="fn-param">host</span>, zval **<span class="fn-param">db</span>, zval **<span class="fn-param">username</span>, zval **<span class="fn-param">password</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on pconnect-failure and 1 on success
// rv: persistent connection handle as resource on success or nothing
// on failure
</pre>
dbx_blabla_pconnect is identical to dbx_blabla_connect except that it will create a persistent connection.<br>
<a name="close"></a><pre class="code">
int <span class="fn-name">dbx_blabla_close</span>(zval **rv, zval **<span class="fn-param">dbx_handle</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on close-failure and 1 on success
// rv: 1 as bool on success or nothing on failure
</pre>
dbx_blabla_close closes an open connection, whether it was created persistently or not.<br>
What must be returned (in <span class="bold">rv</span>) is a boolean true that indicates when the connection was closed successfully. If it wasn't, no value is returned in <span class="bold">rv</span>.<br>
What must be returned from the function is a 1 on success and a 0 on failure. Note that an unsuccessful close is still a succeeded function call.<br>
The dbx_handle is the same value that you returned from dbx_blabla_connect or dbx_blabla_pconnect.<br>
<a name="query"></a><pre class="code">
int <span class="fn-name">dbx_blabla_query</span>(zval **rv, zval **<span class="fn-param">dbx_handle</span>, zval **<span class="fn-param">sql_statement</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on query-failure and 1 on success
// rv: 1 as bool or a result identifier as resource on success 
// or nothing on failure
</pre>
dbx_blabla_query executes an SQL statement over the connection.<br>
What must be returned (in <span class="bold">rv</span>) is a nothing on failure, on success it must return either a boolean 1 for queries that don't return data (like INSERT INTO) or a native result-handle for queries that do return data (SELECT). The native result handle ($q-&gt;handle) can be used by the end-user to call other blabla_* functions that expect this parameter.<br>
What must be returned from the function is a 1 on success and a 0 on failure. Note that a failed query execution can still return a 1 because the query function succeeded!<br>
The dbx_handle is the same value that you returned from dbx_blabla_connect or dbx_blabla_pconnect.<br>
The sql_statement (string) can have any value.<br>
<a name="getcolumncount"></a><pre class="code">
int <span class="fn-name">dbx_blabla_getcolumncount</span>(zval **rv, zval **<span class="fn-param">result_handle</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on query-failure and 1 on success
// returns column-count as long on success or nothing on failure
</pre>
dbx_blabla_getcolumncount gets the number of fields that the query-result contains.<br>
What must be returned (in <span class="bold">rv</span>) is the number of fields as long from the query result specified by the result_handle.<br>
What must be returned from the function is a 1 on success and a 0 on failure. <br>
The result_handle is the same value that you returned from dbx_query.<br>
<a name="getcolumnname"></a><pre class="code">
int <span class="fn-name">dbx_blabla_getcolumnname</span>(zval **rv, zval **<span class="fn-param">result_handle</span>, long <span class="fn-param">column_index</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on failure and 1 on success
// returns column-name as string on success or nothing on failure
</pre>
dbx_blabla_getcolumnname gets the fieldname of the specified column.<br>
What must be returned (in <span class="bold">rv</span>) is the fieldname as string of the given column.<br>
What must be returned from the function is a 1 on success and a 0 on failure. <br>
The result_handle is the same value that you returned from dbx_query.<br>
The column_index is a long that ranges from 0 to the value you returned from dbx_blabla_getcolumncount minus 1 [0..columncount-1].<br>
<a name="getcolumntype"></a><pre class="code">
int <span class="fn-name">dbx_blabla_getcolumntype</span>(zval **rv, zval **<span class="fn-param">result_handle</span>, long <span class="fn-param">column_index</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on failure and 1 on success
// returns column-type as string on success or nothing on failure
</pre>
dbx_blabla_getcolumnname gets the field type of the specified column.<br>
What must be returned (in <span class="bold">rv</span>) is the field type as string of the given column.<br>
What must be returned from the function is a 1 on success and a 0 on failure. <br>
The result_handle is the same value that you returned from dbx_query.<br>
The column_index is a long that ranges from 0 to the value you returned from dbx_blabla_getcolumncount minus 1 [0..columncount-1].<br>
<a name="getrow"></a><pre class="code">
int <span class="fn-name">dbx_blabla_getrow</span>(zval **rv, zval **<span class="fn-param">result_handle</span>, long <span class="fn-param">row_number</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on failure and 1 on success
// returns array[0..columncount-1] as strings on success or 0 as long 
// on failure
</pre>
dbx_blabla_getrow gets the next row from the query-results.<br>
In some cases (PostgreSQL) the rownumber is needed to actually fetch the row. This will be provided (it will be indexed starting at 0) by the dbx_query function. In other cases it is not needed and thus not used.<br>
What must be returned (in <span class="bold">rv</span>) is an indexed array[0..columncount-1] of strings, containing the data from the row (for mysql this is easy since it already performs this way, for ODBC the array has to be constructed inside this function from a loop that fetches the data for each column).<br>
What must be returned from the function is a 1 on success and a 0 on failure (function failed or there are no more rows available). <br>
The result_handle is the same value that you returned from dbx_query.<br>
<a name="error"></a><pre class="code">
int <span class="fn-name">dbx_blabla_error</span>(zval **rv, zval **<span class="fn-param">dbx_handle</span>, INTERNAL_FUNCTION_PARAMETERS);
// int: returns 0 on failure and 1 on success
// returns error message as string
</pre>
dbx_blabla_error gets the error message from the last database call.<br>
What must be returned (in <span class="bold">rv</span>) is the error message as a string.<br>
What must be returned from the function is a 1 on success and a 0 on failure. <br>
The dbx_handle is the same value that you returned from dbx_blabla_connect or dbx_blabla_pconnect.<br>
</div>
<p>
<a href="#top">top</a><br>
<div class="newpage"></div>
<p>
<div class="text">
For specifics or the finer details you can always refer to dbx_mysql.c and dbx_odbc.c to see everything in action. <br>
More Zend API documentation can be found at <a href="http://www.zend.com/apidoc">http://www.zend.com/apidoc</a>.<br>
This document can be found at <a href="http://www.guidance.nl/php/dbx">http://www.guidance.nl/php/dbx</a>.<br>
</div>
<p>
<a href="#top">top</a><br>
<div class="newpage"></div>

</body>
</html>