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 (15 years 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

# User Rev Content
1 root 1.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     }