File Coverage

blib/lib/Geo/GeoNames/DB/SQLite.pm
Criterion Covered Total %
statement 75 85 88.2
branch 13 20 65.0
condition 1 3 33.3
subroutine 15 17 88.2
pod 5 5 100.0
total 109 130 83.8


line stmt bran cond sub pod time code
1             package Geo::GeoNames::DB::SQLite;
2              
3             =head1 NAME
4              
5             Geo::GeoNames::DB::SQLite - Perl module for handling GeoNames.org data stored in a SQLite database.
6              
7             =head1 SYNOPSIS
8              
9             use Geo::GeoNames::DB::SQLite;
10              
11             my $dbh = Geo::GeoNames::DB::SQLite->connect( "geoname.sqlite" );
12              
13             my @records = $dbh->query( "Beijing" );
14              
15             print join( "\n", @records ) . "\n";
16              
17             =head1 DESCRIPTION
18              
19             Geo::GeoNames::DB::SQLite is a Perl module to store GeoNames.org records,
20             which tries to balance the trade-offs between the memory cost of using a
21             Perl hash of Geo::GeoNames::Record objects and the speed of using using a
22             GeoNames.org data file.
23              
24             =head1 AUTHOR
25              
26             Xiangrui Meng
27              
28             =head1 COPYRIGHT
29              
30             Copyright (C) 2010 by Xiangrui Meng
31              
32             This library is free software; you can redistribute it and/or modify
33             it under the same terms as Perl itself, either Perl version 5.8.8 or,
34             at your option, any later version of Perl 5 you may have available.
35              
36             =cut
37              
38 1     1   20743 use 5.008007;
  1         4  
  1         33  
39 1     1   5 use strict;
  1         1  
  1         30  
40 1     1   4 use warnings;
  1         6  
  1         30  
41              
42 1     1   5 use Carp ();
  1         1  
  1         30  
43 1     1   10610 use Data::Dumper ();
  1         15713  
  1         29  
44              
45 1     1   10916 use DBI;
  1         31969  
  1         81  
46              
47 1     1   698 use Geo::GeoNames::Record;
  1         4  
  1         54  
48              
49 1     1   6 use base qw(DBI::db);
  1         2  
  1         1572  
