File Coverage

blib/lib/App/Chart/Database.pm
Criterion Covered Total %
statement 12 14 85.7
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 17 19 89.4


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010, 2011, 2016, 2017 Kevin Ryde
2              
3             # This file is part of Chart.
4             #
5             # Chart is free software; you can redistribute it and/or modify it under the
6             # terms of the GNU General Public License as published by the Free Software
7             # Foundation; either version 3, or (at your option) any later version.
8             #
9             # Chart is distributed in the hope that it will be useful, but WITHOUT ANY
10             # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11             # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12             # details.
13             #
14             # You should have received a copy of the GNU General Public License along
15             # with Chart. If not, see <http://www.gnu.org/licenses/>.
16              
17             package App::Chart::Database;
18 1     1   453 use 5.010;
  1         3  
19 1     1   5 use strict;
  1         2  
  1         18  
20 1     1   4 use warnings;
  1         2  
  1         27  
21 1     1   5 use Carp;
  1         2  
  1         52  
22              
23 1     1   283 use App::Chart;
  0            
  0            
24             use App::Chart::DBI;
25              
26             # uncomment this to run the ### lines
27             #use Devel::Comments;
28              
29              
30             #------------------------------------------------------------------------------
31              
32             # return true if $dbh contains a table called $table
33             sub dbh_table_exists {
34             my ($dbh, $table) = @_;
35             my $sth = $dbh->table_info (undef, undef, $table, undef);
36             my $exists = $sth->fetchrow_arrayref ? 1 : 0;
37             $sth->finish;
38             return $exists;
39             }
40              
41              
42             sub read_single {
43             return App::Chart::DBI->read_single (@_);
44             # my ($sql, @args) = @_;
45             # my $dbh = App::Chart::DBI->instance;
46             # my $sth = $dbh->prepare_cached ($sql);
47             # my $row = $dbh->selectrow_arrayref($sth, undef, @args);
48             # $sth->finish;
49             # if (! defined $row) { return undef; }
50             # return $row->[0];
51             }
52              
53             sub read_notes_single {
54             # my ($sql, @args) = @_;
55             # if (DEBUG) { print "read_notes_single(): $sql\n"; }
56             return App::Chart::DBI->read_single (@_);
57             # my $nbh = App::Chart::DBI->instance;
58             # my $sth = $nbh->prepare_cached ($sql);
59             # my $row = $nbh->selectrow_arrayref($sth, undef, @args);
60             # $sth->finish;
61             # if (! defined $row) { return undef; }
62             # return $row->[0];
63             }
64              
65             # might prefer some sort of "INSERT WHERE NOT EXISTS", but sqlite doesn't
66             # seem to take that (only it's own extension "INSERT OR IGNORE")
67             #
68             sub add_symbol {
69             my ($class, @symbol_list) = @_;
70             ### Database add_symbol(): @symbol_list
71             require App::Chart::Gtk2::Symlist::All;
72             require App::Chart::Gtk2::Symlist::Historical;
73             require App::Chart::Annotation;
74             my $all_symlist = App::Chart::Gtk2::Symlist::All->instance;
75             my $historical_symlist = App::Chart::Gtk2::Symlist::Historical->instance;
76              
77             my $dbh = App::Chart::DBI->instance;
78             call_with_transaction
79             ($dbh, sub {
80             my $sth = $dbh->prepare_cached
81             ('UPDATE info SET historical=0 WHERE symbol=?');
82             foreach my $symbol (@symbol_list) {
83             if ($class->symbol_exists ($symbol)) {
84             $sth->execute ($symbol);
85             $sth->finish;
86             } else {
87             $dbh->do ('INSERT INTO info (symbol) VALUES (?)', {}, $symbol);
88             }
89             $all_symlist->insert_symbol ($symbol);
90             $historical_symlist->delete_symbol ($symbol);
91             # possible existing alert levels
92             App::Chart::Annotation::Alert::update_alert($symbol);
93             }
94             });
95             }
96              
97              
98             sub delete_symbol {
99             my ($class, $symbol, $notes_too) = @_;
100             ### Database delete_symbol(): $symbol
101             ### $notes_too
102              
103             # sqlite allows multiple statements in one handle, but that's apparently
104             # not always so in DBI
105              
106             require App::Chart::Gtk2::Symlist::All;
107             require App::Chart::Gtk2::Symlist::Historical;
108             require App::Chart::Annotation;
109             my $all_symlist = App::Chart::Gtk2::Symlist::All->instance;
110             my $historical_symlist = App::Chart::Gtk2::Symlist::Historical->instance;
111              
112             my $dbh = App::Chart::DBI->instance;
113             call_with_transaction
114             ($dbh, sub {
115             foreach my $statement
116             ('DELETE FROM daily WHERE symbol=?',
117             'DELETE FROM info WHERE symbol=?',
118             'DELETE FROM dividend WHERE symbol=?',
119             'DELETE FROM split WHERE symbol=?',
120             'DELETE FROM extra WHERE symbol=?') {
121             $dbh->do($statement, undef, $symbol);
122             }
123             if ($notes_too) {
124             foreach my $statement
125             ('DELETE FROM annotation WHERE symbol=?',
126             'DELETE FROM line WHERE symbol=?',
127             'DELETE FROM alert WHERE symbol=?') {
128             $dbh->do($statement, undef, $symbol);
129             }
130             }
131             $all_symlist ->delete_symbol ($symbol);
132             $historical_symlist->delete_symbol ($symbol);
133             # delete from alerts list
134             App::Chart::Annotation::Alert::update_alert($symbol);
135             });
136              
137             App::Chart::chart_dirbroadcast()->send ('delete-symbol', $symbol);
138             App::Chart::chart_dirbroadcast()->send ('data-changed', { $symbol => 1 });
139             App::Chart::chart_dirbroadcast()->send ('delete-notes', $symbol);
140             }
141              
142             sub symbol_exists {
143             my ($class, $symbol) = @_;
144             return read_single ('SELECT symbol FROM info WHERE symbol=?', $symbol);
145             }
146              
147             # return a hashref which has for its keys all the symbols in the database
148             # (the daily data, not quotes or intraday)
149             sub database_symbols_hash {
150             my $dbh = App::Chart::DBI->instance;
151             my $sth = $dbh->prepare_cached('SELECT symbol FROM info');
152             my $aref = $dbh->selectcol_arrayref ($sth, { });
153             $sth->finish();
154             my %hash = ();
155             @hash{@$aref} = 1;
156             return \%hash;
157             }
158              
159             sub symbols_list {
160             # my ($class) = @_;
161             my $dbh = App::Chart::DBI->instance;
162             my $sth = $dbh->prepare_cached('SELECT symbol FROM info');
163             my $aref = $dbh->selectcol_arrayref ($sth);
164             $sth->finish();
165             return @$aref;
166             }
167              
168             sub symbol_is_historical {
169             my ($class, $symbol) = @_;
170             my $dbh = App::Chart::DBI->instance;
171             my $sth = $dbh->prepare_cached('SELECT historical FROM info WHERE symbol=?');
172             my $aref = $dbh->selectrow_arrayref ($sth, undef, $symbol);
173             return ($aref && $aref->[0]);
174             }
175              
176             sub symbol_name {
177             my ($class, $symbol) = @_;
178             return read_single ('SELECT name FROM info WHERE symbol=?', $symbol);
179             }
180              
181             sub symbol_decimals {
182             my ($class, $symbol) = @_;
183             return (read_single ('SELECT decimals FROM info WHERE symbol=?', $symbol)
184             || 0);
185             }
186              
187             sub write_extra {
188             my ($class, $symbol, $key, $value) = @_;
189             if (! defined $key) { croak 'write_extra() key cannot be undef'; }
190              
191             my $dbh = App::Chart::DBI->instance;
192             if (defined $value) {
193             my $sth = $dbh->prepare_cached
194             ('INSERT OR REPLACE INTO extra (symbol, key, value) VALUES (?,?,?)');
195             $sth->execute ($symbol, $key, $value);
196             $sth->finish;
197             } else {
198             $dbh->do ('DELETE FROM extra WHERE symbol=? AND key=?',
199             undef,
200             $symbol, $key);
201             }
202             }
203              
204             sub read_extra {
205             my ($class, $symbol, $key) = @_;
206             return read_single ('SELECT value FROM extra WHERE symbol=? AND key=?',
207             $symbol, $key);
208             }
209              
210             # An eval isn't backtrace friendly, but a __DIE__ handler would be reached
211             # by possible normal errors caught by a handler in $subr.
212             #
213             # rollback() can get errors too, like database gone away. They end up
214             # thrown in preference to the original error.
215             #
216             sub call_with_transaction {
217             my ($dbh, $subr) = @_;
218             my $hold = App::Chart::chart_dirbroadcast()->hold;
219              
220             if ($dbh->{AutoCommit}) {
221             my $ret;
222             $dbh->begin_work;
223             if (eval { $ret = $subr->(); 1 }) {
224             $dbh->commit;
225             return $ret;
226             } else {
227             my $err = $@;
228             $dbh->rollback;
229             die $err;
230             }
231              
232             } else {
233             $subr->();
234             }
235             }
236              
237             sub preference_get {
238             my ($class, $key, $default) = @_;
239             my $value = read_notes_single
240             ('SELECT value FROM preference WHERE key=?', $key);
241             if (defined $value) {
242             return $value;
243             } else {
244             return $default;
245             }
246             }
247              
248              
249             1;
250             __END__
251              
252             =for stopwords delisted
253              
254             =head1 NAME
255              
256             App::Chart::Database -- database functions
257              
258             =head1 FUNCTIONS
259              
260             =over 4
261              
262             =item C<< App::Chart::Database->add_symbol ($symbol) >>
263              
264             Add C<$symbol> to the database. If C<$symbol> is already in the database
265             then remove its "historical" marker.
266              
267             =item C<< App::Chart::Database->delete_symbol ($symbol, $notes_too) >>
268              
269             Delete all data relating to C<$symbol> from the database. If C<$notes_too>
270             is given and it's true then delete user notes and annotations too.
271              
272             =back
273              
274             =head2 Symbol Info
275              
276             =over 4
277              
278             =item C<< App::Chart::Database->symbol_exists ($symbol) >>
279              
280             Return true if C<$symbol> exists in the database.
281              
282             =item App::Chart::Database->symbol_is_historical ($symbol)
283              
284             Return true if C<$symbol> is marked as historical, meaning it's delisted, or
285             renamed, or whatever, but in any case is no longer actively trading.
286              
287             =item C<< App::Chart::Database->symbol_name ($symbol) >>
288              
289             Return the stock or commodity name for C<$symbol>, obtained from the
290             database.
291              
292             =item C<< App::Chart::Database->symbol_decimals ($symbol) >>
293              
294             Return the number of decimal places normally shown on prices for C<$symbol>.
295             For example prices in dollars might have this as 2 to show dollars and
296             cents.
297              
298             It's possible particular prices in the database or a quote might have more
299             than this many places. The return is 0 if there's no information on
300             C<$symbol>.
301              
302             =back
303              
304             =head2 Other
305              
306             =over 4
307              
308             =item C<< $value = App::Chart::Database->read_extra ($symbol, $key) >>
309              
310             =item C<< App::Chart::Database->write_extra ($symbol, $key, $value) >>
311              
312             Read or write extra data associated with C<$symbol>. C<$key> is a string
313             describing the data, C<$value> is a string or C<undef>. C<undef> means
314             delete the data.
315              
316             C<$symbol> can be the empty string "" for global extra data. Some data
317             sources cache information this way.
318              
319             =back
320              
321             =head1 SEE ALSO
322              
323             L<App::Chart>
324              
325             =head1 HOME PAGE
326              
327             L<http://user42.tuxfamily.org/chart/index.html>
328              
329             =head1 LICENCE
330              
331             Copyright 2008, 2009, 2010, 2011 Kevin Ryde
332              
333             Chart is free software; you can redistribute it and/or modify it under the
334             terms of the GNU General Public License as published by the Free Software
335             Foundation; either version 3, or (at your option) any later version.
336              
337             Chart is distributed in the hope that it will be useful, but WITHOUT ANY
338             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
339             FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
340             details.
341              
342             You should have received a copy of the GNU General Public License along with
343             Chart; see the file F<COPYING>. Failing that, see
344             L<http://www.gnu.org/licenses/>.
345              
346             =cut
347              
348              
349             # =item C<App::Chart::Database::call_with_transaction ($dbh, $subr)>
350             #
351             # Call C<$subr> with a transaction setup on C<$dbh>. If C<$dbh> doesn't
352             # already have a transaction active then one is started, C<$subr> is called,
353             # and it's then committed. Otherwise if C<$dbh> is already in a transaction
354             # then C<$subr> is simply called with no other action, part of that existing
355             # transaction.
356