merging katipo changes...
[koha.git] / updater / updatedatabase
1 #!/usr/bin/perl
2
3 # $Id$
4
5 # Database Updater
6 # This script checks for required updates to the database.
7
8 # Part of the Koha Library Software www.koha.org
9 # Licensed under the GPL.
10
11 # Bugs/ToDo:
12 # - Would also be a good idea to offer to do a backup at this time...
13
14 # NOTE:  If you do something more than once in here, make it table driven.
15 use strict;
16
17 # CPAN modules
18 use DBI;
19 use Getopt::Long;
20 # Koha modules
21 use C4::Context;
22
23 use MARC::Record;
24 use MARC::File::XML ( BinaryEncoding => 'utf8' );
25  
26 # FIXME - The user might be installing a new database, so can't rely
27 # on /etc/koha.conf anyway.
28
29 my $debug = 0;
30
31 my (
32     $sth, $sti,
33     $query,
34     %existingtables,    # tables already in database
35     %types,
36     $table,
37     $column,
38     $type, $null, $key, $default, $extra,
39     $prefitem,          # preference item in systempreferences table
40 );
41
42 my $silent;
43 GetOptions(
44         's' =>\$silent
45         );
46 my $dbh = C4::Context->dbh;
47 print "connected to your DB. Checking & modifying it\n" unless $silent;
48 $|=1; # flushes output
49
50 #-------------------
51 # Defines
52
53 # Tables to add if they don't exist
54 my %requiretables = (
55     categorytable       => "(categorycode char(5) NOT NULL default '',
56                              description text default '',
57                              itemtypecodes text default '',
58                              PRIMARY KEY (categorycode)
59                             )",
60     subcategorytable       => "(subcategorycode char(5) NOT NULL default '',
61                              description text default '',
62                              itemtypecodes text default '',
63                              PRIMARY KEY (subcategorycode)
64                             )",
65     mediatypetable       => "(mediatypecode char(5) NOT NULL default '',
66                              description text default '',
67                              itemtypecodes text default '',
68                              PRIMARY KEY (mediatypecode)
69                             )",
70     action_logs         => "(
71                                     `timestamp` TIMESTAMP NOT NULL ,
72                                     `user` INT( 11 ) NOT NULL ,
73                                     `module` TEXT default '',
74                                     `action` TEXT default '' ,
75                                     `object` INT(11) default '' ,
76                                     `info` TEXT default '' ,
77                                     PRIMARY KEY ( `timestamp` , `user` )
78                             )",
79         letter          => "(
80                                         module varchar(20) NOT NULL default '',
81                                         code varchar(20) NOT NULL default '',
82                                         name varchar(100) NOT NULL default '',
83                                         title varchar(200) NOT NULL default '',
84                                         content text,
85                                         PRIMARY KEY  (module,code)
86                                 )",
87         alert           =>"(
88                                         alertid int(11) NOT NULL auto_increment,
89                                         borrowernumber int(11) NOT NULL default '0',
90                                         type varchar(10) NOT NULL default '',
91                                         externalid varchar(20) NOT NULL default '',
92                                         PRIMARY KEY  (alertid),
93                                         KEY borrowernumber (borrowernumber),
94                                         KEY type (type,externalid)
95                                 )",
96         opac_news => "(
97                                 `idnew` int(10) unsigned NOT NULL auto_increment,
98                                 `title` varchar(250) NOT NULL default '',
99                                 `new` text NOT NULL,
100                                 `lang` varchar(4) NOT NULL default '',
101                                 `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
102                                 PRIMARY KEY  (`idnew`)
103                                 )",
104         repeatable_holidays => "(
105                                 `id` int(11) NOT NULL auto_increment,
106                                 `branchcode` varchar(4) NOT NULL default '',
107                                 `weekday` smallint(6) default NULL,
108                                 `day` smallint(6) default NULL,
109                                 `month` smallint(6) default NULL,
110                                 `title` varchar(50) NOT NULL default '',
111                                 `description` text NOT NULL,
112                                 PRIMARY KEY  (`id`)
113                                 )",
114         special_holidays => "(
115                                 `id` int(11) NOT NULL auto_increment,
116                                 `branchcode` varchar(4) NOT NULL default '',
117                                 `day` smallint(6) NOT NULL default '0',
118                                 `month` smallint(6) NOT NULL default '0',
119                                 `year` smallint(6) NOT NULL default '0',
120                                 `isexception` smallint(1) NOT NULL default '1',
121                                 `title` varchar(50) NOT NULL default '',
122                                 `description` text NOT NULL,
123                                 PRIMARY KEY  (`id`)
124                                 )",
125         overduerules    =>"(`branchcode` varchar(255) NOT NULL default '',
126                                         `categorycode` char(2) NOT NULL default '',
127                                         `delay1` int(4) default '0',
128                                         `letter1` varchar(20) default NULL,
129                                         `debarred1` char(1) default '0',
130                                         `delay2` int(4) default '0',
131                                         `debarred2` char(1) default '0',
132                                         `letter2` varchar(20) default NULL,
133                                         `delay3` int(4) default '0',
134                                         `letter3` varchar(20) default NULL,
135                                         `debarred3` int(1) default '0',
136                                         PRIMARY KEY  (`branchcode`,`categorycode`)
137                                         )",
138         cities                  => "(`cityid` int auto_increment,
139                                                 `city_name` char(100) NOT NULL,
140                                                 `city_zipcode` char(20),
141                                                 PRIMARY KEY (`cityid`)
142                                         )",
143         roadtype                        => "(`roadtypeid` int auto_increment,
144                                                 `road_type` char(100) NOT NULL,
145                                                 PRIMARY KEY (`roadtypeid`)
146                                         )",
147
148         labels                     => "(
149                                 labelid int(11) NOT NULL auto_increment,
150                                 itemnumber varchar(100) NOT NULL default '',
151                                 timestamp timestamp(14) NOT NULL,
152                                 PRIMARY KEY  (labelid)
153                                 )",
154
155         labels_conf                => "(
156                                 id int(4) NOT NULL auto_increment,
157                                 barcodetype char(100) default '',
158                                 title tinyint(1) default '0',
159                                 isbn tinyint(1) default '0',
160                                 itemtype tinyint(1) default '0',
161                                 barcode tinyint(1) default '0',
162                                 dewey tinyint(1) default '0',
163                                 class tinyint(1) default '0',
164                                 author tinyint(1) default '0',
165                                 papertype char(100) default '',
166                                 startrow int(2) default NULL,
167                                 PRIMARY KEY  (id)
168                                 )",
169
170 );
171
172 my %requirefields = (
173         subscription => { 'letter' => 'char(20) NULL', 'distributedto' => 'text NULL'},
174         itemtypes => { 'imageurl' => 'char(200) NULL'},
175         aqbookfund => { 'branchcode' => 'varchar(4) NULL'},
176         aqbudget => { 'branchcode' => 'varchar(4) NULL'},
177         auth_header => { 'marc' => 'BLOB NOT NULL', 'linkid' => 'BIGINT(20) NULL'},
178         auth_subfield_structure =>{ 'hidden' => 'TINYINT(3) NOT NULL UNSIGNED ZEROFILL', 'kohafield' => 'VARCHAR(45) NOT NULL', 'linkid' =>  'TINYINT(1) NOT NULL UNSIGNED', 'isurl' => 'TINYINT(1) UNSIGNED'},
179         statistics => { 'associatedborrower' => 'integer'},
180 #    tablename        => { 'field' => 'fieldtype' },
181 );
182
183 my %dropable_table = (
184         sessionqueries  => 'sessionqueries',
185         marcrecorddone  => 'marcrecorddone',
186         users                   => 'users',
187         itemsprices             => 'itemsprices',
188         biblioanalysis  => 'biblioanalysis',
189         borexp                  => 'borexp',
190 # tablename => 'tablename',
191 );
192
193 my %uselessfields = (
194 # tablename => "field1,field2",
195         borrowers => "suburb,altstreetaddress,altsuburb,altcity,studentnumber,school,area,preferredcont,altcp",
196         );
197 # the other hash contains other actions that can't be done elsewhere. they are done
198 # either BEFORE of AFTER everything else, depending on "when" entry (default => AFTER)
199
200 # The tabledata hash contains data that should be in the tables.
201 # The uniquefieldrequired hash entry is used to determine which (if any) fields
202 # must not exist in the table for this row to be inserted.  If the
203 # uniquefieldrequired entry is already in the table, the existing data is not
204 # modified, unless the forceupdate hash entry is also set.  Fields in the
205 # anonymous "forceupdate" hash will be forced to be updated to the default
206 # values given in the %tabledata hash.
207
208 my %tabledata = (
209 # tablename => [
210 #       {       uniquefielrequired => 'fieldname', # the primary key in the table
211 #               fieldname => fieldvalue,
212 #               fieldname2 => fieldvalue2,
213 #       },
214 # ],
215     systempreferences => [
216                 {
217             uniquefieldrequired => 'variable',
218             variable            => 'Activate_Log',
219             value               => 'On',
220             forceupdate         => { 'explanation' => 1,
221                                      'type' => 1},
222             explanation         => 'Turn Log Actions on DB On an Off',
223             type                => 'YesNo',
224         },
225         {
226             uniquefieldrequired => 'variable',
227             variable            => 'IndependantBranches',
228             value               => 0,
229             forceupdate         => { 'explanation' => 1,
230                                      'type' => 1},
231             explanation         => 'Turn Branch independancy management On an Off',
232             type                => 'YesNo',
233         },
234                 {
235             uniquefieldrequired => 'variable',
236             variable            => 'ReturnBeforeExpiry',
237             value               => 'Off',
238             forceupdate         => { 'explanation' => 1,
239                                      'type' => 1},
240             explanation         => 'If Yes, Returndate on issuing can\'t be after borrower card expiry',
241             type                => 'YesNo',
242         },
243         {
244             uniquefieldrequired => 'variable',
245             variable            => 'opacstylesheet',
246             value               => '',
247             forceupdate         => { 'explanation' => 1,
248                                      'type' => 1},
249             explanation         => 'Enter a complete URL to use an alternate stylesheet in OPAC',
250             type                => 'free',
251         },
252         {
253             uniquefieldrequired => 'variable',
254             variable            => 'opacsmallimage',
255             value               => '',
256             forceupdate         => { 'explanation' => 1,
257                                      'type' => 1},
258             explanation         => 'Enter a complete URL to an image, will be on top/left instead of the Koha logo',
259             type                => 'free',
260         },
261         {
262             uniquefieldrequired => 'variable',
263             variable            => 'opaclargeimage',
264             value               => '',
265             forceupdate         => { 'explanation' => 1,
266                                      'type' => 1},
267             explanation         => 'Enter a complete URL to an image, will be on the main page, instead of the Koha logo',
268             type                => 'free',
269         },
270         {
271             uniquefieldrequired => 'variable',
272             variable            => 'delimiter',
273             value               => ';',
274             forceupdate         => { 'explanation' => 1,
275                                      'type' => 1},
276             explanation         => 'separator for reports exported to spreadsheet',
277             type                => 'free',
278         },
279         {
280             uniquefieldrequired => 'variable',
281             variable            => 'MIME',
282             value               => 'OPENOFFICE.ORG',
283             forceupdate         => { 'explanation' => 1,
284                                      'type' => 1,
285                                      'options' => 1},
286             explanation         => 'Define the default application for report exportations into files',
287                 type            => 'Choice',
288                 options         => 'EXCEL|OPENOFFICE.ORG'
289         },
290         {
291             uniquefieldrequired => 'variable',
292             variable            => 'Delimiter',
293             value               => ';',
294                 forceupdate             => { 'explanation' => 1,
295                                      'type' => 1,
296                                      'options' => 1},
297             explanation         => 'Define the default separator character for report exportations into files',
298                 type            => 'Choice',
299                 options         => ';|tabulation|,|/|\|#'
300         },
301         {
302             uniquefieldrequired => 'variable',
303             variable            => 'SubscriptionHistory',
304             value               => ';',
305                 forceupdate             => { 'explanation' => 1,
306                                      'type' => 1,
307                                      'options' => 1},
308             explanation         => 'Define the information level for serials history in OPAC',
309                 type            => 'Choice',
310                 options         => 'simplified|full'
311         },
312         {
313             uniquefieldrequired => 'variable',
314             variable            => 'hidelostitems',
315             value               => 'No',
316             forceupdate         => { 'explanation' => 1,
317                                      'type' => 1},
318             explanation         => 'show or hide "lost" items in OPAC.',
319             type                => 'YesNo',
320         },
321                  {
322             uniquefieldrequired => 'variable',
323             variable            => 'IndependantBranches',
324             value               => '0',
325             forceupdate         => { 'explanation' => 1,
326                                      'type' => 1},
327             explanation         => 'Turn Branch independancy management On an Off',
328             type                => 'YesNo',
329         },
330                 {
331             uniquefieldrequired => 'variable',
332             variable            => 'ReturnBeforeExpiry',
333             value               => '0',
334             forceupdate         => { 'explanation' => 1,
335                                      'type' => 1},
336             explanation         => 'If Yes, Returndate on issuing can\'t be after borrower card expiry',
337             type                => 'YesNo',
338         },
339         {
340             uniquefieldrequired => 'variable',
341             variable            => 'Disable_Dictionary',
342             value               => '0',
343             forceupdate         => { 'explanation' => 1,
344                                      'type' => 1},
345             explanation         => 'Disables Dictionary buttons if set to yes',
346             type                => 'YesNo',
347         },
348         {
349             uniquefieldrequired => 'variable',
350             variable            => 'hide_marc',
351             value               => '0',
352             forceupdate         => { 'explanation' => 1,
353                                      'type' => 1},
354             explanation         => 'hide marc specific datas like subfield code & indicators to library',
355             type                => 'YesNo',
356         },
357         {
358             uniquefieldrequired => 'variable',
359             variable            => 'NotifyBorrowerDeparture',
360             value               => '0',
361             forceupdate         => { 'explanation' => 1,
362                                      'type' => 1},
363             explanation         => 'Delay before expiry where a notice is sent when issuing',
364             type                => 'Integer',
365         },
366         {
367             uniquefieldrequired => 'variable',
368             variable            => 'OpacPasswordChange',
369             value               => '1',
370             forceupdate         => { 'explanation' => 1,
371                                      'type' => 1},
372             explanation         => 'Enable/Disable password change in OPAC (disable it when using LDAP auth)',
373             type                => 'YesNo',
374         },
375         {
376             uniquefieldrequired => 'variable',
377             variable            => 'useDaysMode',
378             value               => 'Calendar',
379             forceupdate         => { 'explanation' => 1,
380                                      'type' => 1},
381             explanation                 => 'How to calculate return dates : Calendar means holidays will be controled, Days means the return date don\'t depend on holidays',
382                 type            => 'Choice',
383                 options         => 'Calendar|Days'
384         },
385         {
386             uniquefieldrequired => 'variable',
387             variable            => 'borrowerMandatoryField',
388             value               => 'zipcode|surname',
389             forceupdate         => { 'explanation' => 1,
390                                      'type' => 1},
391             explanation         => 'List all mandatory fields for borrowers',
392             type                => 'free',
393         },
394         {
395             uniquefieldrequired => 'variable',
396             variable            => 'borrowerRelationship',
397             value               => 'father|mother,grand-mother',
398             forceupdate         => { 'explanation' => 1,
399                                      'type' => 1},
400             explanation         => 'The relationships between a guarantor & a guarantee (separated by | or ,)',
401             type                => 'free',
402         },
403         {
404             uniquefieldrequired => 'variable',
405             variable            => 'ReservesMaxPickUpDelay',
406             value               => '10',
407             forceupdate         => { 'explanation' => 1,
408                                      'type' => 1},
409             explanation         => 'Maximum delay to pick up a reserved document',
410             type                => 'free',
411         },
412         {
413             uniquefieldrequired => 'variable',
414             variable            => 'TransfersMaxDaysWarning',
415             value               => '3',
416             forceupdate         => { 'explanation' => 1,
417                                      'type' => 1},
418             explanation         => 'Max delay before considering the transfer has potentialy a problem',
419             type                => 'free',
420         },
421         {
422             uniquefieldrequired => 'variable',
423             variable            => 'memberofinstitution',
424             value               => '0',
425             forceupdate         => { 'explanation' => 1,
426                                      'type' => 1},
427             explanation         => 'Are your patrons members of institutions',
428             type                => 'YesNo',
429         },
430         {
431             uniquefieldrequired => 'variable',
432             variable            => 'ReadingHistory',
433             value               => '0',
434             forceupdate         => { 'explanation' => 1,
435                                      'type' => 1},
436             explanation         => 'Allow reading record info retrievable from issues and oldissues tables',
437             type                => 'YesNo',
438         },
439         {
440             uniquefieldrequired => 'variable',
441             variable            => 'IssuingInProcess',
442             value               => '0',
443             forceupdate         => { 'explanation' => 1,
444                                      'type' => 1},
445             explanation         => 'Allow no debt alert if the patron is issuing item that accumulate debt',
446             type                => 'YesNo',
447         },
448     ],
449
450 );
451
452 my %fielddefinitions = (
453 # fieldname => [
454 #       {                 field => 'fieldname',
455 #             type    => 'fieldtype',
456 #             null    => '',
457 #             key     => '',
458 #             default => ''
459 #         },
460 #     ],
461         serial => [
462         {
463             field   => 'notes',
464             type    => 'TEXT',
465             null    => 'NULL',
466             key     => '',
467             default => '',
468             extra   => ''
469         },
470     ],
471         aqbasket =>  [
472                 {
473                         field   => 'booksellerid',
474                         type    => 'int(11)',
475                         null    => 'NOT NULL',
476                         key             => '',
477                         default => '1',
478                         extra   => '',
479                 },
480         ],
481         aqbooksellers =>  [
482                 {
483                         field   => 'listprice',
484                         type    => 'varchar(10)',
485                         null    => 'NULL',
486                         key             => '',
487                         default => '',
488                         extra   => '',
489                 },
490                 {
491                         field   => 'invoiceprice',
492                         type    => 'varchar(10)',
493                         null    => 'NULL',
494                         key             => '',
495                         default => '',
496                         extra   => '',
497                 },
498         ],
499         issues =>  [
500                 {
501                         field   => 'borrowernumber',
502                         type    => 'int(11)',
503                         null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
504                         key             => '',
505                         default => '',
506                         extra   => '',
507                 },
508                 {
509                         field   => 'itemnumber',
510                         type    => 'int(11)',
511                         null    => 'NULL', # can be null when a borrower is deleted and the foreign key rule executed
512                         key             => '',
513                         default => '',
514                         extra   => '',
515                 },
516         ],
517         borrowers => [
518                 {       field => 'B_email',
519                         type => 'text',
520                         null => 'NULL',
521                         after => 'B_zipcode',
522                  },
523                  {
524                         field => 'streetnumber', # street number (hidden if streettable table is empty)
525                         type => 'char(10)',
526                         null => 'NULL',
527                         after => 'initials',
528                 },
529                 {
530                         field => 'streettype', # street table, list builded from a system table
531                         type => 'char(50)',
532                         null => 'NULL',
533                         after => 'streetnumber',
534                 },
535                  {
536                         field => 'B_streetnumber', # street number (hidden if streettable table is empty)
537                         type => 'char(10)',
538                         null => 'NULL',
539                         after => 'fax',
540                 },
541                 {
542                         field => 'B_streettype', # street table, list builded from a system table
543                         type => 'char(50)',
544                         null => 'NULL',
545                         after => 'B_streetnumber',
546                 },
547                 {
548                         field => 'phonepro',
549                         type => 'text',
550                         null => 'NULL',
551                         after => 'fax',
552                 },
553                 {
554                         field => 'address2', # complement address
555                         type => 'text',
556                         null => 'NULL',
557                         after => 'address',
558                 },
559                 {
560                         field => 'emailpro',
561                         type => 'text',
562                         null => 'NULL',
563                         after => 'fax',
564                 },
565                 {
566                         field => 'contactfirstname', # contact's firstname
567                         type => 'text',
568                         null => 'NULL',
569                         after => 'contactname',
570                 },
571                 {
572                         field => 'contacttitle', # contact's title
573                         type => 'text',
574                         null => 'NULL',
575                         after => 'contactfirstname',
576                 },
577         ],
578         
579         branches =>  [
580                 {
581                         field   => 'branchip',
582                         type    => 'varchar(15)',
583                         null    => 'NULL',
584                         key             => '',
585                         default => '',
586                         extra   => '',
587                 },
588                 {
589                         field   => 'branchprinter',
590                         type    => 'varchar(100)',
591                         null    => 'NULL',
592                         key             => '',
593                         default => '',
594                         extra   => '',
595                 },
596         ],
597         categories =>  [
598                 {
599                         field   => 'category_type',
600                         type    => 'char(1)',
601                         null    => 'NOT NULL',
602                         key             => '',
603                         default => 'A',
604                         extra   => '',
605                 },
606         ],
607         reserves =>  [
608                 {
609                         field   => 'waitingdate',
610                         type    => 'date',
611                         null    => 'NULL',
612                         key             => '',
613                         default => '',
614                         extra   => '',
615                 },
616         ],
617 );
618
619 my %indexes = (
620 #       table => [
621 #               {       indexname => 'index detail'
622 #               }
623 #       ],
624         shelfcontents => [
625                 {       indexname => 'shelfnumber',
626                         content => 'shelfnumber',
627                 },
628                 {       indexname => 'itemnumber',
629                         content => 'itemnumber',
630                 }
631         ],
632         bibliosubject => [
633                 {       indexname => 'biblionumber',
634                         content => 'biblionumber',
635                 }
636         ],
637         items => [
638                 {       indexname => 'homebranch',
639                         content => 'homebranch',
640                 },
641                 {       indexname => 'holdingbranch',
642                         content => 'holdingbranch',
643                 }
644         ],
645         aqbooksellers => [
646                 {       indexname => 'PRIMARY',
647                         content => 'id',
648                         type => 'PRIMARY',
649                 }
650         ],
651         aqbasket => [
652                 {       indexname => 'booksellerid',
653                         content => 'booksellerid',
654                 },
655         ],
656         aqorders => [
657                 {       indexname => 'basketno',
658                         content => 'basketno',
659                 },
660         ],
661         aqorderbreakdown => [
662                 {       indexname => 'ordernumber',
663                         content => 'ordernumber',
664                 },
665                 {       indexname => 'bookfundid',
666                         content => 'bookfundid',
667                 },
668         ],
669         currency => [
670                 {       indexname => 'PRIMARY',
671                         content => 'currency',
672                         type => 'PRIMARY',
673                 }
674         ],
675 );
676
677 my %foreign_keys = (
678 #       table => [
679 #               {       key => 'the key in table' (must be indexed)
680 #                       foreigntable => 'the foreigntable name', # (the parent)
681 #                       foreignkey => 'the foreign key column(s)' # (in the parent)
682 #                       onUpdate => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
683 #                       onDelete => 'CASCADE|SET NULL|NO ACTION| RESTRICT',
684 #               }
685 #       ],
686         shelfcontents => [
687                 {       key => 'shelfnumber',
688                         foreigntable => 'bookshelf',
689                         foreignkey => 'shelfnumber',
690                         onUpdate => 'CASCADE',
691                         onDelete => 'CASCADE',
692                 },
693                 {       key => 'itemnumber',
694                         foreigntable => 'items',
695                         foreignkey => 'itemnumber',
696                         onUpdate => 'CASCADE',
697                         onDelete => 'CASCADE',
698                 },
699         ],
700         # onDelete is RESTRICT on reference tables (branches, itemtype) as we don't want items to be 
701         # easily deleted, but branches/itemtype not too easy to empty...
702         biblioitems => [
703                 {       key => 'biblionumber',
704                         foreigntable => 'biblio',
705                         foreignkey => 'biblionumber',
706                         onUpdate => 'CASCADE',
707                         onDelete => 'CASCADE',
708                 },
709                 {       key => 'itemtype',
710                         foreigntable => 'itemtypes',
711                         foreignkey => 'itemtype',
712                         onUpdate => 'CASCADE',
713                         onDelete => 'RESTRICT',
714                 },
715         ],
716         items => [
717                 {       key => 'biblioitemnumber',
718                         foreigntable => 'biblioitems',
719                         foreignkey => 'biblioitemnumber',
720                         onUpdate => 'CASCADE',
721                         onDelete => 'CASCADE',
722                 },
723                 {       key => 'homebranch',
724                         foreigntable => 'branches',
725                         foreignkey => 'branchcode',
726                         onUpdate => 'CASCADE',
727                         onDelete => 'RESTRICT',
728                 },
729                 {       key => 'holdingbranch',
730                         foreigntable => 'branches',
731                         foreignkey => 'branchcode',
732                         onUpdate => 'CASCADE',
733                         onDelete => 'RESTRICT',
734                 },
735         ],
736         additionalauthors => [
737                 {       key => 'biblionumber',
738                         foreigntable => 'biblio',
739                         foreignkey => 'biblionumber',
740                         onUpdate => 'CASCADE',
741                         onDelete => 'CASCADE',
742                 },
743         ],
744         bibliosubject => [
745                 {       key => 'biblionumber',
746                         foreigntable => 'biblio',
747                         foreignkey => 'biblionumber',
748                         onUpdate => 'CASCADE',
749                         onDelete => 'CASCADE',
750                 },
751         ],
752         aqbasket => [
753                 {       key => 'booksellerid',
754                         foreigntable => 'aqbooksellers',
755                         foreignkey => 'id',
756                         onUpdate => 'CASCADE',
757                         onDelete => 'RESTRICT',
758                 },
759         ],
760         aqorders => [
761                 {       key => 'basketno',
762                         foreigntable => 'aqbasket',
763                         foreignkey => 'basketno',
764                         onUpdate => 'CASCADE',
765                         onDelete => 'CASCADE',
766                 },
767                 {       key => 'biblionumber',
768                         foreigntable => 'biblio',
769                         foreignkey => 'biblionumber',
770                         onUpdate => 'SET NULL',
771                         onDelete => 'SET NULL',
772                 },
773         ],
774         aqbooksellers => [
775                 {       key => 'listprice',
776                         foreigntable => 'currency',
777                         foreignkey => 'currency',
778                         onUpdate => 'CASCADE',
779                         onDelete => 'CASCADE',
780                 },
781                 {       key => 'invoiceprice',
782                         foreigntable => 'currency',
783                         foreignkey => 'currency',
784                         onUpdate => 'CASCADE',
785                         onDelete => 'CASCADE',
786                 },
787         ],
788         aqorderbreakdown => [
789                 {       key => 'ordernumber',
790                         foreigntable => 'aqorders',
791                         foreignkey => 'ordernumber',
792                         onUpdate => 'CASCADE',
793                         onDelete => 'CASCADE',
794                 },
795                 {       key => 'bookfundid',
796                         foreigntable => 'aqbookfund',
797                         foreignkey => 'bookfundid',
798                         onUpdate => 'CASCADE',
799                         onDelete => 'CASCADE',
800                 },
801         ],
802         branchtransfers => [
803                 {       key => 'frombranch',
804                         foreigntable => 'branches',
805                         foreignkey => 'branchcode',
806                         onUpdate => 'CASCADE',
807                         onDelete => 'CASCADE',
808                 },
809                 {       key => 'tobranch',
810                         foreigntable => 'branches',
811                         foreignkey => 'branchcode',
812                         onUpdate => 'CASCADE',
813                         onDelete => 'CASCADE',
814                 },
815                 {       key => 'itemnumber',
816                         foreigntable => 'items',
817                         foreignkey => 'itemnumber',
818                         onUpdate => 'CASCADE',
819                         onDelete => 'CASCADE',
820                 },
821         ],
822         issuingrules => [
823                 {       key => 'categorycode',
824                         foreigntable => 'categories',
825                         foreignkey => 'categorycode',
826                         onUpdate => 'CASCADE',
827                         onDelete => 'CASCADE',
828                 },
829                 {       key => 'itemtype',
830                         foreigntable => 'itemtypes',
831                         foreignkey => 'itemtype',
832                         onUpdate => 'CASCADE',
833                         onDelete => 'CASCADE',
834                 },
835         ],
836         issues => [     # constraint is SET NULL : when a borrower or an item is deleted, we keep the issuing record
837         # for stat purposes
838                 {       key => 'borrowernumber',
839                         foreigntable => 'borrowers',
840                         foreignkey => 'borrowernumber',
841                         onUpdate => 'SET NULL',
842                         onDelete => 'SET NULL',
843                 },
844                 {       key => 'itemnumber',
845                         foreigntable => 'items',
846                         foreignkey => 'itemnumber',
847                         onUpdate => 'SET NULL',
848                         onDelete => 'SET NULL',
849                 },
850         ],
851         reserves => [
852                 {       key => 'borrowernumber',
853                         foreigntable => 'borrowers',
854                         foreignkey => 'borrowernumber',
855                         onUpdate => 'CASCADE',
856                         onDelete => 'CASCADE',
857                 },
858                 {       key => 'biblionumber',
859                         foreigntable => 'biblio',
860                         foreignkey => 'biblionumber',
861                         onUpdate => 'CASCADE',
862                         onDelete => 'CASCADE',
863                 },
864                 {       key => 'itemnumber',
865                         foreigntable => 'items',
866                         foreignkey => 'itemnumber',
867                         onUpdate => 'CASCADE',
868                         onDelete => 'CASCADE',
869                 },
870                 {       key => 'branchcode',
871                         foreigntable => 'branches',
872                         foreignkey => 'branchcode',
873                         onUpdate => 'CASCADE',
874                         onDelete => 'CASCADE',
875                 },
876         ],
877         borrowers => [ # foreign keys are RESTRICT as we don't want to delete borrowers when a branch is deleted
878         # but prevent deleting a branch as soon as it has 1 borrower !
879                 {       key => 'categorycode',
880                         foreigntable => 'categories',
881                         foreignkey => 'categorycode',
882                         onUpdate => 'RESTRICT',
883                         onDelete => 'RESTRICT',
884                 },
885                 {       key => 'branchcode',
886                         foreigntable => 'branches',
887                         foreignkey => 'branchcode',
888                         onUpdate => 'RESTRICT',
889                         onDelete => 'RESTRICT',
890                 },
891         ],
892         accountlines => [
893                 {       key => 'borrowernumber',
894                         foreigntable => 'borrowers',
895                         foreignkey => 'borrowernumber',
896                         onUpdate => 'CASCADE',
897                         onDelete => 'CASCADE',
898                 },
899                 {       key => 'itemnumber',
900                         foreigntable => 'items',
901                         foreignkey => 'itemnumber',
902                         onUpdate => 'SET NULL',
903                         onDelete => 'SET NULL',
904                 },
905         ],
906         auth_tag_structure => [
907                 {       key => 'authtypecode',
908                         foreigntable => 'auth_types',
909                         foreignkey => 'authtypecode',
910                         onUpdate => 'CASCADE',
911                         onDelete => 'CASCADE',
912                 },
913         ],
914         # FIXME : don't constraint auth_*_table and auth_word, as they may be replaced by zebra
915 );
916
917
918 # column changes
919 my %column_change = (
920         # table
921         borrowers => [
922                                 {
923                                         from => 'emailaddress',
924                                         to => 'email',
925                                         after => 'city',
926                                 },
927                                 {
928                                         from => 'streetaddress',
929                                         to => 'address',
930                                         after => 'initials',
931                                 },
932                                 {
933                                         from => 'faxnumber',
934                                         to => 'fax',
935                                         after => 'phone',
936                                 },
937                                 {
938                                         from => 'textmessaging',
939                                         to => 'opacnote',
940                                         after => 'userid',
941                                 },
942                                 {
943                                         from => 'altnotes',
944                                         to => 'contactnote',
945                                         after => 'opacnote',
946                                 },
947                                 {
948                                         from => 'physstreet',
949                                         to => 'B_address',
950                                         after => 'fax',
951                                 },
952                                 {
953                                         from => 'streetcity',
954                                         to => 'B_city',
955                                         after => 'B_address',
956                                 },
957                                 {
958                                         from => 'phoneday',
959                                         to => 'mobile',
960                                         after => 'phone',
961                                 },
962                                 {
963                                         from => 'zipcode',
964                                         to => 'zipcode',
965                                         after => 'city',
966                                 },
967                                 {
968                                         from => 'homezipcode',
969                                         to => 'B_zipcode',
970                                         after => 'B_city',
971                                 },
972                                 {
973                                         from => 'altphone',
974                                         to => 'B_phone',
975                                         after => 'B_zipcode',
976                                 },
977                                 {
978                                         from => 'expiry',
979                                         to => 'dateexpiry',
980                                         after => 'dateenrolled',
981                                 },
982                                 {
983                                         from => 'guarantor',
984                                         to => 'guarantorid',
985                                         after => 'contactname',
986                                 },
987                                 {
988                                         from => 'textmessaging',
989                                         to => 'opacnotes',
990                                         after => 'flags',
991                                 },
992                                 {
993                                         from => 'altnotes',
994                                         to => 'contactnotes',
995                                         after => 'opacnotes',
996                                 },
997                                 {
998                                         from => 'altrelationship',
999                                         to => 'relationship',
1000                                         after => 'borrowernotes',
1001                                 },
1002                         ],
1003                 );
1004                 
1005 foreach my $table (keys %column_change) {
1006         $sth = $dbh->prepare("show columns from $table");
1007         $sth->execute();
1008         undef %types;
1009         while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1010         {
1011                 $types{$column}->{type} ="$type";
1012                 $types{$column}->{null} = "$null";
1013                 $types{$column}->{key} = "$key";
1014                 $types{$column}->{default} = "$default";
1015                 $types{$column}->{extra} = "$extra";
1016         }    # while
1017         my $tablerows = $column_change{$table};
1018         foreach my $row ( @$tablerows ) {
1019                 if ($types{$row->{from}}->{type}) {
1020                         print "altering $table $row->{from} to $row->{to}\n";
1021                         # ALTER TABLE `borrowers` CHANGE `faxnumber` `fax` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL 
1022 #                       alter table `borrowers` change `faxnumber` `fax` type text  null after phone
1023                         my $sql = 
1024                                 "alter table `$table` change `$row->{from}` `$row->{to}` $types{$row->{from}}->{type} ".
1025                                 ($types{$row->{from}}->{null} eq 'YES'?" NULL":" NOT NULL").
1026                                 ($types{$row->{from}}->{default}?" default ".$types{$row->{from}}->{default}:"").
1027                                 "$types{$row->{from}}->{extra} after $row->{after} ";
1028 #                       print "$sql";
1029                         $dbh->do($sql);
1030                 }
1031         }
1032 }
1033
1034 #-------------------
1035 # Initialize
1036
1037 # Start checking
1038
1039 # Get version of MySQL database engine.
1040 my $mysqlversion = `mysqld --version`;
1041 $mysqlversion =~ /Ver (\S*) /;
1042 $mysqlversion = $1;
1043 if ( $mysqlversion ge '3.23' ) {
1044     print "Could convert to MyISAM database tables...\n" unless $silent;
1045 }
1046
1047 #---------------------------------
1048 # Tables
1049
1050 # Collect all tables into a list
1051 $sth = $dbh->prepare("show tables");
1052 $sth->execute;
1053 while ( my ($table) = $sth->fetchrow ) {
1054     $existingtables{$table} = 1;
1055 }
1056
1057
1058 # Now add any missing tables
1059 foreach $table ( keys %requiretables ) {
1060     unless ( $existingtables{$table} ) {
1061         print "Adding $table table...\n" unless $silent;
1062         my $sth = $dbh->prepare("create table $table $requiretables{$table}");
1063         $sth->execute;
1064         if ( $sth->err ) {
1065             print "Error : $sth->errstr \n";
1066             $sth->finish;
1067         }    # if error
1068     }    # unless exists
1069 }    # foreach
1070
1071 # now drop useless tables
1072 foreach $table ( keys %dropable_table ) {
1073         if ( $existingtables{$table} ) {
1074                 print "Dropping unused table $table\n" if $debug and not $silent;
1075                 $dbh->do("drop table $table");
1076                 if ( $dbh->err ) {
1077                         print "Error : $dbh->errstr \n";
1078                 }
1079         }
1080 }
1081
1082 #---------------------------------
1083 # Columns
1084
1085 foreach $table ( keys %requirefields ) {
1086     print "Check table $table\n" if $debug and not $silent;
1087     $sth = $dbh->prepare("show columns from $table");
1088     $sth->execute();
1089     undef %types;
1090     while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1091     {
1092         $types{$column} = $type;
1093     }    # while
1094     foreach $column ( keys %{ $requirefields{$table} } ) {
1095         print "  Check column $column  [$types{$column}]\n" if $debug and not $silent;
1096         if ( !$types{$column} ) {
1097
1098             # column doesn't exist
1099             print "Adding $column field to $table table...\n" unless $silent;
1100             $query = "alter table $table
1101                         add column $column " . $requirefields{$table}->{$column};
1102             print "Execute: $query\n" if $debug;
1103             my $sti = $dbh->prepare($query);
1104             $sti->execute;
1105             if ( $sti->err ) {
1106                 print "**Error : $sti->errstr \n";
1107                 $sti->finish;
1108             }    # if error
1109         }    # if column
1110     }    # foreach column
1111 }    # foreach table
1112
1113 foreach $table ( keys %fielddefinitions ) {
1114         print "Check table $table\n" if $debug;
1115         $sth = $dbh->prepare("show columns from $table");
1116         $sth->execute();
1117         my $definitions;
1118         while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1119         {
1120                 $definitions->{$column}->{type}    = $type;
1121                 $definitions->{$column}->{null}    = $null;
1122                 $definitions->{$column}->{null}    = 'NULL' if $null eq 'YES';
1123                 $definitions->{$column}->{key}     = $key;
1124                 $definitions->{$column}->{default} = $default;
1125                 $definitions->{$column}->{extra}   = $extra;
1126         }    # while
1127         my $fieldrow = $fielddefinitions{$table};
1128         foreach my $row (@$fieldrow) {
1129                 my $field   = $row->{field};
1130                 my $type    = $row->{type};
1131                 my $null    = $row->{null};
1132 #               $null    = 'YES' if $row->{null} eq 'NULL';
1133                 my $key     = $row->{key};
1134                 my $default = $row->{default};
1135                 my $null    = $row->{null};
1136 #               $default="''" unless $default;
1137                 my $extra   = $row->{extra};
1138                 my $def     = $definitions->{$field};
1139                 my $after       = ($row->{after}?" after ".$row->{after}:"");
1140
1141                 unless ( $type eq $def->{type}
1142                         && $null eq $def->{null}
1143                         && $key eq $def->{key}
1144                         && $extra eq $def->{extra} )
1145                 {
1146                         if ( $null eq '' ) {
1147                                 $null = 'NOT NULL';
1148                         }
1149                         if ( $key eq 'PRI' ) {
1150                                 $key = 'PRIMARY KEY';
1151                         }
1152                         unless ( $extra eq 'auto_increment' ) {
1153                                 $extra = '';
1154                         }
1155
1156                         # if it's a new column use "add", if it's an old one, use "change".
1157                         my $action;
1158                         if ($definitions->{$field}->{type}) {
1159                                 $action="change $field"
1160                         } else {
1161                                 $action="add";
1162                         }
1163 # if it's a primary key, drop the previous pk, before altering the table
1164                         my $sth;
1165                         if ($key ne 'PRIMARY KEY') {
1166                                 $sth =$dbh->prepare("alter table $table $action $field $type $null $key $extra default ? $after");
1167                         } else {
1168                                 $sth =$dbh->prepare("alter table $table drop primary key, $action $field $type $null $key $extra default ? $after");
1169                         }
1170                         $sth->execute($default);
1171                         print "  alter or create $field in $table\n" unless $silent;
1172                 }
1173         }
1174 }
1175
1176 # Populate tables with required data
1177
1178
1179 # synch table and deletedtable.
1180 foreach my $table (('borrowers','items','biblio','biblioitems')) {
1181         my %deletedborrowers;
1182         print "synch'ing $table\n";
1183         $sth = $dbh->prepare("show columns from deleted$table");
1184         $sth->execute;
1185         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1186                 $deletedborrowers{$column}=1;
1187         }
1188         $sth = $dbh->prepare("show columns from $table");
1189         $sth->execute;
1190         my $previous;
1191         while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ) {
1192                 unless ($deletedborrowers{$column}) {
1193                         my $newcol="alter table deleted$table add $column $type";
1194                         if ($null eq 'YES') {
1195                                 $newcol .= " NULL ";
1196                         } else {
1197                                 $newcol .= " NOT NULL ";
1198                         }
1199                         $newcol .= "default $default" if $default;
1200                         $newcol .= " after $previous" if $previous;
1201                         $previous=$column;
1202                         print "creating column $column\n";
1203                         $dbh->do($newcol);
1204                 }
1205         }
1206 }
1207
1208 foreach my $table ( keys %tabledata ) {
1209     print "Checking for data required in table $table...\n" unless $silent;
1210     my $tablerows = $tabledata{$table};
1211     foreach my $row (@$tablerows) {
1212         my $uniquefieldrequired = $row->{uniquefieldrequired};
1213         my $uniquevalue         = $row->{$uniquefieldrequired};
1214         my $forceupdate         = $row->{forceupdate};
1215         my $sth                 =
1216           $dbh->prepare(
1217 "select $uniquefieldrequired from $table where $uniquefieldrequired=?"
1218         );
1219         $sth->execute($uniquevalue);
1220                 if ($sth->rows) {
1221                         foreach my $field (keys %$forceupdate) {
1222                                 if ($forceupdate->{$field}) {
1223                                         my $sth=$dbh->prepare("update systempreferences set $field=? where $uniquefieldrequired=?");
1224                                         $sth->execute($row->{$field}, $uniquevalue);
1225                                 }
1226                 }
1227                 } else {
1228                         print "Adding row to $table: " unless $silent;
1229                         my @values;
1230                         my $fieldlist;
1231                         my $placeholders;
1232                         foreach my $field ( keys %$row ) {
1233                                 next if $field eq 'uniquefieldrequired';
1234                                 next if $field eq 'forceupdate';
1235                                 my $value = $row->{$field};
1236                                 push @values, $value;
1237                                 print "  $field => $value" unless $silent;
1238                                 $fieldlist .= "$field,";
1239                                 $placeholders .= "?,";
1240                         }
1241                         print "\n" unless $silent;
1242                         $fieldlist    =~ s/,$//;
1243                         $placeholders =~ s/,$//;
1244                         my $sth =
1245                         $dbh->prepare(
1246                                 "insert into $table ($fieldlist) values ($placeholders)");
1247                         $sth->execute(@values);
1248                 }
1249         }
1250 }
1251
1252 #
1253 # check indexes and create them when needed
1254 #
1255 print "Checking for index required...\n" unless $silent;
1256 foreach my $table ( keys %indexes ) {
1257         #
1258         # read all indexes from $table
1259         #
1260         $sth = $dbh->prepare("show index from $table");
1261         $sth->execute;
1262         my %existingindexes;
1263         while ( my ( $table, $non_unique, $key_name, $Seq_in_index, $Column_name, $Collation, $cardinality, $sub_part, $Packed, $comment ) = $sth->fetchrow ) {
1264                 $existingindexes{$key_name} = 1;
1265         }
1266         # read indexes to check
1267         my $tablerows = $indexes{$table};
1268         foreach my $row (@$tablerows) {
1269                 my $key_name=$row->{indexname};
1270                 if ($existingindexes{$key_name} eq 1) {
1271 #                       print "$key_name existing";
1272                 } else {
1273                         print "\tCreating index $key_name in $table\n";
1274                         my $sql;
1275                         if ($row->{indexname} eq 'PRIMARY') {
1276                                 $sql = "alter table $table ADD PRIMARY KEY ($row->{content})";
1277                         } else {
1278                                 $sql = "alter table $table ADD INDEX $key_name ($row->{content}) $row->{type}";
1279                         }
1280                         $dbh->do($sql);
1281             print "Error $sql : $dbh->err \n" if $dbh->err;
1282                 }
1283         }
1284 }
1285
1286 #
1287 # check foreign keys and create them when needed
1288 #
1289 print "Checking for foreign keys required...\n" unless $silent;
1290 foreach my $table ( keys %foreign_keys ) {
1291         #
1292         # read all indexes from $table
1293         #
1294         $sth = $dbh->prepare("show table status like '$table'");
1295         $sth->execute;
1296         my $stat = $sth->fetchrow_hashref;
1297         # read indexes to check
1298         my $tablerows = $foreign_keys{$table};
1299         foreach my $row (@$tablerows) {
1300                 my $foreign_table=$row->{foreigntable};
1301                 if ($stat->{'Comment'} =~/$foreign_table/) {
1302 #                       print "$foreign_table existing\n";
1303                 } else {
1304                         print "\tCreating foreign key $foreign_table in $table\n";
1305                         # first, drop any orphan value in child table
1306                         if ($row->{onDelete} ne "RESTRICT") {
1307                                 my $sql = "delete from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})";
1308                                 $dbh->do($sql);
1309                                 print "SQL ERROR: $sql : $dbh->err \n" if $dbh->err;
1310                         }
1311                         my $sql="alter table $table ADD FOREIGN KEY $row->{key} ($row->{key}) REFERENCES $row->{foreigntable} ($row->{foreignkey})";
1312                         $sql .= " on update ".$row->{onUpdate} if $row->{onUpdate};
1313                         $sql .= " on delete ".$row->{onDelete} if $row->{onDelete};
1314                         $dbh->do($sql);
1315                         if ($dbh->err) {
1316                                 print "====================
1317 An error occured during :
1318 \t$sql
1319 It probably means there is something wrong in your DB : a row ($table.$row->{key}) refers to a value in $row->{foreigntable}.$row->{foreignkey} that does not exist. solve the problem and run updater again (or just the previous SQL statement).
1320 You can find those values with select
1321 \t$table.* from $table where $row->{key} not in (select $row->{foreignkey} from $row->{foreigntable})
1322 ====================\n
1323 ";
1324                         }
1325                 }
1326         }
1327 }
1328
1329 #
1330 # SPECIFIC STUFF
1331 #
1332 #
1333 # create frameworkcode row in biblio table & fill it with marc_biblio.frameworkcode.
1334 #
1335
1336 # 1st, get how many biblio we will have to do...
1337 $sth = $dbh->prepare('select count(*) from marc_biblio');
1338 $sth->execute;
1339 my ($totaltodo) = $sth->fetchrow;
1340
1341 $sth = $dbh->prepare("show columns from biblio");
1342 $sth->execute();
1343 my $definitions;
1344 my $bibliofwexist=0;
1345 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1346         $bibliofwexist=1 if $column eq 'frameworkcode';
1347 }
1348 unless ($bibliofwexist) {
1349         print "moving biblioframework to biblio table\n";
1350         $dbh->do('ALTER TABLE `biblio` ADD `frameworkcode` VARCHAR( 4 ) NOT NULL AFTER `biblionumber`');
1351         $sth = $dbh->prepare('select biblionumber,frameworkcode from marc_biblio');
1352         $sth->execute;
1353         my $sth_update = $dbh->prepare('update biblio set frameworkcode=? where biblionumber=?');
1354         my $totaldone=0;
1355         while (my ($biblionumber,$frameworkcode) = $sth->fetchrow) {
1356                 $sth_update->execute($frameworkcode,$biblionumber);
1357                 $totaldone++;
1358                 print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1359         }
1360         print "\rdone\n";
1361 }
1362
1363 #
1364 # moving MARC data from marc_subfield_table to biblioitems.marc
1365 #
1366 $sth = $dbh->prepare("show columns from biblioitems");
1367 $sth->execute();
1368 my $definitions;
1369 my $marcdone=0;
1370 while ( ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow ){
1371         $marcdone=1 if ($type eq 'blob' && $column eq 'marc') ;
1372 }
1373 unless ($marcdone) {
1374         print "moving MARC record to biblioitems table\n";
1375         # changing marc field type
1376         $dbh->do('ALTER TABLE `biblioitems` CHANGE `marc` `marc` BLOB NULL DEFAULT NULL ');
1377         # adding marc xml, just for convenience
1378         $dbh->do('ALTER TABLE `biblioitems` ADD `marcxml` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ');
1379         # moving data from marc_subfield_value to biblio
1380         $sth = $dbh->prepare('select bibid,biblionumber from marc_biblio');
1381         $sth->execute;
1382         my $sth_update = $dbh->prepare('update biblioitems set marc=?, marcxml=? where biblionumber=?');
1383         my $totaldone=0;
1384         while (my ($bibid,$biblionumber) = $sth->fetchrow) {
1385                 my $record = MARCgetbiblio($dbh,$bibid);
1386         #Force UTF-8 in record leader
1387                 $record->encoding('UTF-8');
1388                 print $record->as_formatted if ($biblionumber==3902);
1389                 $sth_update->execute($record->as_usmarc(),$record->as_xml_record(),$biblionumber);
1390                 $totaldone++;
1391                 print "\r$totaldone / $totaltodo" unless ($totaldone % 100);
1392         }
1393         print "\rdone\n";
1394 }
1395
1396
1397 # at last, remove useless fields
1398 foreach $table ( keys %uselessfields ) {
1399         my @fields = split /,/,$uselessfields{$table};
1400         my $fields;
1401         my $exists;
1402         foreach my $fieldtodrop (@fields) {
1403                 $fieldtodrop =~ s/\t//g;
1404                 $fieldtodrop =~ s/\n//g;
1405                 $exists =0;
1406                 $sth = $dbh->prepare("show columns from $table");
1407                 $sth->execute;
1408                 while ( my ( $column, $type, $null, $key, $default, $extra ) = $sth->fetchrow )
1409                 {
1410                         $exists =1 if ($column eq $fieldtodrop);
1411                 }
1412                 if ($exists) {
1413                         print "deleting $fieldtodrop field in $table...\n" unless $silent;
1414                         my $sth = $dbh->prepare("alter table $table drop $fieldtodrop");
1415                         $sth->execute;
1416                 }
1417         }
1418 }    # foreach
1419
1420
1421 # MOVE all tables TO UTF-8 and innoDB
1422 $sth = $dbh->prepare("show table status");
1423 $sth->execute;
1424 while ( my $table = $sth->fetchrow_hashref ) {
1425 #       if ($table->{Engine} ne 'InnoDB') {
1426 #               $dbh->do("ALTER TABLE $table->{Name} TYPE = innodb");
1427 #               print "moving $table->{Name} to InnoDB\n";
1428 #       }
1429         unless ($table->{Collation} =~ /^utf8/) {
1430                 $dbh->do("ALTER TABLE $table->{Name} CONVERT TO CHARACTER SET utf8");
1431                 $dbh->do("ALTER TABLE $table->{Name} DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci");
1432                 # FIXME : maybe a ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8 would be better, def char set seems to work fine. If any problem encountered, let's try with convert !
1433                 print "moving $table->{Name} to utf8\n";
1434         } else {
1435         }
1436 }
1437
1438 $sth->finish;
1439
1440 #
1441 # those 2 subs are a copy of Biblio.pm, version 2.2.4
1442 # they are useful only once, for moving from 2.2 to 3.0
1443 # the MARCgetbiblio & MARCgetitem subs in Biblio.pm
1444 # are still here, but uses other tables
1445 # (the ones that are filled by updatedatabase !)
1446 #
1447
1448 sub MARCgetbiblio {
1449
1450     # Returns MARC::Record of the biblio passed in parameter.
1451     my ( $dbh, $bibid ) = @_;
1452     my $record = MARC::Record->new();
1453 #       warn "". $bidid;
1454
1455     my $sth =
1456       $dbh->prepare(
1457 "select bibid,subfieldid,tag,tagorder,tag_indicator,subfieldcode,subfieldorder,subfieldvalue,valuebloblink
1458                                  from marc_subfield_table
1459                                  where bibid=? order by tag,tagorder,subfieldorder
1460                          "
1461     );
1462     my $sth2 =
1463       $dbh->prepare(
1464         "select subfieldvalue from marc_blob_subfield where blobidlink=?");
1465     $sth->execute($bibid);
1466     my $prevtagorder = 1;
1467     my $prevtag      = 'XXX';
1468     my $previndicator;
1469     my $field;        # for >=10 tags
1470     my $prevvalue;    # for <10 tags
1471     while ( my $row = $sth->fetchrow_hashref ) {
1472
1473         if ( $row->{'valuebloblink'} ) {    #---- search blob if there is one
1474             $sth2->execute( $row->{'valuebloblink'} );
1475             my $row2 = $sth2->fetchrow_hashref;
1476             $sth2->finish;
1477             $row->{'subfieldvalue'} = $row2->{'subfieldvalue'};
1478         }
1479         if ( $row->{tagorder} ne $prevtagorder || $row->{tag} ne $prevtag ) {
1480             $previndicator .= "  ";
1481             if ( $prevtag < 10 ) {
1482                                 if ($prevtag ne '000') {
1483                         $record->add_fields( ( sprintf "%03s", $prevtag ), $prevvalue ) unless $prevtag eq "XXX";    # ignore the 1st loop
1484                                 } else {
1485                                         $record->leader(sprintf("%24s",$prevvalue));
1486                                 }
1487             }
1488             else {
1489                 $record->add_fields($field) unless $prevtag eq "XXX";
1490             }
1491             undef $field;
1492             $prevtagorder  = $row->{tagorder};
1493             $prevtag       = $row->{tag};
1494             $previndicator = $row->{tag_indicator};
1495             if ( $row->{tag} < 10 ) {
1496                 $prevvalue = $row->{subfieldvalue};
1497             }
1498             else {
1499                 $field = MARC::Field->new(
1500                     ( sprintf "%03s", $prevtag ),
1501                     substr( $row->{tag_indicator} . '  ', 0, 1 ),
1502                     substr( $row->{tag_indicator} . '  ', 1, 1 ),
1503                     $row->{'subfieldcode'},
1504                     $row->{'subfieldvalue'}
1505                 );
1506             }
1507         }
1508         else {
1509             if ( $row->{tag} < 10 ) {
1510                 $record->add_fields( ( sprintf "%03s", $row->{tag} ),
1511                     $row->{'subfieldvalue'} );
1512             }
1513             else {
1514                 $field->add_subfields( $row->{'subfieldcode'},
1515                     $row->{'subfieldvalue'} );
1516             }
1517             $prevtag       = $row->{tag};
1518             $previndicator = $row->{tag_indicator};
1519         }
1520     }
1521
1522     # the last has not been included inside the loop... do it now !
1523     if ( $prevtag ne "XXX" )
1524     { # check that we have found something. Otherwise, prevtag is still XXX and we
1525          # must return an empty record, not make MARC::Record fail because we try to
1526          # create a record with XXX as field :-(
1527         if ( $prevtag < 10 ) {
1528             $record->add_fields( $prevtag, $prevvalue );
1529         }
1530         else {
1531
1532             #           my $field = MARC::Field->new( $prevtag, "", "", %subfieldlist);
1533             $record->add_fields($field);
1534         }
1535     }
1536     return $record;
1537 }
1538
1539 sub MARCgetitem {
1540
1541     # Returns MARC::Record of the biblio passed in parameter.
1542     my ( $dbh, $bibid, $itemnumber ) = @_;
1543     my $record = MARC::Record->new();
1544
1545     # search MARC tagorder
1546     my $sth2 =
1547       $dbh->prepare(
1548 "select tagorder from marc_subfield_table,marc_subfield_structure where marc_subfield_table.tag=marc_subfield_structure.tagfield and marc_subfield_table.subfieldcode=marc_subfield_structure.tagsubfield and bibid=? and kohafield='items.itemnumber' and subfieldvalue=?"
1549     );
1550     $sth2->execute( $bibid, $itemnumber );
1551     my ($tagorder) = $sth2->fetchrow_array();
1552
1553     #---- TODO : the leader is missing
1554     my $sth =
1555       $dbh->prepare(
1556 "select bibid,subfieldid,tag,tagorder,tag_indicator,subfieldcode,subfieldorder,subfieldvalue,valuebloblink
1557                                  from marc_subfield_table
1558                                  where bibid=? and tagorder=? order by subfieldcode,subfieldorder
1559                          "
1560     );
1561     $sth2 =
1562       $dbh->prepare(
1563         "select subfieldvalue from marc_blob_subfield where blobidlink=?");
1564     $sth->execute( $bibid, $tagorder );
1565     while ( my $row = $sth->fetchrow_hashref ) {
1566         if ( $row->{'valuebloblink'} ) {    #---- search blob if there is one
1567             $sth2->execute( $row->{'valuebloblink'} );
1568             my $row2 = $sth2->fetchrow_hashref;
1569             $sth2->finish;
1570             $row->{'subfieldvalue'} = $row2->{'subfieldvalue'};
1571         }
1572         if ( $record->field( $row->{'tag'} ) ) {
1573             my $field;
1574
1575 #--- this test must stay as this, because of strange behaviour of mySQL/Perl DBI with char var containing a number...
1576             #--- sometimes, eliminates 0 at beginning, sometimes no ;-\\\
1577             if ( length( $row->{'tag'} ) < 3 ) {
1578                 $row->{'tag'} = "0" . $row->{'tag'};
1579             }
1580             $field = $record->field( $row->{'tag'} );
1581             if ($field) {
1582                 my $x =
1583                   $field->add_subfields( $row->{'subfieldcode'},
1584                     $row->{'subfieldvalue'} );
1585                 $record->delete_field($field);
1586                 $record->add_fields($field);
1587             }
1588         }
1589         else {
1590             if ( length( $row->{'tag'} ) < 3 ) {
1591                 $row->{'tag'} = "0" . $row->{'tag'};
1592             }
1593             my $temp =
1594               MARC::Field->new( $row->{'tag'}, " ", " ",
1595                 $row->{'subfieldcode'} => $row->{'subfieldvalue'} );
1596             $record->add_fields($temp);
1597         }
1598
1599     }
1600     return $record;
1601 }
1602
1603
1604 exit;
1605
1606 # $Log$
1607 # Revision 1.143  2006/06/07 02:02:47  bob_lyon
1608 # merging katipo changes...
1609 #
1610 # adding new preference IssuingInProcess
1611 #
1612 # Revision 1.142  2006/06/06 23:42:46  bob_lyon
1613 # Merging Katipo changes...
1614 #
1615 # Adding new system pref where one can still retrieve a correct reading
1616 # record history if one has moved older data from issues to oldissues table
1617 # to speed up issues speed
1618 #
1619 # Revision 1.141  2006/06/01 03:18:11  rangi
1620 # Adding a new column to the statistics table
1621 #
1622 # Revision 1.140  2006/05/22 22:40:45  rangi
1623 # Adding new systempreference allowing for the library to add borrowers to institutions (rest homes, parishes, schools, classes etc).
1624 #
1625 # Revision 1.139  2006/05/19 19:31:29  tgarip1957
1626 # Added new fields to auth_header and auth_subfield_table to allow ZEBRA use of authorities and new MARC framework like structure.
1627 # Authority tables are modified to be compatible with new MARC frameworks. This change is part of Authority Linking & Zebra authorities. Requires change in Mysql database. It will break head unless all changes regarding this is implemented. This warning will take place on all commits regarding this
1628 #
1629 # Revision 1.138  2006/05/19 16:51:44  alaurin
1630 # update database for :
1631 # - new feature ip and printer management
1632 # adding two fields in branches table (branchip,branchprinter)
1633 #
1634 # - waiting date : adding one field in reserves table(waiting date) to calculate the Maximum delay to pick up a reserved document when it's available
1635 #
1636 # new system preference :
1637 # - ReservesMaxPickUpDelay : Maximum delay to pick up a reserved document
1638 # TransfersMaxDaysWarning : Max delay before considering the transfer as potentialy a problem
1639 #
1640 # Revision 1.137  2006/04/18 09:36:36  plg
1641 # bug fixed: typo fixed in labels and labels_conf tables creation query.
1642 #
1643 # Revision 1.136  2006/04/17 21:55:33  sushi
1644 # Added 'labels' and 'labels_conf' tables, for spine lable tool.
1645 #
1646 # Revision 1.135  2006/04/15 02:37:03  tgarip1957
1647 # Marc record should be set to UTF-8 in leader.Force it.
1648 # XML should be with<record> wrappers
1649 #
1650 # Revision 1.134  2006/04/14 09:37:29  tipaul
1651 # improvements from SAN Ouest Provence :
1652 # * introducing a category_type into categories. It can be A (adult), C (children), P (Professionnal), I (institution/organisation).
1653 # * each category_type has it's own forms to create members.
1654 # * the borrowers table has been heavily modified (many fields changed), to get something more logic & readable
1655 # * reintroducing guarantor/guanrantee system that is now independant from hardcoded C/A for categories
1656 # * updating templates to fit template rules
1657 #
1658 # (see mail feb, 17 on koha-devel "new features for borrowers" for more details)
1659 #
1660 # Revision 1.133  2006/04/13 08:36:42  plg
1661 # new: function C4::Date::get_date_format_string_for_DHTMLcalendar based on
1662 # the system preference prefered date format.
1663 #
1664 # improvement: book fund list and budget list screen redesigned. Filters on
1665 # each field. Columns are not sortable yet. Using DHTML Calendar to fill date
1666 # fields instead of manual filling. Pagination system. From the book fund
1667 # list, you can reach the budget list, filtered on a book fund, or not. A
1668 # budget can be added only from book fund list screen.
1669 #
1670 # bug fixed: branchcode was missing in table aqbudget.
1671 #
1672 # bug fixed: when setting a branchcode to a book fund, all associated budgets
1673 # move to this branchcode.
1674 #
1675 # modification: when adding/modifying budget/fund, MySQL specific "REPLACE..."
1676 # statements replaced by standard SQL compliant statement.
1677 #
1678 # bug fixed: when adding/modifying a budget, if the book fund is associated to
1679 # a branch, the branch selection is disabled and set to the book fund branch.
1680 #
1681 # Revision 1.132  2006/04/06 12:37:05  hdl
1682 # Bugfixing : aqbookfund needed a field.
1683 #
1684 # Revision 1.131  2006/03/03 17:02:22  tipaul
1685 # commit for holidays and news management.
1686 # (some forgotten files)
1687 #
1688 # Revision 1.130  2006/03/03 16:35:21  tipaul
1689 # commit for holidays and news management.
1690 #
1691 # Contrib from Tümer Garip (from Turkey) :
1692 # * holiday :
1693 # in /tools/ the holiday.pl script let you define holidays (days where the library is closed), branch by branch. You can define 3 types of holidays :
1694 # - single day : only this day is closed
1695 # - repet weekly (like "sunday") : the day is holiday every week
1696 # - repet yearly (like "July, 4") : this day is closed every year.
1697 #
1698 # You can also put exception :
1699 # - sunday is holiday, but "2006 March, 5th" the library will be open
1700 #
1701 # The holidays are used for return date calculation : the return date is set to the next date where the library is open. A systempreference (useDaysMode) set ON (Calendar) or OFF (Normal) the calendar calculation.
1702 #
1703 # Revision 1.129  2006/02/27 18:19:33  hdl
1704 # New table used in overduerules.pl tools page.
1705 #
1706 # Revision 1.128  2006/01/25 15:16:06  tipaul
1707 # updating DB :
1708 # * removing useless tables
1709 # * adding useful indexes
1710 # * altering some columns definitions
1711 # * The goal being to have updater working fine for foreign keys.
1712 #
1713 # For me it's done, let me know if it works for you. You can see an updated schema of the DB (with constraints) on the wiki
1714 #
1715 # Revision 1.127  2006/01/24 17:57:17  tipaul
1716 # DB improvements : adding foreign keys on some tables. partial stuff done.
1717 #
1718 # Revision 1.126  2006/01/06 16:39:42  tipaul
1719 # synch'ing head and rel_2_2 (from 2.2.5, including npl templates)
1720 # Seems not to break too many things, but i'm probably wrong here.
1721 # at least, new features/bugfixes from 2.2.5 are here (tested on some features on my head local copy)
1722 #
1723 # - removing useless directories (koha-html and koha-plucene)
1724 #
1725 # Revision 1.125  2006/01/04 15:54:55  tipaul
1726 # utf8 is a : go for beta test in HEAD.
1727 # some explanations :
1728 # - updater/updatedatabase => will transform all tables in innoDB (not related to utf8, just to warn you) AND collate them in utf8 / utf8_general_ci. The SQL command is : ALTER TABLE tablename DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci.
1729 # - *-top.inc will show the pages in utf8
1730 # - THE HARD THING : for me, mysql-client and mysql-server were set up to communicate in iso8859-1, whatever the mysql collation ! Thus, pages were improperly shown, as datas were transmitted in iso8859-1 format ! After a full day of investigation, someone on usenet pointed "set NAMES 'utf8'" to explain that I wanted utf8. I could put this in my.cnf, but if I do that, ALL databases will "speak" in utf8, that's not what we want. Thus, I added a line in Context.pm : everytime a DB handle is opened, the communication is set to utf8.
1731 # - using marcxml field and no more the iso2709 raw marc biblioitems.marc field.
1732 #
1733 # Revision 1.124  2005/10/27 12:09:05  tipaul
1734 # new features for serial module :
1735 # - the last 5 issues are now shown, and their status can be changed (but not reverted to "waited", as there can be only one "waited")
1736 # - the library can create a "distribution list". this paper contains a list of borrowers (selected from the borrower list, or manually entered), and print it for a given issue. once printed, the sheet can be put on the issue and distributed to every reader on the list (one by one).
1737 #
1738 # Revision 1.123  2005/10/26 09:13:37  tipaul
1739 # big commit, still breaking things...
1740 #
1741 # * synch with rel_2_2. Probably the last non manual synch, as rel_2_2 should not be modified deeply.
1742 # * code cleaning (cleaning warnings from perl -w) continued
1743 #
1744 # Revision 1.122  2005/09/02 14:18:38  tipaul
1745 # new feature : image for itemtypes.
1746 #
1747 # * run updater/updatedatabase to create imageurl field in itemtypes.
1748 # * go to Koha >> parameters >> itemtypes >> modify (or add) an itemtype. You will see around 20 nice images to choose between (thanks to owen). If you prefer your own image, you also can type a complete url (http://www.myserver.lib/path/to/my/image.gif)
1749 # * go to OPAC, and search something. In the result list, you now have the picture instead of the text itemtype.
1750 #
1751 # Revision 1.121  2005/08/24 08:49:03  hdl
1752 # Adding a note field in serial table.
1753 # This will allow librarian to mention a note on a peculiar waiting serial number.
1754 #
1755 # Revision 1.120  2005/08/09 14:10:32  tipaul
1756 # 1st commit to go to zebra.
1757 # don't update your cvs if you want to have a working head...
1758 #
1759 # this commit contains :
1760 # * updater/updatedatabase : get rid with marc_* tables, but DON'T remove them. As a lot of things uses them, it would not be a good idea for instance to drop them. If you really want to play, you can rename them to test head without them but being still able to reintroduce them...
1761 # * Biblio.pm : modify MARCgetbiblio to find the raw marc record in biblioitems.marc field, not from marc_subfield_table, modify MARCfindframeworkcode to find frameworkcode in biblio.frameworkcode, modify some other subs to use biblio.biblionumber & get rid of bibid.
1762 # * other files : get rid of bibid and use biblionumber instead.
1763 #
1764 # What is broken :
1765 # * does not do anything on zebra yet.
1766 # * if you rename marc_subfield_table, you can't search anymore.
1767 # * you can view a biblio & bibliodetails, go to MARC editor, but NOT save any modif.
1768 # * don't try to add a biblio, it would add data poorly... (don't try to delete either, it may work, but that would be a surprise ;-) )
1769 #
1770 # IMPORTANT NOTE : you need MARC::XML package (http://search.cpan.org/~esummers/MARC-XML-0.7/lib/MARC/File/XML.pm), that requires a recent version of MARC::Record
1771 # Updatedatabase stores the iso2709 data in biblioitems.marc field & an xml version in biblioitems.marcxml Not sure we will keep it when releasing the stable version, but I think it's a good idea to have something readable in sql, at least for development stage.
1772 #
1773 # Revision 1.119  2005/08/04 16:07:58  tipaul
1774 # Synch really broke this script...
1775 #
1776 # Revision 1.118  2005/08/04 16:02:55  tipaul
1777 # oops... error in synch between 2.2 and head
1778 #
1779 # Revision 1.117  2005/08/04 14:24:39  tipaul
1780 # synch'ing 2.2 and head
1781 #
1782 # Revision 1.116  2005/08/04 08:55:54  tipaul
1783 # Letters / alert system, continuing...
1784 #
1785 # * adding a package Letters.pm, that manages Letters & alerts.
1786 # * adding feature : it's now possible to define a "letter" for any subscription created. If a letter is defined, users in OPAC can put an alert on the subscription. When an issue is marked "arrived", all users in the alert will recieve a mail (as defined in the "letter"). This last part (= send the mail) is not yet developped. (Should be done this week)
1787 # * adding feature : it's now possible to "put to an alert" in OPAC, for any serial subscription. The alert is stored in a new table, called alert. An alert can be put only if the librarian has activated them in subscription (and they activate it just by choosing a "letter" to sent to borrowers on new issues)
1788 # * adding feature : librarian can see in borrower detail which alerts they have put, and a user can see in opac-detail which alert they have put too.
1789 #
1790 # Note that the system should be generic enough to manage any type of alert.
1791 # I plan to extend it soon to virtual shelves : a borrower will be able to put an alert on a virtual shelf, to be warned when something is changed in the virtual shelf (mail being sent once a day by cron, or manually by the shelf owner. Anyway, a mail won't be sent on every change, users would be spammed by Koha ;-) )
1792 #
1793 # Revision 1.115  2005/08/02 16:15:34  tipaul
1794 # adding 2 fields to letter system :
1795 # * module (acquisition, catalogue...) : it will be usefull to show the librarian only letters he may be interested by.
1796 # * title, that will be used as mail subject.
1797 #
1798 # Revision 1.114  2005/07/28 15:10:13  tipaul
1799 # Introducing new "Letters" system : Letters will be used everytime you want to sent something to someone (through mail or paper). For example, sending a mail for overdues use letter that you can put as parameters. Sending a mail to a borrower when a suggestion is validated uses a letter too.
1800 # the letter table contains 3 fields :
1801 # * code => the code of the letter
1802 # * name => the complete name of the letter
1803 # * content => the complete text. It's a TEXT field type, so has no limits.
1804 #
1805 # My next goal now is to work on point 2-I "serial issue alert"
1806 # With this feature, in serials, a user can subscribe the "issue alert". For every issue arrived/missing, a mail is sent to all subscribers of this list. The mail warns the user that the issue is arrive or missing. Will be in head.
1807 # (see mail on koha-devel, 2005/04/07)
1808 #
1809 # The "serial issue alert" will be the 1st to use this letter system that probably needs some tweaking ;-)
1810 #
1811 # Once it will be stabilised default letters (in any languages) could be added during installer to help the library begin with this new feature.
1812 #
1813 # Revision 1.113  2005/07/28 08:38:41  tipaul
1814 # For instance, the return date does not rely on the borrower expiration date. A systempref will be added in Koha, to modify return date calculation schema :
1815 # * ReturnBeforeExpiry = yes => return date can't be after expiry date
1816 # * ReturnBeforeExpiry = no  => return date can be after expiry date
1817 #
1818 # Revision 1.112  2005/07/26 08:19:47  hdl
1819 # Adding IndependantBranches System preference variable in order to manage Branch independancy.
1820 #
1821 # Revision 1.111  2005/07/25 15:35:38  tipaul
1822 # we have decided that moving to Koha 3.0 requires being already in Koha 2.2.x
1823 # So, the updatedatabase script can highly be cleaned (90% removed).
1824 # Let's play with the new Koha DB structure now ;-)
1825 #