50              
51             =head1 METHODS
52              
53             =over
54              
55             =item connect()
56              
57             Constructor.
58              
59             my $dbh = Geo::GeoNames::DB::SQLite->connect( $dbname );
60              
61             =cut
62              
63             sub connect
64             {
65 2     2 1 785 my ( $class, $dbname ) = @_;
66            
67 2   33     20 $class = (ref $class) || $class;
68              
69 2 50       32 my $self = DBI->connect( "dbi:SQLite:dbname=$dbname", "", "", {AutoCommit=>0} )
70             or Carp::carp( $DBI::errstr );
71              
72 2         15551 $self->{sqlite_unicode} = 1;
73              
74 2         8 bless $self, $class;
75              
76 2         10 $self->_init;
77            
78 2         8 return $self;
79             }
80              
81             =item insert()
82              
83             Insert or replace GeoNames.org records. It accepts Geo::GeoNames::Record
84             and Geo::GeoNames::File object(s) as input.
85              
86             Always remember to commit changes by
87              
88             $db->commit;
89              
90             =cut
91              
92             sub insert
93             {
94 2     2 1 727 my $self = shift;
95              
96 2         16 while ( my $data = shift )
97             {
98 2 100       11 if ( ref( $data ) eq "Geo::GeoNames::Record" )
    50          
    0          
99             {
100 1         5 $self->_insert( $data );
101             }
102             elsif ( ref( $data ) eq "Geo::GeoNames::File" )
103             {
104 1         8 while ( my $rec = $data->next() )
105             {
106 10         28 $self->_insert( $rec );
107             }
108             }
109             elsif ( ref( $data ) eq "ARRAY" )
110             {
111 0         0 foreach ( @$data )
112             {
113 0         0 $self->_insert( $_ );
114             }
115             }
116             else
117             {
118 0         0 Carp::carp( "Cannot recgonize input type!" );
119             }
120             }
121              
122 2         18 return $self;
123             }
124              
125             # insert or replace a single Geo::GeoNames::Record object
126              
127             sub _insert
128             {
129 11     11   16 my ( $self, $record ) = @_;
130            
131 11 50       34 if ( ref( $record ) eq "Geo::GeoNames::Record" )
132             {
133 11         19 $self->do( "INSERT OR REPLACE INTO geoname VALUES (" . join( ", ", map( $self->quote($_), @{$record}{@Geo::GeoNames::Record::fields} ) ) . ")" );
  11         126  
134            
135 11         2990 foreach ( $record->names() )
136             {
137 224         37402 $self->do( "INSERT OR REPLACE INTO alternate_name (geonameid, alternate_name) VALUES ( $record->{geonameid}, " . $self->quote($_) . ")" );
138             }
139             }
140             else
141             {
142 0         0 Carp::carp( "Wrong type in insertion!" );
143             }
144            
145 11         2028 return $self;
146             }
147              
148             =item select_all_records()
149              
150             Select all records. (slow)
151              
152             =cut
153              
154             sub select_all_records
155             {
156 0     0 1 0 my $self = shift;
157              
158 0         0 my $records = $self->selectall_hashref( "SELECT * FROM geoname", "geonameid" );
159              
160 0         0 return map( bless($_, "Geo::GeoNames::Record"), values(%$records) );
161             }
162              
163             =item select_all_alternate_names()
164              
165             Select all the alternate names and corresponding geonameids.
166              
167             =cut
168              
169             sub select_all_alternate_names
170             {
171 0     0 1 0 my $self = shift;
172              
173 0         0 return $self->selectall_arrayref( "SELECT alternate_name, geonameid FROM alternate_name" );
174             }
175              
176             =item query()
177              
178             Query function.
179              
180             my @records = $dbh->query( $geonameid );
181             my @records = $dbh->query( $name1, $name2 );
182              
183             =cut
184              
185             sub query
186             {
187 1     1 1 3 my $self = shift;
188              
189 1         2 my @records;
190              
191 1         4 foreach my $word (@_)
192             {
193 1 50       8 if ( $word =~ /^\d+$/ )
194             {
195 0         0 push @records, $self->_query_id( $word );
196             }
197             else
198             {
199 1         5 push @records, $self->_query_name( $word );
200             }
201             }
202              
203 1         5 return @records;
204             }
205              
206             sub _query_id
207             {
208 1     1   3 my ( $self, $id ) = @_;
209            
210 1         20 my $record = $self->selectrow_hashref( "SELECT * FROM geoname where geonameid = $id" );
211              
212 1 50       186 if( $record )
213             {
214 1         4 bless $record, "Geo::GeoNames::Record";
215             }
216              
217 1         6 return $record;
218             }
219              
220             sub _query_name
221             {
222 1     1   2 my ( $self, $name ) = @_;
223              
224 1         9 $name = $self->quote($name);
225              
226 1         32 my $records = $self->selectall_hashref( "SELECT * FROM geoname WHERE geonameid IN (SELECT DISTINCT geonameid from alternate_name where alternate_name = $name)", "geonameid" );
227              
228 1         437 return map( bless($_, "Geo::GeoNames::Record"), values(%$records) );
229             }
230              
231              
232             # check and build database structure
233              
234             sub _init
235             {
236 2     2   3 my $self = shift;
237            
238             # check tables
239            
240 2         28 my @tbl_names = map( $_->[0],
241 2         5 @{$self->selectall_arrayref("SELECT name FROM sqlite_master WHERE type='table'")}
242             );
243            
244 2 100       1004 unless( grep {$_ eq "geoname";} @tbl_names )
  2         9  
245             {
246 1         13 $self->do( "CREATE TABLE geoname (geonameid INTEGER NOT NULL, name TEXT NOT NULL, asciiname TEXT NOT NULL, alternatenames TEXT, latitude REAL, longitude REAL, feature_class TEXT, feature_code TEXT, country_code TEXT, cc2 TEXT, admin1_code TEXT, admin2_code TEXT, admin3_code TEXT, admin4_code TEXT, population INTEGER, elevation INTEGER, gtopo30 INTEGER, timezone TEXT, modification_date TEXT, PRIMARY KEY (geonameid) )" );
247             }
248            
249 2 100       367 unless( grep {$_ eq "alternate_name";} @tbl_names )
  2         6  
250             {
251 1         6 $self->do( "CREATE TABLE alternate_name (geonameid INTEGER NOT NULL, alternate_name TEXT NOT NULL, PRIMARY KEY (geonameid, alternate_name) )" );
252             }
253            
254             # check index
255              
256 2         14 my @idx_names = map( $_->[0],
257 2         232 @{$self->selectall_arrayref("SELECT name FROM sqlite_master WHERE type='index'")}
258             );
259              
260 2 100       163 unless( grep {$_ eq "alternate_name_idx";} @idx_names )
  3         23  
261             {
262 1         6 $self->do( "CREATE INDEX alternate_name_idx ON alternate_name (alternate_name)" );
263             }
264              
265 2         170 return $self;
266             }
267              
268             =back
269              
270             =cut
271              
272             1;
273             __END__