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 |
} |