ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-DBI/t/fake-mysql
Revision: 1.1
Committed: Tue Jun 2 16:16:03 2009 UTC (14 years, 11 months ago) by root
Branch: MAIN
CVS Tags: rel-3_0, rel-2_2, rel-2_3, rel-2_0, rel-2_1, rel-3_01, rel-3_02, rel-3_03, rel-3_04, HEAD
Log Message:
big patch by adam

File Contents

# Content
1 #!/usr/bin/perl
2
3 =pod
4
5 =head1 SYNOPSIS
6
7 perl examples/myserver.pl --config=examples/myserver.conf --port=1234 --dsn="dbi:mysql:"
8
9 mysql -h127.0.0.1 -P1234 -umyuser -e 'info'
10
11 =head1 DESCRIPTION
12
13 This is a simple server that listens for incoming connections from MySQL
14 clients or connectors.
15
16 Each query received is processed according to a set of configuration files,
17 which can rewrite the query, forward it to a DBI handle or construct a response
18 or a result set on the fly from any data.
19
20 =head1 COMMAND LINE OPTIONS
21
22 C<--port=XXXX> - port to listen on. Default is C<23306>, which is the default
23 MySQL port with a 2 in front.
24
25 C<--interface=AAA.BBB.CCC.DDD> - interface to listen to. Default is
26 C<127.0.0.1> which means that only connections from the localhost will be
27 accepted. To enable connections from the outside use C<--interface=0.0.0.0>. In
28 this case, please make sure you have some other form of access protection,
29 e.g. like the first rule in the C<myserver.conf> example configuration file.
30
31 C<--config=config.file> - a configuration file containing rules to be
32 executed. The option can be specified multiple times and the rules will be
33 checked in the order specified.
34
35 C<--dsn> - specifies a L<DBI> DSN. All queries that did not match a rule or
36 where the rule rewrote the query or did not return any response or a result set
37 on its own will be forwarded to that database. Individual rules can forward
38 specific queries to specific DSNs. If you do not want non-matching queries to
39 be forwarded, either create a match-all rule at the bottom of your last
40 configuration file or omit the C<--dsn> option. If you omit the option, an
41 error message will be sent to the client.
42
43 C<--dsn_user> and C<--dsn_password> can be used to specify username and
44 password for DBI drivers where those can not be specified in the DSN string.
45
46 =head1 RULES
47
48 Rules to be executed are contained in configuration files. The configuration
49 files are actually standard perl scripts and are executed as perl
50 subroutines. Therefore, they can contain any perl code -- the only requirement
51 is that the last statement in the file (that is, the return value of the file)
52 is an array containing the rules to be executed.
53
54 The actions from a rule will be executed for all queries that match a specific
55 pattern. Rules are processed in order and processing is terminated at the first
56 rule that returns some data to the client. This allows you to rewrite a query
57 numerous times and have a final default rule that forwards the query to another
58 server. If C<forward> is defined, further rules are not processed.
59
60 Each rule can have the following attributes:
61
62 C<command> The rule will match if the MySQL command issued by the client
63 matches C<command>. C<command> can either be an integer from the list found at
64 C<DBIx::MyServer.pm> or a reference to a C<SUB> that returns such an
65 integer. This is mainly useful for processing incoming C<COM_PING>,
66 C<COM_INIT_DB> and C<COM_FIELD_LIST>.
67
68 C<match> The rule will match if the test of the query matches a regular
69 expression or is identical to a string. C<match> can also be a reference to a
70 C<SUB> in which case the sub is executed and can either return a string or a
71 regular expression.
72
73 If both C<command> and C<match> are specified, both must match for the rule to
74 be executed.
75
76 C<dbh> if specified, any matching query will be forwarded to this database
77 handle (possibly after a C<rewrite>), rather than the default handle specifeid
78 on the command line.
79
80 C<dsn> behaves identically, however a database handle is contructed from the
81 C<dsn> provided and an attempt is made to connect to the database. If C<dsn> is
82 a reference to an array, the first item from the array is used as a DSN, the
83 second one is used as username and the third one is used as password.
84
85 C<before> this can be a reference to a subroutine that will be called after a
86 matching query has been encountered but before any further processing has taken
87 place. The subroutine will be called with the text of the query as the first
88 argument, followed by extra arguments containing the strings matched from any
89 parenthesis found in the C<match> regular expression. You can use C<before> to
90 execute any extra queries before the main query, such as C<EXPLAIN>. The return
91 value from the C<before> subroutine is discarded and is not used.
92
93 C<rewrite> is a string that will replace the original query that matches the
94 rule, or a reference to a subroutine that will produce such a string. If
95 C<rewrite> is not defined, and C<match> was a string, the query is passed along
96 unchanged. If C<match> was a regular expression, the string matched by the
97 first set of parenthesis is used. This way, if the rule does not specify any
98 C<data>, C<columns>, C<error> or C<ok> clauses, but a valid DBI handle is
99 defined, the query will be forwarded to that handle automatically.
100
101 C<error> can be either an array reference containing three arguments for
102 C<DBIx::MyServer::sendError()> or a reference to a subroutine returning such an
103 array (or array reference). If this is the case, the error message will be sent
104 to the client. If C<error> is not defined or the subroutine returns C<undef>,
105 no error message will be sent. In this case, you need to send at some point
106 either an C<ok> or a result set, otherwise the client will hang forever waiting
107 for a response.
108
109 C<ok> behaves identically to C<error> -- if it is defined or points to a
110 subroutine which, when called, returns a true value, an OK response will be
111 sent to the client. C<ok> can also be a reference to an array, or the
112 subroutine can return such an array -- in this case the first item is the
113 message to be sent to the client, the second one is the number of affected
114 rows, the third is the insert_id and the last one is the warning count.
115
116 C<columns> must contain either an array reference or a reference to a
117 subroutine which returns and array or array reference. The column names from
118 the array will be sent to the client. By default, all columns are defined as
119 C<MYSQL_TYPE_STRING>.
120
121 C<data> must contain either a reference to the data to be returned to the
122 client or a reference to subroutine that will produce the data. "Data" can be a
123 reference to a C<HASH>, in which case the hash will be sent with the key names
124 in the first column and the key values in the second. It can be a flat array,
125 in which case the array items will be sent as a single column, or it can be a
126 reference to a nested array, with each sub-array being a single row from the
127 response.
128
129 C<after> is called after all other parts of the rule have been processed.
130
131 C<forward> if defined, the query will be immediately forwarded to the server
132 and no further rules will be processed.
133
134 All subroutine references that are called will have the text of the query
135 passed as the first argument and the subsequent arguments will be any strings
136 matched by parenthesis in the C<match> regular expression.
137
138 =head1 VARIABLES
139
140 Your code in the configuration file can save and retrieve state by using
141 C<get($variable)> and C<set($variable, $value)>. State is retained as long as
142 the connection is open. Each new connection starts with a clean state. The
143 following variables are maintained by the system:
144
145 C<myserver> contains a reference to the L<DBIx::MyServer> object being used to
146 service the connection. You can use this to inject data and packets directly
147 into the network stream.
148
149 C<username> contains the username provided by the client at connection
150 establishment.
151
152 C<database> contains the database requested by the client at connection
153 establishment. By default, C<myserver.pl> will not automatically handle any
154 database changes requested by the client. You are responsible for handling
155 those either by responding with a simple OK or by updating the variables.
156
157 C<remote_host> contains the IP of the client.
158
159 C<dbh> and C<dsn> will contain a reference to the default DBI handle and the
160 DSN string it was produced from, as taken from the command line. Even if a
161 specific rule has its own C<dsn>, the value of those variables will always
162 refer to the default C<dbh> and C<dsn>. If you change the <dsn> variable, the
163 system will attempt to connect to the new dsn string and will produce a new
164 C<dbh> handle from it. If you set C<dsn> to an array reference, the first item
165 will be used as a DSN, the second one as a username and the third one as a
166 password. C<dsn_user> and C<dsn_password> can be used for the same purpose.
167
168 C<args> contains a reference to the C<@ARGV> array, that is, the command line
169 options that evoked myserver.pl
170
171 C<remote_dsn>, C<remote_dsn_user> and C<remote_dsn_password> are convenience
172 variables that can also be specified on the command line. It is not used by
173 C<myserver.pl> however you can use it in your rules, the way
174 C<remotequery.conf> does.
175
176 =head1 SECURITY
177
178 By default the script will only accept incoming connections from the local
179 host. If you relax that via the C<--interface> command-line option, all
180 connections will be accepted. However, once the connection has been
181 established, you can implement access control as demonstrated in the first rule
182 of the C<myserver.conf> file -- it returns "Access denied" for every query
183 unless the username is "myuser". Future versions of the script will allow
184 connections to be rejected during handshake.
185
186 =head1 SAMPLE RULES
187
188 The following rule sets are provided in the C<examples/> directory.
189
190 =head2 Simple examples - myserver.conf
191
192 This configuration provides some simple query rewriting examples as suggested
193 by Giuseppe Maxia and Jan Kneschke, e.g. commands like C<ls>, C<cd> as well as
194 fixing spelling mistakes. In addition, some very simple access control is
195 demonstrated at the top of the file.
196
197 =head2 Remote queries - remotequery.conf
198
199 This rule set implements a C<SELECT REMOTE select_query ON 'dsn'> operator
200 which will execute the query on <dsn> specified, bring the results back into a
201 temporary table on the default server and substitute the C<REMOTE_SELECT> part
202 in the orignal query with a reference to the temoporary table. The following
203 scenarios are possible:
204
205 # Standalone usage
206 mysql> SELECT REMOTE * FROM mysql.user ON 'dbi:mysql:host=remote:user=foo:password=bar'
207
208 # CREATE ... SELECT usage
209 mysql> CREATE TABLE local_table SELECT REMOTE * FROM remote_table ON 'dbi:mysql:host=remote'
210
211 # Non-correlated subquery
212 mysql> SELECT *
213 mysql> FROM (SELECT REMOTE * FROM mysql_user ON 'dbi:mysql:host=remote:user=foo:password=bar')
214 mysql> WHERE user = 'mojo'
215
216 # Specify remote dsn on the command line
217 shell> ./myserver.pl --config=remotequery.conf --dsn=dbi:mysql: --remote_dsn=dbi:mysql:host=host2
218 mysql> select remote 1;
219
220 # Specify remote dsn as variable
221
222 mysql> set @@@remote_dsn=dbi:mysql:host=host2
223 mysql> select remote NOW();
224
225 mysql> set @@@devel_dsn=dbi:mysql:host=dev3
226 mysql> select remote NOW() ON @@@devel_dsn;
227
228 This is different from using the Federated storage handler because the entire
229 C<REMOTE_SELECT> query is executed on the remote server and only the result is
230 sent back to the default server for further processing. This is useful if a lot
231 of processing is to be done on the remote server -- the Federated engine will
232 bring most of the data to the connecting server and will process it there,
233 which can potentially be very time consuming.
234
235 Please note that since a temporary table is created and it must reside
236 somewhere, you need to be in a working database on the default
237 server. Updateable C<VIEW>a are not supported.
238
239 =head2 Development support - devel.conf
240
241 This configuration provides the following operators:
242
243 C<shell> - can be used to execute shell commands, e.g. C<shell ls -la>.
244
245 C<env> - returns the operating environment of C<myserver.pl>.
246
247 C<stats> - executes C<SHOW STATUS> before and after each query and returns the
248 difference. First you execute
249
250 C<stats select a from b> and then C<show stats>.
251
252 C<devel> - can be used to send specfic queries to a different server. You can
253 execute a single query as
254
255 C<devel select a from b> or use a standalone C<devel> to redirect all future
256 queries until you issue C<restore>.
257
258 You specify the server to send "development" queries to via C<set('devel_dsn')>
259 at the top of C<devel.conf>
260
261 =head2 ODBC compatibility - odbc.conf
262
263 The C<odbc.conf> contains an example on how to unintelligently answer generic
264 queries sent by the MySQL ODBC driver and the applications that use it, up to
265 the point where real data can be sent over the connection and imported into the
266 client application.
267
268 =cut
269
270 use strict;
271 use Socket;
272 use DBI;
273 use DBIx::MyServer;
274 use DBIx::MyServer::DBI;
275 use Getopt::Long qw(:config pass_through);
276
277 $SIG{CHLD} = 'IGNORE';
278
279 my $start_dsn;
280 my $start_dsn_user;
281 my $start_dsn_password;
282
283 my $remote_dsn;
284 my $remote_dsn_user;
285 my $remote_dsn_password;
286
287 my $port = '23306';
288 my $interface = '127.0.0.1';
289 my $debug;
290 my @config_names;
291 my @rules;
292 my %storage;
293
294 my @args = @ARGV;
295
296 my $result = GetOptions(
297 "dsn=s" => \$start_dsn,
298 "dsn_user=s" => \$start_dsn_user,
299 "dsn_password=s" => \$start_dsn_password,
300 "remote_dsn=s" => \$remote_dsn,
301 "remote_dsn_user=s" => \$remote_dsn_user,
302 "remote_dsn_password=s" => \$remote_dsn_password,
303 "port=i" => \$port,
304 "config=s" => \@config_names,
305 "if|interface|ip=s" => \$interface,
306 "debug" => \$debug
307 ) or die;
308
309 @ARGV = @args;
310
311 my $start_dbh;
312 if (defined $start_dsn) {
313 print localtime()." [$$] Connecting to DSN $start_dsn.\n" if $debug;
314 $start_dbh = DBI->connect($start_dsn, $start_dsn_user, $start_dsn_password);
315 }
316
317 $storage{dbh} = $start_dbh;
318 $storage{dsn} = $start_dsn;
319 $storage{dsn_user} = $start_dsn_user;
320 $storage{dsn_password} = $start_dsn_password;
321
322 $storage{remote_dsn} = $remote_dsn;
323 $storage{remote_dsn_user} = $remote_dsn_user;
324 $storage{remote_dsn_password} = $remote_dsn_password;
325
326 foreach my $config_name (@config_names) {
327 my $config_sub;
328 open (CONFIG_FILE, $config_name) or die "unable to open $config_name: $!";
329 read (CONFIG_FILE, my $config_text, -s $config_name);
330 close (CONFIG_FILE);
331 eval ('$config_sub = sub { '.$config_text.'}') or die $@;
332 my @config_rules = &$config_sub();
333 push @rules, @config_rules;
334 print localtime()." [$$] Loaded ".($#config_rules + 1)." rules from $config_name.\n" if $debug;
335 }
336
337 socket(SERVER_SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
338 setsockopt(SERVER_SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
339 bind(SERVER_SOCK, sockaddr_in($port, inet_aton($interface))) || die "bind: $!";
340 listen(SERVER_SOCK,1);
341
342 print localtime()." [$$] Note: port $port is now open on interface $interface.\n" if $debug;
343 while (1) {
344 my $remote_paddr = accept(my $remote_socket, SERVER_SOCK);
345
346 if (!defined(my $pid = fork)) {
347 die "cannot fork: $!";
348 } elsif ($pid) {
349 next;
350 }
351
352 $storage{dbh} = $start_dbh->clone() if defined $start_dbh;
353 $storage{dsn} = $start_dsn;
354 $storage{args}= \@ARGV;
355
356 my $dbh = get('dbh');
357 my $myserver = DBIx::MyServer::DBI->new(
358 socket => $remote_socket,
359 dbh => $dbh,
360 banner => $0.' '.join(' ', @ARGV)
361 );
362 set('myserver', $myserver);
363
364 $myserver->sendServerHello();
365 my ($username, $database) = $myserver->readClientHello();
366 set('username', $username); set('database', $database);
367
368 eval {
369 my $hersockaddr = getpeername($myserver->getSocket());
370 my ($port, $iaddr) = sockaddr_in($hersockaddr);
371 my $remote_host = inet_ntoa($iaddr);
372 set('remote_host', $remote_host);
373 };
374
375 $myserver->sendOK();
376
377 while (1) {
378 my ($command, $query) = $myserver->readCommand();
379 print localtime()." [$$] command: $command; data = $query\n" if $debug;
380 last if (not defined $command) || ($command == DBIx::MyServer::COM_QUIT);
381
382 my $outgoing_query = $query;
383
384 foreach my $i (0..$#rules) {
385
386 my $rule = $rules[$i];
387 my $rule_matches = 0;
388
389 my @placeholders;
390
391 if (defined $rule->{command}) {
392 if ($command == $rule->{command}) {
393 $rule_matches = 1;
394 } else {
395 next;
396 }
397 }
398
399 my $match_type = ref($rule->{match});
400 if (defined $rule->{match}) {
401 $rule->{match_string} = $match_type eq 'CODE' ? $rule->{match}($query) : $rule->{match};
402 if (ref($rule->{match_string}) eq 'Regexp') {
403 $rule_matches = 1 if @placeholders = $query =~ $rule->{match};
404 } else {
405 $rule_matches = 1 if $query eq $rule->{match_string};
406 }
407 print localtime()." [$$] Executing 'match' from rule $i: $rule->{match_string}, result is $rule_matches.\n" if $debug;
408 } else {
409 $rule_matches = 1;
410 }
411 $rule->{placeholders} = \@placeholders;
412
413 next if $rule_matches == 0;
414
415 my ($definitions, $data);
416
417 undef $storage{data_sent};
418
419 if (defined $rule->{before}) {
420 print localtime()." [$$] Executing 'before' from rule $i\n" if $debug;
421 eval{
422 $rule->{before}($query, @{$rule->{placeholders}});
423 };
424 error($@) if defined $@ && $@ ne '';
425 }
426
427 if (defined $rule->{rewrite}) {
428 if (ref($rule->{rewrite}) eq 'CODE') {
429 $outgoing_query = $rule->{rewrite}($query, @{$rule->{placeholders}});
430 } else {
431 $outgoing_query = $rule->{rewrite};
432 }
433 print localtime()." [$$] Executing 'rewrite' from rule $i, result is '$outgoing_query'\n" if $debug;
434 } elsif (defined $rule->{match}) {
435 $outgoing_query = $rule->{match_string} eq 'Regexp' ? $rule->{placeholders}->[0] : $outgoing_query;
436 }
437
438 if (defined $rule->{error}) {
439 my @error = ref ($rule->{error}) eq 'CODE' ? $rule->{error}($query, @{$rule->{placeholders}}) : $rule->{error};
440 my @mid_error = ref($error[0]) eq 'ARRAY' ? @{$error[0]} : @error;
441 if (defined $mid_error[0]) {
442 print localtime()." [$$] Sending error: ".join(', ', @mid_error).".\n" if $debug;
443 error(@mid_error);
444 }
445 }
446
447 if (defined $rule->{ok}) {
448 my @ok = ref ($rule->{ok}) eq 'CODE' ? $rule->{ok}($query, @{$rule->{placeholders}}) : $rule->{ok};
449 my @mid_ok = ref($ok[0]) eq 'ARRAY' ? @{$ok[0]} : @ok;
450 if (defined $mid_ok[0]) {
451 print localtime()." [$$] Sending OK: ".join(', ', @mid_ok).").\n" if $debug;
452 ok(@mid_ok);
453 }
454 }
455
456 if (defined $rule->{columns}) {
457 my @column_names = ref($rule->{columns}) eq 'CODE' ? $rule->{columns}($query, @{$rule->{placeholders}}) : $rule->{columns};
458 my $column_names;
459 if (defined $column_names[1]) {
460 $column_names = \@column_names;
461 } elsif (ref($column_names[0]) eq 'ARRAY') {
462 $column_names = $column_names[0];
463 } elsif (defined $column_names[0]) {
464 $column_names = [ $column_names[0] ];
465 }
466 print localtime()." [$$] Converting column_names into definitions.\n" if $debug;
467 $definitions = [ map { $myserver->newDefinition( name => $_ ) } @$column_names ];
468 }
469
470 if (defined $rule->{data}) {
471 my @start_data = ref($rule->{data}) eq 'CODE' ? $rule->{data}($query, @{$rule->{placeholders}}) : $rule->{data};
472 my $mid_data = defined $start_data[1] ? \@start_data : $start_data[0];
473
474 if (ref($mid_data) eq 'HASH') {
475 print localtime()." [$$] Converting data from hash.\n" if $debug;
476 $data = [ map { [ $_, $mid_data->{$_} ] } sort keys %$mid_data ];
477 } elsif ((ref($mid_data) eq 'ARRAY') && (ref($mid_data->[0]) ne 'ARRAY')) {
478 print localtime()." [$$] Converting data from a flat array.\n" if $debug;
479 $data = [ map { [ $_ ] } @$mid_data ];
480 } elsif (ref($mid_data) eq '') {
481 $data = [ [ $mid_data ] ];
482 } else {
483 $data = $mid_data;
484 }
485 }
486
487 if (
488 (not defined $storage{data_sent}) && (not defined $definitions) && (not defined $data) &&
489 ( ($i == $#rules) || (defined $rule->{dbh}) || (defined $rule->{forward}) )
490 ) {
491 if (defined $rule->{dbh}) {
492 $myserver->setDbh($rule->{dbh});
493 } elsif (defined $rule->{dsn}) {
494 if (ref($rule->{dsn}) eq 'ARRAY') {
495 print localtime()." [$$] Connecting to DSN $rule->{dsn}->[0].\n" if $debug;
496 $myserver->setDbh(DBI->connect(@{$rule->{dsn}}));
497 } else {
498 print localtime()." [$$] Connecting to DSN $rule->{dsn}.\n" if $debug;
499 $myserver->setDbh(DBI->connect($rule->{dsn}, get('dsn_user'), get('dsn_password')));
500 }
501 }
502 if (not defined get('dbh')) {
503 error("No --dbh specified. Can not forward query.",1235, 42000);
504 } elsif ($command == DBIx::MyServer::COM_QUERY) {
505 (my $foo, $definitions, $data) = $myserver->comQuery($outgoing_query);
506 } elsif ($command == DBIx::MyServer::COM_INIT_DB) {
507 (my $foo, $definitions, $data) = $myserver->comInitDb($outgoing_query);
508 } else {
509 error("Don't know how to handle command $command.",1235, 42000);
510 }
511 $storage{data_sent} = 1;
512 }
513
514 if (defined $definitions) {
515 print localtime()." [$$] Sending definitions.\n" if $debug;
516 $myserver->sendDefinitions($definitions);
517 $storage{data_sent} = 1;
518 }
519
520 if (defined $data) {
521 print localtime()." [$$] Sending data.\n" if $debug;
522 $myserver->sendRows($data);
523 $storage{data_sent} = 1;
524 }
525
526 if (defined $rule->{after}) {
527 print localtime()." [$$] Executing 'after' for rule $i\n" if $debug;
528 $rule->{after}($query, @{$rule->{placeholders}})
529 }
530
531 last if defined $storage{data_sent};
532 }
533
534 }
535
536 print localtime()." [$$] Exit.\n" if $debug;
537 exit;
538 }
539
540 sub set {
541 my ($name, $value) = @_;
542 $storage{$name} = $value;
543 if ($name eq 'dsn') {
544 if (defined $value) {
545 my $dbh;
546 if (ref($value) eq 'ARRAY') {
547 print localtime()." [$$] Connecting to DSN $value->[0].\n" if $debug;
548 $dbh = DBI->connect(@{$value});
549 } else {
550 print localtime()." [$$] Connecting to DSN $value.\n" if $debug;
551 $dbh = DBI->connect($value, get('dsn_user'), get('dsn_password'));
552 }
553 $storage{myserver}->setDbh($dbh);
554 $storage{dbh} = $dbh;
555 } else {
556 $storage{myserver}->setDbh(undef);
557 $storage{dbh} = undef;
558 }
559 }
560 return 1;
561 }
562
563 sub error {
564 my $myserver = get('myserver');
565 $myserver->sendError(@_);
566 $storage{data_sent} = 1;
567 }
568
569 sub error_dbi {
570 my $myserver = get ('myserver');
571 my $dbh = $_[0] || get ('dbh');
572 $myserver->sendErrorFromDBI($dbh);
573 $storage{data_sent} = 1;
574 }
575
576 sub ok {
577 my $myserver = get('myserver');
578 if ($_[0] == 1) {
579 $myserver->sendOK();
580 } else {
581 $myserver->sendOK(@_);
582 }
583 $storage{data_sent} = 1;
584 }
585
586 sub disconnect { exit; }
587
588 sub get {
589 return $storage{$_[0]};
590 }