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