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