line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Google::Fusion; |
2
|
1
|
|
|
1
|
|
21982
|
use 5.006; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
43
|
|
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
2315
|
use Moose; |
|
1
|
|
|
|
|
570267
|
|
|
1
|
|
|
|
|
9
|
|
5
|
1
|
|
|
1
|
|
7378
|
use Moose::Util::TypeConstraints; |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
12
|
|
6
|
1
|
|
|
1
|
|
3925
|
use MooseX::Params::Validate; |
|
1
|
|
|
|
|
18646
|
|
|
1
|
|
|
|
|
8
|
|
7
|
1
|
|
|
1
|
|
3790
|
use LWP::UserAgent; |
|
1
|
|
|
|
|
92246
|
|
|
1
|
|
|
|
|
56
|
|
8
|
1
|
|
|
1
|
|
15
|
use HTTP::Request; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
38
|
|
9
|
1
|
|
|
1
|
|
1995
|
use URL::Encode qw/url_encode/; |
|
1
|
|
|
|
|
22135
|
|
|
1
|
|
|
|
|
108
|
|
10
|
1
|
|
|
1
|
|
9754
|
use YAML qw/LoadFile DumpFile Dump/; |
|
1
|
|
|
|
|
21699
|
|
|
1
|
|
|
|
|
113
|
|
11
|
1
|
|
|
1
|
|
15
|
use Carp; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
71
|
|
12
|
1
|
|
|
1
|
|
1307
|
use Net::OAuth2::Moosey::Client; |
|
1
|
|
|
|
|
929588
|
|
|
1
|
|
|
|
|
58
|
|
13
|
1
|
|
|
1
|
|
873
|
use Google::Fusion::Result; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
49
|
|
14
|
1
|
|
|
1
|
|
2790
|
use Text::CSV; |
|
1
|
|
|
|
|
14364
|
|
|
1
|
|
|
|
|
9
|
|
15
|
1
|
|
|
1
|
|
52
|
use Time::HiRes qw/time/; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
17
|
|
16
|
1
|
|
|
1
|
|
246
|
use Try::Tiny; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
88
|
|
17
|
1
|
|
|
1
|
|
1672
|
use Digest::SHA qw/sha256_hex/; |
|
1
|
|
|
|
|
5087
|
|
|
1
|
|
|
|
|
134
|
|
18
|
1
|
|
|
1
|
|
1112
|
use File::Spec::Functions; |
|
1
|
|
|
|
|
1028
|
|
|
1
|
|
|
|
|
106
|
|
19
|
1
|
|
|
1
|
|
1682
|
use IO::String; |
|
1
|
|
|
|
|
2969
|
|
|
1
|
|
|
|
|
1598
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 NAME |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
Google::Fusion - Interface to the Google Fusion Tables API |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 VERSION |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
Version 0.10 |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=cut |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 SYNOPSIS |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
my $fusion = Google::Fusion->new( |
37
|
|
|
|
|
|
|
client_id => $client_id, |
38
|
|
|
|
|
|
|
client_secret => $client_secret, |
39
|
|
|
|
|
|
|
token_store => $token_store, |
40
|
|
|
|
|
|
|
); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# Get the result for a query |
43
|
|
|
|
|
|
|
my $result = $fusion->query( $sql ); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# Print out the rows returned |
46
|
|
|
|
|
|
|
foreach( @{ $result->rows } ){ |
47
|
|
|
|
|
|
|
print join( ',', @{ $_ } ) . "\n"; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 PARAMS/ACCESSORS |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
One of the following combination of parameters is required: |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
client_id and client_secret |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
You will be prompted with a URL, with which you will atain an access_code. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
client_id, client_secret, access_code |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
The OAuth2 client will complete the authorization process for you and get the refresh_token and access_token for you |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
refresh_token and optionally access_token |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
The OAuth2 client will get a valid access_token for you if necessary, and refresh it when necessary. |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
access_token |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
You will be able to make requests as long as the access_token is valid. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=over 2 |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=item * client_id <Str> |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
The client id of your application. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=item * client_secret <Str> |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
The secret for your application |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item * refresh_token <Str> |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Refresh token. |
83
|
|
|
|
|
|
|
Can be defined here, otherwise it will be aquired during the authorization process |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=item * access_token <Str> |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
A temporary access token aquired during the authorization process |
88
|
|
|
|
|
|
|
Can be defined here, otherwise it will be aquired during the authorization process |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=item * keep_alive <Int> |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Use keep_alive for connections - this will make the application /much/ more responsive. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Default: 1 |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=item * headers <Bool> |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
Responses passed with headers. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Default: 1 |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=item * access_code <Str> |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
The code returned during the OAuth2 authorization process with which access_token and refresh_token are aquired. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=item * auth_client |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
A Net::OAuth2::Moosey::Client object with which authenticated requests are made. If you are running |
109
|
|
|
|
|
|
|
in application mode (interactive), then you can accept the default. |
110
|
|
|
|
|
|
|
If you already have an authenticated client, then initialise with it. |
111
|
|
|
|
|
|
|
If you have some required parameters (access_token, refresh_token or access_code), but no client |
112
|
|
|
|
|
|
|
object yet, then just define these parameters, and allow the client to be created for you. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=item * query_cache <Str> |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Path to a directory to use as a query cache. This can be used to cache your results for blazing |
117
|
|
|
|
|
|
|
fast performance, and not actually hitting google for every query when testing, but should not |
118
|
|
|
|
|
|
|
be enabled in a productive environment otherwise you will have stale content. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=item * token_store <Str> |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Path to the token store file to store access/refresh tokens |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=back |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=cut |
127
|
|
|
|
|
|
|
subtype 'InsertStatement', |
128
|
|
|
|
|
|
|
as 'Str', |
129
|
|
|
|
|
|
|
where { $_ =~ m/^INSERT/ }; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
has 'client_id' => ( is => 'ro', isa => 'Str', ); |
132
|
|
|
|
|
|
|
has 'client_secret' => ( is => 'ro', isa => 'Str', ); |
133
|
|
|
|
|
|
|
has 'refresh_token' => ( is => 'ro', isa => 'Str', ); |
134
|
|
|
|
|
|
|
has 'access_token' => ( is => 'ro', isa => 'Str', ); |
135
|
|
|
|
|
|
|
has 'access_code' => ( is => 'ro', isa => 'Str', ); |
136
|
|
|
|
|
|
|
has 'query_cache' => ( is => 'ro', isa => 'Str', ); |
137
|
|
|
|
|
|
|
has 'token_store' => ( is => 'ro', isa => 'Str', ); |
138
|
|
|
|
|
|
|
has 'headers' => ( is => 'ro', isa => 'Bool', required => 1, default => 1, ); |
139
|
|
|
|
|
|
|
has 'keep_alive' => ( is => 'ro', isa => 'Bool', required => 1, default => 1, ); |
140
|
|
|
|
|
|
|
has 'auth_client' => ( is => 'ro', required => 1, lazy => 1, |
141
|
|
|
|
|
|
|
isa => 'Net::OAuth2::Moosey::Client', |
142
|
|
|
|
|
|
|
builder => '_build_auth_client', |
143
|
|
|
|
|
|
|
); |
144
|
|
|
|
|
|
|
has 'insert_buffer' => ( is => 'rw', isa => 'Str', clearer => 'clear_insert_buffer', default => sub{ '' } ); |
145
|
|
|
|
|
|
|
has 'insert_buffer_limit' => ( is => 'ro', isa => 'Int', default => 20_000 ); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Local method to build the auth_client if it wasn't passed |
148
|
|
|
|
|
|
|
sub _build_auth_client { |
149
|
0
|
|
|
0
|
|
|
my $self = shift; |
150
|
|
|
|
|
|
|
|
151
|
0
|
|
|
|
|
|
my %client_params = ( |
152
|
|
|
|
|
|
|
site_url_base => 'https://accounts.google.com/o/oauth2/auth', |
153
|
|
|
|
|
|
|
access_token_url_base => 'https://accounts.google.com/o/oauth2/token', |
154
|
|
|
|
|
|
|
authorize_url_base => 'https://accounts.google.com/o/oauth2/auth', |
155
|
|
|
|
|
|
|
scope => 'https://www.google.com/fusiontables/api/query', |
156
|
|
|
|
|
|
|
); |
157
|
0
|
|
|
|
|
|
foreach( qw/client_id client_secret refresh_token access_code access_token keep_alive token_store/ ){ |
158
|
0
|
0
|
|
|
|
|
$client_params{$_} = $self->$_ if defined $self->$_; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# $self->logger->debug( "Initialising Client with:\n". Dump( \%client_params ) ); |
162
|
0
|
|
|
|
|
|
my $client = Net::OAuth2::Moosey::Client->new( %client_params ); |
163
|
0
|
|
|
|
|
|
return $client; |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=head1 METHODS |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head2 query |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Submit a (Googley) SQL query. Single argument is the SQL. |
171
|
|
|
|
|
|
|
Return is a L<Google::Fusion::Result> object |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Example: |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
my $result = $fusion->query( 'SELECT * FROM 123456' ); |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=cut |
178
|
|
|
|
|
|
|
sub query { |
179
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
180
|
0
|
|
|
|
|
|
my $sql = shift; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# Get a valid access_token before timing the query time |
183
|
0
|
|
|
|
|
|
my $auth_time_start = time(); |
184
|
0
|
|
|
|
|
|
$self->auth_client->access_token_object->valid_access_token(); |
185
|
0
|
|
|
|
|
|
my $auth_time = time() - $auth_time_start; |
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
|
my $query_start = time(); |
188
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
|
my $response = $self->_query_or_cache( $sql ); |
190
|
|
|
|
|
|
|
|
191
|
0
|
|
|
|
|
|
my $query_time = time() - $query_start; |
192
|
0
|
|
|
|
|
|
my $result = Google::Fusion::Result->new( |
193
|
|
|
|
|
|
|
query => $sql, |
194
|
|
|
|
|
|
|
response => $response, |
195
|
|
|
|
|
|
|
query_time => $query_time, |
196
|
|
|
|
|
|
|
auth_time => $auth_time, |
197
|
|
|
|
|
|
|
total_time => $query_time + $auth_time, |
198
|
|
|
|
|
|
|
); |
199
|
|
|
|
|
|
|
|
200
|
0
|
0
|
|
|
|
|
if( not $response->is_success ){ |
201
|
0
|
|
|
|
|
|
$result->error( sprintf "%s (%u)", $response->message, $response->code ); |
202
|
|
|
|
|
|
|
}else{ |
203
|
|
|
|
|
|
|
# Response was a success |
204
|
|
|
|
|
|
|
# TODO: RCL 2011-09-08 Parse the actual error message from the response |
205
|
|
|
|
|
|
|
# TODO: RCL 2011-09-08 Refresh access_key if it was invalid, or move that |
206
|
|
|
|
|
|
|
# action to the Client? |
207
|
|
|
|
|
|
|
|
208
|
0
|
|
|
|
|
|
my $data = $response->decoded_content(); |
209
|
|
|
|
|
|
|
# print $data; |
210
|
0
|
0
|
|
|
|
|
my $csv = Text::CSV->new ( { |
211
|
|
|
|
|
|
|
binary => 1, # Reliable handling of UTF8 characters |
212
|
|
|
|
|
|
|
escape_char => '"', |
213
|
|
|
|
|
|
|
quote_char => '"', |
214
|
|
|
|
|
|
|
} ) or croak( "Cannot use CSV: ".Text::CSV->error_diag () ); |
215
|
0
|
|
|
|
|
|
my $io = IO::String->new( $data ); |
216
|
0
|
|
|
|
|
|
my $parsed_data = $csv->getline_all( $io ); |
217
|
0
|
0
|
|
|
|
|
$csv->eof or $csv->error_diag(); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Find the max length of each column |
221
|
|
|
|
|
|
|
# TODO: RCL 2011-09-09 This won't handle elements with newlines gracefully... |
222
|
0
|
|
|
|
|
|
my @max; |
223
|
0
|
|
|
|
|
|
foreach my $row_idx( 0 .. scalar( @{ $parsed_data } ) - 1 ){ |
|
0
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
|
foreach my $col_idx ( 0 .. scalar( @{ $parsed_data->[0] } ) - 1 ){ |
|
0
|
|
|
|
|
|
|
225
|
0
|
0
|
0
|
|
|
|
if( ( not $max[$col_idx] ) or ( length( $parsed_data->[$row_idx][$col_idx] ) > $max[$col_idx] ) ){ |
226
|
0
|
|
|
|
|
|
$max[$col_idx] = length( $parsed_data->[$row_idx][$col_idx] ); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
|
232
|
0
|
0
|
|
|
|
|
if( $self->headers ){ |
233
|
0
|
|
|
|
|
|
$result->columns( shift( @{ $parsed_data } ) ); |
|
0
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
} |
235
|
0
|
|
|
|
|
|
$result->rows( $parsed_data ); |
236
|
0
|
|
|
|
|
|
$result->has_headers( $self->headers ); |
237
|
0
|
0
|
|
|
|
|
if( not $result->num_columns ){ |
238
|
0
|
|
|
|
|
|
$result->num_columns( scalar( @{ $parsed_data->[0] } ) ); |
|
0
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
} |
240
|
0
|
|
|
|
|
|
$result->max_lengths( \@max ); |
241
|
0
|
|
|
|
|
|
$result->has_headers( $self->headers ); |
242
|
|
|
|
|
|
|
} |
243
|
0
|
|
|
|
|
|
return $result; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=head2 get_fresh_access_token |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
Force the OAuth access token to be refreshed |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
Example: |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
$fusion->get_fresh_access_token(); |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=cut |
255
|
|
|
|
|
|
|
sub get_fresh_access_token { |
256
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
257
|
0
|
|
|
|
|
|
$self->auth_client->get_fresh_access_token(); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=head2 add_to_insert_buffer |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
The Fusion Table intefrace allows multiple INSERT commands to be sent in one query. |
264
|
|
|
|
|
|
|
This can be used similarly to the query method, but and will reduce the number of queries |
265
|
|
|
|
|
|
|
you use. |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
# Get data from your local database |
269
|
|
|
|
|
|
|
$sth->execute(); |
270
|
|
|
|
|
|
|
while( my $row = $sth->fetchrow_hashref ){ |
271
|
|
|
|
|
|
|
$fusion->add_to_insert_buffer( sprintf( "INSERT INTO INTO 12345 ( Id, Text ) VALUES( '%s', '%s' )", $row->{Id}, $row->{Text} ) ); |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
$fusion->send_insert_buffer; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Obviously this can be further optimised by having many VALUES per INSERT. |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
If a send_insert_buffer was triggered during the add, this is returned, otherwise undef is returned |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=cut |
280
|
|
|
|
|
|
|
sub add_to_insert_buffer { |
281
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
282
|
0
|
|
|
|
|
|
my ( $sql ) = pos_validated_list( |
283
|
|
|
|
|
|
|
\@_, |
284
|
|
|
|
|
|
|
{ isa => 'InsertStatement' }, |
285
|
|
|
|
|
|
|
); |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# Make sure there is a newline after the new SQL |
288
|
0
|
|
|
|
|
|
$sql =~ s/^(.*);?\s*/$1;\n/s; |
289
|
|
|
|
|
|
|
|
290
|
0
|
|
|
|
|
|
my $rtn = undef; |
291
|
|
|
|
|
|
|
# Send the buffer if it is already full |
292
|
0
|
0
|
|
|
|
|
if( ( length( $sql ) + length( $self->insert_buffer ) ) > $self->insert_buffer_limit ){ |
293
|
0
|
|
|
|
|
|
$rtn = $self->send_insert_buffer; |
294
|
|
|
|
|
|
|
} |
295
|
0
|
|
|
|
|
|
$self->insert_buffer( $self->insert_buffer . $sql ); |
296
|
0
|
|
|
|
|
|
return $rtn; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=head2 send_insert_buffer |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
Flush the rest of the insert buffer |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=cut |
304
|
|
|
|
|
|
|
sub send_insert_buffer { |
305
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
306
|
0
|
|
|
|
|
|
my $sql = $self->insert_buffer; |
307
|
0
|
|
|
|
|
|
$self->clear_insert_buffer; |
308
|
0
|
|
|
|
|
|
return $self->query( $sql ); |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# Private method to use cached queries if possible (and desired - the query_cache is defined) |
312
|
|
|
|
|
|
|
sub _query_or_cache { |
313
|
0
|
|
|
0
|
|
|
my $self = shift; |
314
|
0
|
|
|
|
|
|
my $sql = shift; |
315
|
0
|
|
|
|
|
|
my $digest = sha256_hex( $sql ); |
316
|
|
|
|
|
|
|
# printf "Digest: %s\n", $digest; |
317
|
0
|
|
|
|
|
|
my $cache_file = catfile( $self->query_cache, $digest ); |
318
|
|
|
|
|
|
|
|
319
|
0
|
|
|
|
|
|
my $response = undef; |
320
|
0
|
0
|
|
|
|
|
if( $self->query_cache ){ |
321
|
0
|
0
|
|
|
|
|
if( -f $cache_file ){ |
322
|
0
|
|
|
|
|
|
$response = LoadFile( $cache_file ); |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
} |
325
|
0
|
0
|
|
|
|
|
if( not $response ){ |
326
|
0
|
0
|
|
|
|
|
my @post_args = ( 'https://www.google.com/fusiontables/api/query', |
327
|
|
|
|
|
|
|
HTTP::Headers->new( Content_Type => 'application/x-www-form-urlencoded' ), |
328
|
|
|
|
|
|
|
sprintf( 'sql=%s&hdrs=%s', |
329
|
|
|
|
|
|
|
url_encode( $sql ), |
330
|
|
|
|
|
|
|
( $self->headers ? 'true' : 'false' ), |
331
|
|
|
|
|
|
|
), |
332
|
|
|
|
|
|
|
); |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
|
335
|
0
|
|
|
|
|
|
$response = $self->auth_client->post( @post_args ); |
336
|
|
|
|
|
|
|
# If the response was not Unauthorized, most likely is that the token is invalid |
337
|
|
|
|
|
|
|
# Invalidate the current token, and try again |
338
|
0
|
0
|
0
|
|
|
|
if( $response->code == 401 and $response->message eq 'Unauthorized' ){ |
339
|
|
|
|
|
|
|
# Make the token expire, so a new one is requested |
340
|
0
|
|
|
|
|
|
$self->auth_client->get_fresh_access_token(); |
341
|
0
|
|
|
|
|
|
$response = $self->auth_client->post( @post_args ); |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
0
|
0
|
|
|
|
|
if( $self->query_cache ){ |
345
|
0
|
|
|
|
|
|
DumpFile( $cache_file, $response ); |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
} |
348
|
0
|
|
|
|
|
|
return $response; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=head1 AUTHOR |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
Robin Clarke, C<< <perl at robinclarke.net> >> |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=head1 BUGS |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
Please report any bugs or feature requests to C<bug-google-fusion at rt.cpan.org>, or through |
359
|
|
|
|
|
|
|
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Google-Fusion>. I will be notified, and then you'll |
360
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=head1 SUPPORT |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
perldoc Google::Fusion |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
You can also look for information at: |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=over 4 |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item * Repository on Github |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
L<https://github.com/robin13/Google-Fusion> |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Google-Fusion> |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Google-Fusion> |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=item * CPAN Ratings |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/Google-Fusion> |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=item * Search CPAN |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
L<http://search.cpan.org/dist/Google-Fusion/> |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=back |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
Copyright 2011 Robin Clarke. |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
404
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
405
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=cut |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
1; # End of Google::Fusion |