File Coverage

blib/lib/Wiki/Toolkit/Setup/Pg.pm
Criterion Covered Total %
statement 15 96 15.6
branch 0 46 0.0
condition 0 21 0.0
subroutine 5 11 45.4
pod 2 2 100.0
total 22 176 12.5


line stmt bran cond sub pod time code
1             package Wiki::Toolkit::Setup::Pg;
2              
3 2     2   1681 use strict;
  2         2  
  2         80  
4              
5 2     2   9 use vars qw( @ISA $VERSION $SCHEMA_VERSION );
  2         4  
  2         143  
6              
7 2     2   548 use Wiki::Toolkit::Setup::Database;
  2         6  
  2         79  
8              
9             @ISA = qw( Wiki::Toolkit::Setup::Database );
10             $VERSION = '0.10';
11              
12 2     2   9 use DBI;
  2         2  
  2         81  
13 2     2   9 use Carp;
  2         3  
  2         2849  
14              
15             $SCHEMA_VERSION = $VERSION*100;
16              
17             my $create_sql = {
18             8 => {
19             schema_info => [ qq|
20             CREATE TABLE schema_info (
21             version integer NOT NULL default 0
22             )
23             |, qq|
24             INSERT INTO schema_info VALUES (8)
25             | ],
26              
27             node => [ qq|
28             CREATE SEQUENCE node_seq
29             |, qq|
30             CREATE TABLE node (
31             id integer NOT NULL DEFAULT NEXTVAL('node_seq'),
32             name varchar(200) NOT NULL DEFAULT '',
33             version integer NOT NULL default 0,
34             text text NOT NULL default '',
35             modified timestamp without time zone default NULL,
36             CONSTRAINT pk_id PRIMARY KEY (id)
37             )
38             |, qq|
39             CREATE UNIQUE INDEX node_name ON node (name)
40             | ],
41              
42             content => [ qq|
43             CREATE TABLE content (
44             node_id integer NOT NULL,
45             version integer NOT NULL default 0,
46             text text NOT NULL default '',
47             modified timestamp without time zone default NULL,
48             comment text NOT NULL default '',
49             CONSTRAINT pk_node_id PRIMARY KEY (node_id,version),
50             CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
51             )
52             | ],
53              
54             internal_links => [ qq|
55             CREATE TABLE internal_links (
56             link_from varchar(200) NOT NULL default '',
57             link_to varchar(200) NOT NULL default ''
58             )
59             |, qq|
60             CREATE UNIQUE INDEX internal_links_pkey ON internal_links (link_from, link_to)
61             | ],
62              
63             metadata => [ qq|
64             CREATE TABLE metadata (
65             node_id integer NOT NULL,
66             version integer NOT NULL default 0,
67             metadata_type varchar(200) NOT NULL DEFAULT '',
68             metadata_value text NOT NULL DEFAULT '',
69             CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
70             )
71             |, qq|
72             CREATE INDEX metadata_index ON metadata (node_id, version, metadata_type, metadata_value)
73             | ]
74              
75             },
76             9 => {
77             schema_info => [ qq|
78             CREATE TABLE schema_info (
79             version integer NOT NULL default 0
80             )
81             |, qq|
82             INSERT INTO schema_info VALUES (9)
83             | ],
84              
85             node => [ qq|
86             CREATE SEQUENCE node_seq
87             |, qq|
88             CREATE TABLE node (
89             id integer NOT NULL DEFAULT NEXTVAL('node_seq'),
90             name varchar(200) NOT NULL DEFAULT '',
91             version integer NOT NULL default 0,
92             text text NOT NULL default '',
93             modified timestamp without time zone default NULL,
94             moderate boolean NOT NULL default '0',
95             CONSTRAINT pk_id PRIMARY KEY (id)
96             )
97             |, qq|
98             CREATE UNIQUE INDEX node_name ON node (name)
99             | ],
100              
101             content => [ qq|
102             CREATE TABLE content (
103             node_id integer NOT NULL,
104             version integer NOT NULL default 0,
105             text text NOT NULL default '',
106             modified timestamp without time zone default NULL,
107             comment text NOT NULL default '',
108             moderated boolean NOT NULL default '1',
109             CONSTRAINT pk_node_id PRIMARY KEY (node_id,version),
110             CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
111             )
112             | ],
113              
114             internal_links => [ qq|
115             CREATE TABLE internal_links (
116             link_from varchar(200) NOT NULL default '',
117             link_to varchar(200) NOT NULL default ''
118             )
119             |, qq|
120             CREATE UNIQUE INDEX internal_links_pkey ON internal_links (link_from, link_to)
121             | ],
122              
123             metadata => [ qq|
124             CREATE TABLE metadata (
125             node_id integer NOT NULL,
126             version integer NOT NULL default 0,
127             metadata_type varchar(200) NOT NULL DEFAULT '',
128             metadata_value text NOT NULL DEFAULT '',
129             CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
130             )
131             |, qq|
132             CREATE INDEX metadata_index ON metadata (node_id, version, metadata_type, metadata_value)
133             | ]
134             },
135             10 => {
136             schema_info => [ qq|
137             CREATE TABLE schema_info (
138             version integer NOT NULL default 0
139             )
140             |, qq|
141             INSERT INTO schema_info VALUES (10)
142             | ],
143              
144             node => [ qq|
145             CREATE SEQUENCE node_seq
146             |, qq|
147             CREATE TABLE node (
148             id integer NOT NULL DEFAULT NEXTVAL('node_seq'),
149             name varchar(200) NOT NULL DEFAULT '',
150             version integer NOT NULL default 0,
151             text text NOT NULL default '',
152             modified timestamp without time zone default NULL,
153             moderate boolean NOT NULL default '0',
154             CONSTRAINT pk_id PRIMARY KEY (id)
155             )
156             |, qq|
157             CREATE UNIQUE INDEX node_name ON node (name)
158             | ],
159              
160             content => [ qq|
161             CREATE TABLE content (
162             node_id integer NOT NULL,
163             version integer NOT NULL default 0,
164             text text NOT NULL default '',
165             modified timestamp without time zone default NULL,
166             comment text NOT NULL default '',
167             moderated boolean NOT NULL default '1',
168             verified timestamp without time zone default NULL,
169             verified_info text NOT NULL default '',
170             CONSTRAINT pk_node_id PRIMARY KEY (node_id,version),
171             CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
172             )
173             | ],
174              
175             internal_links => [ qq|
176             CREATE TABLE internal_links (
177             link_from varchar(200) NOT NULL default '',
178             link_to varchar(200) NOT NULL default ''
179             )
180             |, qq|
181             CREATE UNIQUE INDEX internal_links_pkey ON internal_links (link_from, link_to)
182             | ],
183              
184             metadata => [ qq|
185             CREATE TABLE metadata (
186             node_id integer NOT NULL,
187             version integer NOT NULL default 0,
188             metadata_type varchar(200) NOT NULL DEFAULT '',
189             metadata_value text NOT NULL DEFAULT '',
190             CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
191             )
192             |, qq|
193             CREATE INDEX metadata_index ON metadata (node_id, version, metadata_type, metadata_value)
194             | ]
195             },
196             };
197              
198             my %upgrades = (
199             old_to_8 => [ qq|
200             CREATE SEQUENCE node_seq;
201             ALTER TABLE node ADD COLUMN id INTEGER;
202             UPDATE node SET id = NEXTVAL('node_seq');
203             |, qq|
204             ALTER TABLE node ALTER COLUMN id SET NOT NULL;
205             ALTER TABLE node ALTER COLUMN id SET DEFAULT NEXTVAL('node_seq');
206             |, qq|
207             DROP INDEX node_pkey;
208             ALTER TABLE node ADD CONSTRAINT pk_id PRIMARY KEY (id);
209             CREATE UNIQUE INDEX node_name ON node (name)
210             |,
211              
212             qq|
213             ALTER TABLE content ADD COLUMN node_id INTEGER;
214             UPDATE content SET node_id =
215             (SELECT id FROM node where node.name = content.name)
216             |, qq|
217             DELETE FROM content WHERE node_id IS NULL;
218             ALTER TABLE content ALTER COLUMN node_id SET NOT NULL;
219             ALTER TABLE content DROP COLUMN name;
220             ALTER TABLE content ADD CONSTRAINT pk_node_id PRIMARY KEY (node_id,version);
221             ALTER TABLE content ADD CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id)
222             |,
223              
224             qq|
225             ALTER TABLE metadata ADD COLUMN node_id INTEGER;
226             UPDATE metadata SET node_id =
227             (SELECT id FROM node where node.name = metadata.node)
228             |, qq|
229             DELETE FROM metadata WHERE node_id IS NULL;
230             ALTER TABLE metadata ALTER COLUMN node_id SET NOT NULL;
231             ALTER TABLE metadata DROP COLUMN node;
232             ALTER TABLE metadata ADD CONSTRAINT fk_node_id FOREIGN KEY (node_id) REFERENCES node (id);
233             CREATE INDEX metadata_index ON metadata (node_id, version, metadata_type, metadata_value)
234             |,
235              
236             qq|
237             CREATE TABLE schema_info (version integer NOT NULL default 0);
238             INSERT INTO schema_info VALUES (8)
239             |
240             ],
241              
242             '8_to_9' => [ qq|
243             ALTER TABLE node ADD COLUMN moderate boolean;
244             UPDATE node SET moderate = '0';
245             ALTER TABLE node ALTER COLUMN moderate SET DEFAULT '0';
246             ALTER TABLE node ALTER COLUMN moderate SET NOT NULL;
247             |, qq|
248             ALTER TABLE content ADD COLUMN moderated boolean;
249             UPDATE content SET moderated = '1';
250             ALTER TABLE content ALTER COLUMN moderated SET DEFAULT '1';
251             ALTER TABLE content ALTER COLUMN moderated SET NOT NULL;
252             UPDATE schema_info SET version = 9;
253             |
254             ],
255              
256             '9_to_10' => [ qq|
257             ALTER TABLE content ADD COLUMN verified timestamp without time zone default NULL;
258             ALTER TABLE content ADD COLUMN verified_info text NOT NULL default '';
259             |, qq|
260             UPDATE schema_info SET version = 10;
261             |
262             ],
263              
264             );
265              
266             my @old_to_10 = ($upgrades{'old_to_8'},$upgrades{'8_to_9'},$upgrades{'9_to_10'});
267             my @eight_to_10 = ($upgrades{'8_to_9'},$upgrades{'9_to_10'});
268             $upgrades{'old_to_10'} = \@old_to_10;
269             $upgrades{'8_to_10'} = \@eight_to_10;
270              
271             =head1 NAME
272              
273             Wiki::Toolkit::Setup::Pg - Set up tables for a Wiki::Toolkit store in a Postgres database.
274              
275             =head1 SYNOPSIS
276              
277             use Wiki::Toolkit::Setup::Pg;
278             Wiki::Toolkit::Setup::Pg::setup($dbname, $dbuser, $dbpass, $dbhost);
279              
280             Omit $dbhost if the database is local.
281              
282             =head1 DESCRIPTION
283              
284             Set up a Postgres database for use as a Wiki::Toolkit store.
285              
286             =head1 FUNCTIONS
287              
288             =over 4
289              
290             =item B
291              
292             use Wiki::Toolkit::Setup::Pg;
293             Wiki::Toolkit::Setup::Pg::setup($dbname, $dbuser, $dbpass, $dbhost);
294              
295             or
296              
297             Wiki::Toolkit::Setup::Pg::setup( $dbh );
298              
299             You can either provide an active database handle C<$dbh> or connection
300             parameters.
301              
302             If you provide connection parameters the following arguments are
303             mandatory -- the database name, the username and the password. The
304             username must be able to create and drop tables in the database.
305              
306             The $dbhost argument is optional -- omit it if the database is local.
307              
308             B If a table that the module wants to create already exists,
309             C will leave it alone. This means that you can safely run this
310             on an existing L database to bring the schema up to date
311             with the current L version. If you wish to completely start
312             again with a fresh database, run C first.
313              
314             =cut
315              
316             sub setup {
317 0     0 1   my @args = @_;
318 0           my $dbh = _get_dbh( @args );
319 0           my $disconnect_required = _disconnect_required( @args );
320 0   0       my $wanted_schema = _get_wanted_schema( @args ) || $SCHEMA_VERSION;
321              
322             die "No schema information for requested schema version $wanted_schema\n"
323 0 0         unless $create_sql->{$wanted_schema};
324              
325             # Check whether tables exist
326             my $sql = "SELECT tablename FROM pg_tables
327             WHERE tablename in ("
328 0           . join( ",", map { $dbh->quote($_) } keys %{$create_sql->{$wanted_schema}} ) . ")";
  0            
  0            
329 0 0         my $sth = $dbh->prepare($sql) or croak $dbh->errstr;
330 0           $sth->execute;
331 0           my %tables;
332 0           while ( my $table = $sth->fetchrow_array ) {
333 0 0         exists $create_sql->{$wanted_schema}->{$table} and $tables{$table} = 1;
334             }
335              
336             # Do we need to upgrade the schema of existing tables?
337             # (Don't check if no tables currently exist)
338 0           my $upgrade_schema;
339 0 0         if(scalar keys %tables > 0) {
340 0           $upgrade_schema = Wiki::Toolkit::Setup::Database::get_database_upgrade_required($dbh,$wanted_schema);
341             } else {
342 0           print "Skipping schema upgrade check - no tables found\n";
343             }
344              
345             # Set up tables if not found
346 0           foreach my $required ( reverse sort keys %{$create_sql->{$wanted_schema}} ) {
  0            
347 0 0         if ( $tables{$required} ) {
348 0           print "Table $required already exists... skipping...\n";
349             } else {
350 0           print "Creating table $required... done\n";
351 0           foreach my $sql ( @{ $create_sql->{$wanted_schema}->{$required} } ) {
  0            
352 0 0         $dbh->do($sql) or croak $dbh->errstr;
353             }
354             }
355             }
356              
357             # Do the upgrade if required
358 0 0         if($upgrade_schema) {
359 0           print "Upgrading schema: $upgrade_schema\n";
360 0           my @updates = @{$upgrades{$upgrade_schema}};
  0            
361 0           foreach my $update (@updates) {
362 0 0         if(ref($update) eq "CODE") {
    0          
363 0           &$update($dbh);
364             } elsif(ref($update) eq "ARRAY") {
365 0           foreach my $nupdate (@$update) {
366 0           $dbh->do($nupdate);
367             }
368             } else {
369 0           $dbh->do($update);
370             }
371             }
372             }
373              
374             # Clean up if we made our own dbh.
375 0 0         $dbh->disconnect if $disconnect_required;
376             }
377              
378             =item B
379              
380             use Wiki::Toolkit::Setup::Pg;
381              
382             # Clear out all Wiki::Toolkit tables from the database.
383             Wiki::Toolkit::Setup::Pg::cleardb($dbname, $dbuser, $dbpass, $dbhost);
384              
385             or
386              
387             Wiki::Toolkit::Setup::Pg::cleardb( $dbh );
388              
389             You can either provide an active database handle C<$dbh> or connection
390             parameters.
391              
392             If you provide connection parameters the following arguments are
393             mandatory -- the database name, the username and the password. The
394             username must be able to drop tables in the database.
395              
396             The $dbhost argument is optional -- omit it if the database is local.
397              
398             Clears out all L store tables from the database. B
399             that this will lose all your data; you probably only want to use this
400             for testing purposes or if you really screwed up somewhere. Note also
401             that it doesn't touch any L search backend tables; if you
402             have any of those in the same or a different database see
403             L or L, depending on
404             which search backend you're using.
405              
406             =cut
407              
408             sub cleardb {
409 0     0 1   my @args = @_;
410 0           my $dbh = _get_dbh( @args );
411 0           my $disconnect_required = _disconnect_required( @args );
412              
413 0           print "Dropping tables... ";
414             my $sql = "SELECT tablename FROM pg_tables
415             WHERE tablename in ("
416 0           . join( ",", map { $dbh->quote($_) } keys %{$create_sql->{$SCHEMA_VERSION}} ) . ")";
  0            
  0            
417 0           foreach my $tableref (@{$dbh->selectall_arrayref($sql)}) {
  0            
418 0 0         $dbh->do("DROP TABLE $tableref->[0] CASCADE") or croak $dbh->errstr;
419             }
420              
421 0           $sql = "SELECT relname FROM pg_statio_all_sequences
422             WHERE relname = 'node_seq'";
423 0           foreach my $seqref (@{$dbh->selectall_arrayref($sql)}) {
  0            
424 0 0         $dbh->do("DROP SEQUENCE $seqref->[0]") or croak $dbh->errstr;
425             }
426              
427 0           print "done\n";
428              
429             # Clean up if we made our own dbh.
430 0 0         $dbh->disconnect if $disconnect_required;
431             }
432              
433             sub _get_dbh {
434             # Database handle passed in.
435 0 0 0 0     if ( ref $_[0] and ref $_[0] eq 'DBI::db' ) {
436 0           return $_[0];
437             }
438              
439             # Args passed as hashref.
440 0 0 0       if ( ref $_[0] and ref $_[0] eq 'HASH' ) {
441 0           my %args = %{$_[0]};
  0            
442 0 0         if ( $args{dbh} ) {
443 0           return $args{dbh};
444             } else {
445 0           return _make_dbh( %args );
446             }
447             }
448              
449             # Args passed as list of connection details.
450 0           return _make_dbh(
451             dbname => $_[0],
452             dbuser => $_[1],
453             dbpass => $_[2],
454             dbhost => $_[3],
455             );
456             }
457              
458             sub _get_wanted_schema {
459             # Database handle passed in.
460 0 0 0 0     if ( ref $_[0] and ref $_[0] eq 'DBI::db' ) {
461 0           return undef;
462             }
463              
464             # Args passed as hashref.
465 0 0 0       if ( ref $_[0] and ref $_[0] eq 'HASH' ) {
466 0           my %args = %{$_[0]};
  0            
467 0           return $args{wanted_schema};
468             }
469             }
470              
471             sub _disconnect_required {
472             # Database handle passed in.
473 0 0 0 0     if ( ref $_[0] and ref $_[0] eq 'DBI::db' ) {
474 0           return 0;
475             }
476              
477             # Args passed as hashref.
478 0 0 0       if ( ref $_[0] and ref $_[0] eq 'HASH' ) {
479 0           my %args = %{$_[0]};
  0            
480 0 0         if ( $args{dbh} ) {
481 0           return 0;
482             } else {
483 0           return 1;
484             }
485             }
486              
487             # Args passed as list of connection details.
488 0           return 1;
489             }
490              
491             sub _make_dbh {
492 0     0     my %args = @_;
493 0           my $dsn = "dbi:Pg:dbname=$args{dbname}";
494 0 0         $dsn .= ";host=$args{dbhost}" if $args{dbhost};
495             my $dbh = DBI->connect($dsn, $args{dbuser}, $args{dbpass},
496 0 0         { PrintError => 1, RaiseError => 1,
497             AutoCommit => 1 } )
498             or croak DBI::errstr;
499 0           return $dbh;
500             }
501              
502             =back
503              
504             =head1 ALTERNATIVE CALLING SYNTAX
505              
506             As requested by Podmaster. Instead of passing arguments to the methods as
507              
508             ($dbname, $dbuser, $dbpass, $dbhost)
509              
510             you can pass them as
511              
512             ( { dbname => $dbname,
513             dbuser => $dbuser,
514             dbpass => $dbpass,
515             dbhost => $dbhost
516             }
517             )
518              
519             or indeed as
520              
521             ( { dbh => $dbh } )
522              
523             Note that's a hashref, not a hash.
524              
525             =head1 AUTHOR
526              
527             Kake Pugh (kake@earth.li).
528              
529             =head1 COPYRIGHT
530              
531             Copyright (C) 2002-2004 Kake Pugh. All Rights Reserved.
532             Copyright (C) 2006-2008 the Wiki::Toolkit team. All Rights Reserved.
533              
534             This module is free software; you can redistribute it and/or modify it
535             under the same terms as Perl itself.
536              
537             =head1 SEE ALSO
538              
539             L, L, L
540              
541             =cut
542              
543             1;