~ubuntu-branches/debian/sid/bugzilla/sid

« back to all changes in this revision

Viewing changes to Bugzilla/DB/Schema.pm

  • Committer: Bazaar Package Importer
  • Author(s): Raphael Bossek
  • Date: 2008-06-27 22:34:34 UTC
  • mfrom: (1.1.7 upstream)
  • Revision ID: james.westby@ubuntu.com-20080627223434-0ib57vstn43bb4a3
Tags: 3.0.4.1-1
* Update of French, Russian and German translations. (closes: #488251)
* Added Bulgarian and Belarusian translations.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- Mode: perl; indent-tabs-mode: nil -*-
2
 
#
3
 
# The contents of this file are subject to the Mozilla Public
4
 
# License Version 1.1 (the "License"); you may not use this file
5
 
# except in compliance with the License. You may obtain a copy of
6
 
# the License at http://www.mozilla.org/MPL/
7
 
#
8
 
# Software distributed under the License is distributed on an "AS
9
 
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
 
# implied. See the License for the specific language governing
11
 
# rights and limitations under the License.
12
 
#
13
 
# The Original Code is the Bugzilla Bug Tracking System.
14
 
#
15
 
# The Initial Developer of the Original Code is Netscape Communications
16
 
# Corporation. Portions created by Netscape are
17
 
# Copyright (C) 1998 Netscape Communications Corporation. All
18
 
# Rights Reserved.
19
 
#
20
 
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
21
 
#                 Edward J. Sabol <edwardjsabol@iname.com>
22
 
#                 Max Kanat-Alexander <mkanat@bugzilla.org>
23
 
#                 Lance Larsh <lance.larsh@oracle.com>
24
 
#                 Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
25
 
#                 Akamai Technologies <bugzilla-dev@akamai.com>
26
 
 
27
 
package Bugzilla::DB::Schema;
28
 
 
29
 
###########################################################################
30
 
#
31
 
# Purpose: Object-oriented, DBMS-independent database schema for Bugzilla
32
 
#
33
 
# This is the base class implementing common methods and abstract schema.
34
 
#
35
 
###########################################################################
36
 
 
37
 
use strict;
38
 
use Bugzilla::Error;
39
 
use Bugzilla::Hook;
40
 
use Bugzilla::Util;
41
 
use Bugzilla::Constants;
42
 
 
43
 
use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
44
 
use Safe;
45
 
# Historical, needed for SCHEMA_VERSION = '1.00'
46
 
use Storable qw(dclone freeze thaw);
47
 
 
48
 
# New SCHEMA_VERSION (2.00) use this
49
 
use Data::Dumper;
50
 
 
51
 
=head1 NAME
52
 
 
53
 
Bugzilla::DB::Schema - Abstract database schema for Bugzilla
54
 
 
55
 
=head1 SYNOPSIS
56
 
 
57
 
  # Obtain MySQL database schema.
58
 
  # Do not do this. Use Bugzilla::DB instead.
59
 
  use Bugzilla::DB::Schema;
60
 
  my $mysql_schema = new Bugzilla::DB::Schema('Mysql');
61
 
 
62
 
  # Recommended way to obtain database schema.
63
 
  use Bugzilla::DB;
64
 
  my $dbh = Bugzilla->dbh;
65
 
  my $schema = $dbh->_bz_schema();
66
 
 
67
 
  # Get the list of tables in the Bugzilla database.
68
 
  my @tables = $schema->get_table_list();
69
 
 
70
 
  # Get the SQL statements need to create the bugs table.
71
 
  my @statements = $schema->get_table_ddl('bugs');
72
 
 
73
 
  # Get the database-specific SQL data type used to implement
74
 
  # the abstract data type INT1.
75
 
  my $db_specific_type = $schema->sql_type('INT1');
76
 
 
77
 
=head1 DESCRIPTION
78
 
 
79
 
This module implements an object-oriented, abstract database schema.
80
 
It should be considered package-private to the Bugzilla::DB module.
81
 
That means that CGI scripts should never call any function in this
82
 
module directly, but should instead rely on methods provided by
83
 
Bugzilla::DB.
84
 
 
85
 
=head1 NEW TO SCHEMA.PM?
86
 
 
87
 
If this is your first time looking at Schema.pm, especially if
88
 
you are making changes to the database, please take a look at
89
 
L<http://www.bugzilla.org/docs/developer.html#sql-schema> to learn
90
 
more about how this integrates into the rest of Bugzilla.
91
 
 
92
 
=cut
93
 
 
94
 
#--------------------------------------------------------------------------
95
 
# Define the Bugzilla abstract database schema and version as constants.
96
 
 
97
 
=head1 CONSTANTS
98
 
 
99
 
=over 4
100
 
 
101
 
=item C<SCHEMA_VERSION>
102
 
 
103
 
The 'version' of the internal schema structure. This version number
104
 
is incremented every time the the fundamental structure of Schema
105
 
internals changes.
106
 
 
107
 
This is NOT changed every time a table or a column is added. This 
108
 
number is incremented only if the internal structures of this 
109
 
Schema would be incompatible with the internal structures of a 
110
 
previous Schema version.
111
 
 
112
 
In general, unless you are messing around with serialization
113
 
and deserialization of the schema, you don't need to worry about
114
 
this constant.
115
 
 
116
 
=begin private
117
 
 
118
 
An example of the use of the version number:
119
 
 
120
 
Today, we store all individual columns like this:
121
 
 
122
 
column_name => { TYPE => 'SOMETYPE', NOTNULL => 1 }
123
 
 
124
 
Imagine that someday we decide that NOTNULL => 1 is bad, and we want
125
 
to change it so that the schema instead uses NULL => 0.
126
 
 
127
 
But we have a bunch of Bugzilla installations around the world with a
128
 
serialized schema that has NOTNULL in it! When we deserialize that 
129
 
structure, it just WILL NOT WORK properly inside of our new Schema object.
130
 
So, immediately after deserializing, we need to go through the hash 
131
 
and change all NOTNULLs to NULLs and so on.
132
 
 
133
 
We know that we need to do that on deserializing because we know that
134
 
version 1.00 used NOTNULL. Having made the change to NULL, we would now
135
 
be version 1.01.
136
 
 
137
 
=end private
138
 
 
139
 
=item C<ABSTRACT_SCHEMA>
140
 
 
141
 
The abstract database schema structure consists of a hash reference
142
 
in which each key is the name of a table in the Bugzilla database.
143
 
 
144
 
The value for each key is a hash reference containing the keys
145
 
C<FIELDS> and C<INDEXES> which in turn point to array references
146
 
containing information on the table's fields and indexes. 
147
 
 
148
 
A field hash reference should must contain the key C<TYPE>. Optional field
149
 
keys include C<PRIMARYKEY>, C<NOTNULL>, and C<DEFAULT>. 
150
 
 
151
 
The C<INDEXES> array reference contains index names and information 
152
 
regarding the index. If the index name points to an array reference,
153
 
then the index is a regular index and the array contains the indexed
154
 
columns. If the index name points to a hash reference, then the hash
155
 
must contain the key C<FIELDS>. It may also contain the key C<TYPE>,
156
 
which can be used to specify the type of index such as UNIQUE or FULLTEXT.
157
 
 
158
 
=back
159
 
 
160
 
=cut
161
 
 
162
 
use constant SCHEMA_VERSION  => '2.00';
163
 
use constant ABSTRACT_SCHEMA => {
164
 
 
165
 
    # BUG-RELATED TABLES
166
 
    # ------------------
167
 
 
168
 
    # General Bug Information
169
 
    # -----------------------
170
 
    bugs => {
171
 
        FIELDS => [
172
 
            bug_id              => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
173
 
                                    PRIMARYKEY => 1},
174
 
            assigned_to         => {TYPE => 'INT3', NOTNULL => 1},
175
 
            bug_file_loc        => {TYPE => 'TEXT'},
176
 
            bug_severity        => {TYPE => 'varchar(64)', NOTNULL => 1},
177
 
            bug_status          => {TYPE => 'varchar(64)', NOTNULL => 1},
178
 
            creation_ts         => {TYPE => 'DATETIME'},
179
 
            delta_ts            => {TYPE => 'DATETIME', NOTNULL => 1},
180
 
            short_desc          => {TYPE => 'varchar(255)', NOTNULL => 1},
181
 
            op_sys              => {TYPE => 'varchar(64)', NOTNULL => 1},
182
 
            priority            => {TYPE => 'varchar(64)', NOTNULL => 1},
183
 
            product_id          => {TYPE => 'INT2', NOTNULL => 1},
184
 
            rep_platform        => {TYPE => 'varchar(64)', NOTNULL => 1},
185
 
            reporter            => {TYPE => 'INT3', NOTNULL => 1},
186
 
            version             => {TYPE => 'varchar(64)', NOTNULL => 1},
187
 
            component_id        => {TYPE => 'INT2', NOTNULL => 1},
188
 
            resolution          => {TYPE => 'varchar(64)',
189
 
                                    NOTNULL => 1, DEFAULT => "''"},
190
 
            target_milestone    => {TYPE => 'varchar(20)',
191
 
                                    NOTNULL => 1, DEFAULT => "'---'"},
192
 
            qa_contact          => {TYPE => 'INT3'},
193
 
            status_whiteboard   => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
194
 
                                    DEFAULT => "''"},
195
 
            votes               => {TYPE => 'INT3', NOTNULL => 1,
196
 
                                    DEFAULT => '0'},
197
 
            # Note: keywords field is only a cache; the real data
198
 
            # comes from the keywords table
199
 
            keywords            => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
200
 
                                    DEFAULT => "''"},
201
 
            lastdiffed          => {TYPE => 'DATETIME'},
202
 
            everconfirmed       => {TYPE => 'BOOLEAN', NOTNULL => 1},
203
 
            reporter_accessible => {TYPE => 'BOOLEAN',
204
 
                                    NOTNULL => 1, DEFAULT => 'TRUE'},
205
 
            cclist_accessible   => {TYPE => 'BOOLEAN',
206
 
                                    NOTNULL => 1, DEFAULT => 'TRUE'},
207
 
            estimated_time      => {TYPE => 'decimal(5,2)',
208
 
                                    NOTNULL => 1, DEFAULT => '0'},
209
 
            remaining_time      => {TYPE => 'decimal(5,2)',
210
 
                                    NOTNULL => 1, DEFAULT => '0'},
211
 
            deadline            => {TYPE => 'DATETIME'},
212
 
            alias               => {TYPE => 'varchar(20)'},
213
 
        ],
214
 
        INDEXES => [
215
 
            bugs_alias_idx            => {FIELDS => ['alias'],
216
 
                                          TYPE => 'UNIQUE'},
217
 
            bugs_assigned_to_idx      => ['assigned_to'],
218
 
            bugs_creation_ts_idx      => ['creation_ts'],
219
 
            bugs_delta_ts_idx         => ['delta_ts'],
220
 
            bugs_bug_severity_idx     => ['bug_severity'],
221
 
            bugs_bug_status_idx       => ['bug_status'],
222
 
            bugs_op_sys_idx           => ['op_sys'],
223
 
            bugs_priority_idx         => ['priority'],
224
 
            bugs_product_id_idx       => ['product_id'],
225
 
            bugs_reporter_idx         => ['reporter'],
226
 
            bugs_version_idx          => ['version'],
227
 
            bugs_component_id_idx     => ['component_id'],
228
 
            bugs_resolution_idx       => ['resolution'],
229
 
            bugs_target_milestone_idx => ['target_milestone'],
230
 
            bugs_qa_contact_idx       => ['qa_contact'],
231
 
            bugs_votes_idx            => ['votes'],
232
 
        ],
233
 
    },
234
 
 
235
 
    bugs_activity => {
236
 
        FIELDS => [
237
 
            bug_id    => {TYPE => 'INT3', NOTNULL => 1},
238
 
            attach_id => {TYPE => 'INT3'},
239
 
            who       => {TYPE => 'INT3', NOTNULL => 1},
240
 
            bug_when  => {TYPE => 'DATETIME', NOTNULL => 1},
241
 
            fieldid   => {TYPE => 'INT3', NOTNULL => 1},
242
 
            added     => {TYPE => 'TINYTEXT'},
243
 
            removed   => {TYPE => 'TINYTEXT'},
244
 
        ],
245
 
        INDEXES => [
246
 
            bugs_activity_bug_id_idx  => ['bug_id'],
247
 
            bugs_activity_who_idx     => ['who'],
248
 
            bugs_activity_bug_when_idx => ['bug_when'],
249
 
            bugs_activity_fieldid_idx => ['fieldid'],
250
 
        ],
251
 
    },
252
 
 
253
 
    cc => {
254
 
        FIELDS => [
255
 
            bug_id => {TYPE => 'INT3', NOTNULL => 1},
256
 
            who    => {TYPE => 'INT3', NOTNULL => 1},
257
 
        ],
258
 
        INDEXES => [
259
 
            cc_bug_id_idx => {FIELDS => [qw(bug_id who)],
260
 
                              TYPE => 'UNIQUE'},
261
 
            cc_who_idx    => ['who'],
262
 
        ],
263
 
    },
264
 
 
265
 
    longdescs => {
266
 
        FIELDS => [
267
 
            comment_id      => {TYPE => 'MEDIUMSERIAL',  NOTNULL => 1,
268
 
                                PRIMARYKEY => 1},
269
 
            bug_id          => {TYPE => 'INT3',  NOTNULL => 1},
270
 
            who             => {TYPE => 'INT3', NOTNULL => 1},
271
 
            bug_when        => {TYPE => 'DATETIME', NOTNULL => 1},
272
 
            work_time       => {TYPE => 'decimal(5,2)', NOTNULL => 1,
273
 
                                DEFAULT => '0'},
274
 
            thetext         => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
275
 
            isprivate       => {TYPE => 'BOOLEAN', NOTNULL => 1,
276
 
                                DEFAULT => 'FALSE'},
277
 
            already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1,
278
 
                                DEFAULT => 'FALSE'},
279
 
            type            => {TYPE => 'INT2', NOTNULL => 1,
280
 
                                DEFAULT => '0'},
281
 
            extra_data      => {TYPE => 'varchar(255)'}
282
 
        ],
283
 
        INDEXES => [
284
 
            longdescs_bug_id_idx   => ['bug_id'],
285
 
            longdescs_who_idx     => [qw(who bug_id)],
286
 
            longdescs_bug_when_idx => ['bug_when'],
287
 
            longdescs_thetext_idx => {FIELDS => ['thetext'],
288
 
                                      TYPE => 'FULLTEXT'},
289
 
        ],
290
 
    },
291
 
 
292
 
    dependencies => {
293
 
        FIELDS => [
294
 
            blocked   => {TYPE => 'INT3', NOTNULL => 1},
295
 
            dependson => {TYPE => 'INT3', NOTNULL => 1},
296
 
        ],
297
 
        INDEXES => [
298
 
            dependencies_blocked_idx   => ['blocked'],
299
 
            dependencies_dependson_idx => ['dependson'],
300
 
        ],
301
 
    },
302
 
 
303
 
    votes => {
304
 
        FIELDS => [
305
 
            who        => {TYPE => 'INT3', NOTNULL => 1},
306
 
            bug_id     => {TYPE => 'INT3', NOTNULL => 1},
307
 
            vote_count => {TYPE => 'INT2', NOTNULL => 1},
308
 
        ],
309
 
        INDEXES => [
310
 
            votes_who_idx    => ['who'],
311
 
            votes_bug_id_idx => ['bug_id'],
312
 
        ],
313
 
    },
314
 
 
315
 
    attachments => {
316
 
        FIELDS => [
317
 
            attach_id    => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
318
 
                             PRIMARYKEY => 1},
319
 
            bug_id       => {TYPE => 'INT3', NOTNULL => 1},
320
 
            creation_ts  => {TYPE => 'DATETIME', NOTNULL => 1},
321
 
            description  => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
322
 
            mimetype     => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
323
 
            ispatch      => {TYPE => 'BOOLEAN'},
324
 
            filename     => {TYPE => 'varchar(100)', NOTNULL => 1},
325
 
            submitter_id => {TYPE => 'INT3', NOTNULL => 1},
326
 
            isobsolete   => {TYPE => 'BOOLEAN', NOTNULL => 1,
327
 
                             DEFAULT => 'FALSE'},
328
 
            isprivate    => {TYPE => 'BOOLEAN', NOTNULL => 1,
329
 
                             DEFAULT => 'FALSE'},
330
 
            isurl        => {TYPE => 'BOOLEAN', NOTNULL => 1,
331
 
                             DEFAULT => 'FALSE'},
332
 
        ],
333
 
        INDEXES => [
334
 
            attachments_bug_id_idx => ['bug_id'],
335
 
            attachments_creation_ts_idx => ['creation_ts'],
336
 
            attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
337
 
        ],
338
 
    },
339
 
    attach_data => {
340
 
        FIELDS => [
341
 
            id      => {TYPE => 'INT3', NOTNULL => 1,
342
 
                        PRIMARYKEY => 1},
343
 
            thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
344
 
        ],
345
 
    },
346
 
 
347
 
    duplicates => {
348
 
        FIELDS => [
349
 
            dupe_of => {TYPE => 'INT3', NOTNULL => 1},
350
 
            dupe    => {TYPE => 'INT3', NOTNULL => 1,
351
 
                       PRIMARYKEY => 1},
352
 
        ],
353
 
    },
354
 
 
355
 
    # Keywords
356
 
    # --------
357
 
 
358
 
    keyworddefs => {
359
 
        FIELDS => [
360
 
            id          => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
361
 
                            PRIMARYKEY => 1},
362
 
            name        => {TYPE => 'varchar(64)', NOTNULL => 1},
363
 
            description => {TYPE => 'MEDIUMTEXT'},
364
 
        ],
365
 
        INDEXES => [
366
 
            keyworddefs_name_idx   => {FIELDS => ['name'],
367
 
                                       TYPE => 'UNIQUE'},
368
 
        ],
369
 
    },
370
 
 
371
 
    keywords => {
372
 
        FIELDS => [
373
 
            bug_id    => {TYPE => 'INT3', NOTNULL => 1},
374
 
            keywordid => {TYPE => 'INT2', NOTNULL => 1},
375
 
        ],
376
 
        INDEXES => [
377
 
            keywords_bug_id_idx    => {FIELDS => [qw(bug_id keywordid)],
378
 
                                       TYPE => 'UNIQUE'},
379
 
            keywords_keywordid_idx => ['keywordid'],
380
 
        ],
381
 
    },
382
 
 
383
 
    # Flags
384
 
    # -----
385
 
 
386
 
    # "flags" stores one record for each flag on each bug/attachment.
387
 
    flags => {
388
 
        FIELDS => [
389
 
            id                => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
390
 
                                  PRIMARYKEY => 1},
391
 
            type_id           => {TYPE => 'INT2', NOTNULL => 1},
392
 
            status            => {TYPE => 'char(1)', NOTNULL => 1},
393
 
            bug_id            => {TYPE => 'INT3', NOTNULL => 1},
394
 
            attach_id         => {TYPE => 'INT3'},
395
 
            creation_date     => {TYPE => 'DATETIME', NOTNULL => 1},
396
 
            modification_date => {TYPE => 'DATETIME'},
397
 
            setter_id         => {TYPE => 'INT3'},
398
 
            requestee_id      => {TYPE => 'INT3'},
399
 
        ],
400
 
        INDEXES => [
401
 
            flags_bug_id_idx       => [qw(bug_id attach_id)],
402
 
            flags_setter_id_idx    => ['setter_id'],
403
 
            flags_requestee_id_idx => ['requestee_id'],
404
 
            flags_type_id_idx      => ['type_id'],
405
 
        ],
406
 
    },
407
 
 
408
 
    # "flagtypes" defines the types of flags that can be set.
409
 
    flagtypes => {
410
 
        FIELDS => [
411
 
            id               => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
412
 
                                 PRIMARYKEY => 1},
413
 
            name             => {TYPE => 'varchar(50)', NOTNULL => 1},
414
 
            description      => {TYPE => 'TEXT'},
415
 
            cc_list          => {TYPE => 'varchar(200)'},
416
 
            target_type      => {TYPE => 'char(1)', NOTNULL => 1,
417
 
                                 DEFAULT => "'b'"},
418
 
            is_active        => {TYPE => 'BOOLEAN', NOTNULL => 1,
419
 
                                 DEFAULT => 'TRUE'},
420
 
            is_requestable   => {TYPE => 'BOOLEAN', NOTNULL => 1,
421
 
                                 DEFAULT => 'FALSE'},
422
 
            is_requesteeble  => {TYPE => 'BOOLEAN', NOTNULL => 1,
423
 
                                 DEFAULT => 'FALSE'},
424
 
            is_multiplicable => {TYPE => 'BOOLEAN', NOTNULL => 1,
425
 
                                 DEFAULT => 'FALSE'},
426
 
            sortkey          => {TYPE => 'INT2', NOTNULL => 1,
427
 
                                 DEFAULT => '0'},
428
 
            grant_group_id   => {TYPE => 'INT3'},
429
 
            request_group_id => {TYPE => 'INT3'},
430
 
        ],
431
 
    },
432
 
 
433
 
    # "flaginclusions" and "flagexclusions" specify the products/components
434
 
    #     a bug/attachment must belong to in order for flags of a given type
435
 
    #     to be set for them.
436
 
    flaginclusions => {
437
 
        FIELDS => [
438
 
            type_id      => {TYPE => 'INT2', NOTNULL => 1},
439
 
            product_id   => {TYPE => 'INT2'},
440
 
            component_id => {TYPE => 'INT2'},
441
 
        ],
442
 
        INDEXES => [
443
 
            flaginclusions_type_id_idx =>
444
 
                [qw(type_id product_id component_id)],
445
 
        ],
446
 
    },
447
 
 
448
 
    flagexclusions => {
449
 
        FIELDS => [
450
 
            type_id      => {TYPE => 'INT2', NOTNULL => 1},
451
 
            product_id   => {TYPE => 'INT2'},
452
 
            component_id => {TYPE => 'INT2'},
453
 
        ],
454
 
        INDEXES => [
455
 
            flagexclusions_type_id_idx =>
456
 
                [qw(type_id product_id component_id)],
457
 
        ],
458
 
    },
459
 
 
460
 
    # General Field Information
461
 
    # -------------------------
462
 
 
463
 
    fielddefs => {
464
 
        FIELDS => [
465
 
            id          => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
466
 
                            PRIMARYKEY => 1},
467
 
            name        => {TYPE => 'varchar(64)', NOTNULL => 1},
468
 
            type        => {TYPE => 'INT2', NOTNULL => 1,
469
 
                            DEFAULT => FIELD_TYPE_UNKNOWN},
470
 
            custom      => {TYPE => 'BOOLEAN', NOTNULL => 1,
471
 
                            DEFAULT => 'FALSE'},
472
 
            description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
473
 
            mailhead    => {TYPE => 'BOOLEAN', NOTNULL => 1,
474
 
                            DEFAULT => 'FALSE'},
475
 
            sortkey     => {TYPE => 'INT2', NOTNULL => 1},
476
 
            obsolete    => {TYPE => 'BOOLEAN', NOTNULL => 1,
477
 
                            DEFAULT => 'FALSE'},
478
 
            enter_bug   => {TYPE => 'BOOLEAN', NOTNULL => 1,
479
 
                            DEFAULT => 'FALSE'},
480
 
        ],
481
 
        INDEXES => [
482
 
            fielddefs_name_idx    => {FIELDS => ['name'],
483
 
                                      TYPE => 'UNIQUE'},
484
 
            fielddefs_sortkey_idx => ['sortkey'],
485
 
        ],
486
 
    },
487
 
 
488
 
    # Per-product Field Values
489
 
    # ------------------------
490
 
 
491
 
    versions => {
492
 
        FIELDS => [
493
 
            id         =>  {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
494
 
                            PRIMARYKEY => 1},
495
 
            value      =>  {TYPE => 'varchar(64)', NOTNULL => 1},
496
 
            product_id =>  {TYPE => 'INT2', NOTNULL => 1},
497
 
        ],
498
 
        INDEXES => [
499
 
            versions_product_id_idx => {FIELDS => [qw(product_id value)],
500
 
                                        TYPE => 'UNIQUE'},
501
 
        ],
502
 
    },
503
 
 
504
 
    milestones => {
505
 
        FIELDS => [
506
 
            id         => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, 
507
 
                           PRIMARYKEY => 1},
508
 
            product_id => {TYPE => 'INT2', NOTNULL => 1},
509
 
            value      => {TYPE => 'varchar(20)', NOTNULL => 1},
510
 
            sortkey    => {TYPE => 'INT2', NOTNULL => 1,
511
 
                           DEFAULT => 0},
512
 
        ],
513
 
        INDEXES => [
514
 
            milestones_product_id_idx => {FIELDS => [qw(product_id value)],
515
 
                                          TYPE => 'UNIQUE'},
516
 
        ],
517
 
    },
518
 
 
519
 
    # Global Field Values
520
 
    # -------------------
521
 
 
522
 
    bug_status => {
523
 
        FIELDS => [
524
 
            id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
525
 
                         PRIMARYKEY => 1},
526
 
            value    => {TYPE => 'varchar(64)', NOTNULL => 1},
527
 
            sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
528
 
            isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
529
 
                         DEFAULT => 'TRUE'},
530
 
        ],
531
 
        INDEXES => [
532
 
            bug_status_value_idx  => {FIELDS => ['value'],
533
 
                                       TYPE => 'UNIQUE'},
534
 
            bug_status_sortkey_idx => ['sortkey', 'value'],
535
 
        ],
536
 
    },
537
 
 
538
 
    resolution => {
539
 
        FIELDS => [
540
 
            id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
541
 
                         PRIMARYKEY => 1},
542
 
            value    => {TYPE => 'varchar(64)', NOTNULL => 1},
543
 
            sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
544
 
            isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
545
 
                         DEFAULT => 'TRUE'},
546
 
        ],
547
 
        INDEXES => [
548
 
            resolution_value_idx   => {FIELDS => ['value'],
549
 
                                       TYPE => 'UNIQUE'},
550
 
            resolution_sortkey_idx => ['sortkey', 'value'],
551
 
        ],
552
 
    },
553
 
 
554
 
    bug_severity => {
555
 
        FIELDS => [
556
 
            id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1, 
557
 
                         PRIMARYKEY => 1},
558
 
            value    => {TYPE => 'varchar(64)', NOTNULL => 1},
559
 
            sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
560
 
            isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
561
 
                         DEFAULT => 'TRUE'},
562
 
        ],
563
 
        INDEXES => [
564
 
            bug_severity_value_idx   => {FIELDS => ['value'],
565
 
                                         TYPE => 'UNIQUE'},
566
 
            bug_severity_sortkey_idx => ['sortkey', 'value'],
567
 
        ],
568
 
    },
569
 
 
570
 
    priority => {
571
 
        FIELDS => [
572
 
            id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
573
 
                         PRIMARYKEY => 1},
574
 
            value    => {TYPE => 'varchar(64)', NOTNULL => 1},
575
 
            sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
576
 
            isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
577
 
                         DEFAULT => 'TRUE'},
578
 
        ],
579
 
        INDEXES => [
580
 
            priority_value_idx   => {FIELDS => ['value'],
581
 
                                     TYPE => 'UNIQUE'},
582
 
            priority_sortkey_idx => ['sortkey', 'value'],
583
 
        ],
584
 
    },
585
 
 
586
 
    rep_platform => {
587
 
        FIELDS => [
588
 
            id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
589
 
                         PRIMARYKEY => 1},
590
 
            value    => {TYPE => 'varchar(64)', NOTNULL => 1},
591
 
            sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
592
 
            isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
593
 
                         DEFAULT => 'TRUE'},
594
 
        ],
595
 
        INDEXES => [
596
 
            rep_platform_value_idx   => {FIELDS => ['value'],
597
 
                                         TYPE => 'UNIQUE'},
598
 
            rep_platform_sortkey_idx => ['sortkey', 'value'],
599
 
        ],
600
 
    },
601
 
 
602
 
    op_sys => {
603
 
        FIELDS => [
604
 
            id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
605
 
                         PRIMARYKEY => 1},
606
 
            value    => {TYPE => 'varchar(64)', NOTNULL => 1},
607
 
            sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
608
 
            isactive => {TYPE => 'BOOLEAN', NOTNULL => 1, 
609
 
                         DEFAULT => 'TRUE'},
610
 
        ],
611
 
        INDEXES => [
612
 
            op_sys_value_idx   => {FIELDS => ['value'],
613
 
                                   TYPE => 'UNIQUE'},
614
 
            op_sys_sortkey_idx => ['sortkey', 'value'],
615
 
        ],
616
 
    },
617
 
 
618
 
    # USER INFO
619
 
    # ---------
620
 
 
621
 
    # General User Information
622
 
    # ------------------------
623
 
 
624
 
    profiles => {
625
 
        FIELDS => [
626
 
            userid         => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
627
 
                               PRIMARYKEY => 1},
628
 
            login_name     => {TYPE => 'varchar(255)', NOTNULL => 1},
629
 
            cryptpassword  => {TYPE => 'varchar(128)'},
630
 
            realname       => {TYPE => 'varchar(255)', NOTNULL => 1,
631
 
                               DEFAULT => "''"},
632
 
            disabledtext   => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
633
 
                               DEFAULT => "''"},
634
 
            disable_mail   => {TYPE => 'BOOLEAN', NOTNULL => 1,
635
 
                               DEFAULT => 'FALSE'},
636
 
            mybugslink     => {TYPE => 'BOOLEAN', NOTNULL => 1,
637
 
                               DEFAULT => 'TRUE'},
638
 
            extern_id      => {TYPE => 'varchar(64)'},
639
 
        ],
640
 
        INDEXES => [
641
 
            profiles_login_name_idx => {FIELDS => ['login_name'],
642
 
                                        TYPE => 'UNIQUE'},
643
 
        ],
644
 
    },
645
 
 
646
 
    profiles_activity => {
647
 
        FIELDS => [
648
 
            userid        => {TYPE => 'INT3', NOTNULL => 1},
649
 
            who           => {TYPE => 'INT3', NOTNULL => 1},
650
 
            profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
651
 
            fieldid       => {TYPE => 'INT3', NOTNULL => 1},
652
 
            oldvalue      => {TYPE => 'TINYTEXT'},
653
 
            newvalue      => {TYPE => 'TINYTEXT'},
654
 
        ],
655
 
        INDEXES => [
656
 
            profiles_activity_userid_idx  => ['userid'],
657
 
            profiles_activity_profiles_when_idx => ['profiles_when'],
658
 
            profiles_activity_fieldid_idx => ['fieldid'],
659
 
        ],
660
 
    },
661
 
 
662
 
    email_setting => {
663
 
        FIELDS => [
664
 
            user_id      => {TYPE => 'INT3', NOTNULL => 1},
665
 
            relationship => {TYPE => 'INT1', NOTNULL => 1},
666
 
            event        => {TYPE => 'INT1', NOTNULL => 1},
667
 
        ],
668
 
        INDEXES => [
669
 
            email_setting_user_id_idx  =>
670
 
                                    {FIELDS => [qw(user_id relationship event)],
671
 
                                     TYPE => 'UNIQUE'},
672
 
        ],
673
 
    },
674
 
 
675
 
    watch => {
676
 
        FIELDS => [
677
 
            watcher => {TYPE => 'INT3', NOTNULL => 1},
678
 
            watched => {TYPE => 'INT3', NOTNULL => 1},
679
 
        ],
680
 
        INDEXES => [
681
 
            watch_watcher_idx => {FIELDS => [qw(watcher watched)],
682
 
                                  TYPE => 'UNIQUE'},
683
 
            watch_watched_idx => ['watched'],
684
 
        ],
685
 
    },
686
 
 
687
 
    namedqueries => {
688
 
        FIELDS => [
689
 
            id           => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
690
 
                             PRIMARYKEY => 1},
691
 
            userid       => {TYPE => 'INT3', NOTNULL => 1},
692
 
            name         => {TYPE => 'varchar(64)', NOTNULL => 1},
693
 
            query        => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
694
 
            query_type   => {TYPE => 'BOOLEAN', NOTNULL => 1},
695
 
        ],
696
 
        INDEXES => [
697
 
            namedqueries_userid_idx => {FIELDS => [qw(userid name)],
698
 
                                        TYPE => 'UNIQUE'},
699
 
        ],
700
 
    },
701
 
 
702
 
    namedqueries_link_in_footer => {
703
 
        FIELDS => [
704
 
            namedquery_id => {TYPE => 'INT3', NOTNULL => 1},
705
 
            user_id       => {TYPE => 'INT3', NOTNULL => 1},
706
 
        ],
707
 
        INDEXES => [
708
 
            namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)],
709
 
                                                   TYPE => 'UNIQUE'},
710
 
            namedqueries_link_in_footer_userid_idx => ['user_id'],
711
 
        ],
712
 
    },
713
 
 
714
 
    component_cc => {
715
 
 
716
 
        FIELDS => [
717
 
            user_id      => {TYPE => 'INT3', NOTNULL => 1},
718
 
            component_id => {TYPE => 'INT2', NOTNULL => 1},
719
 
        ],
720
 
        INDEXES => [
721
 
            component_cc_user_id_idx => {FIELDS => [qw(component_id user_id)],
722
 
                                         TYPE => 'UNIQUE'},
723
 
        ],
724
 
    },
725
 
 
726
 
    # Authentication
727
 
    # --------------
728
 
 
729
 
    logincookies => {
730
 
        FIELDS => [
731
 
            cookie   => {TYPE => 'varchar(16)', NOTNULL => 1,
732
 
                         PRIMARYKEY => 1},
733
 
            userid   => {TYPE => 'INT3', NOTNULL => 1},
734
 
            ipaddr   => {TYPE => 'varchar(40)', NOTNULL => 1},
735
 
            lastused => {TYPE => 'DATETIME', NOTNULL => 1},
736
 
        ],
737
 
        INDEXES => [
738
 
            logincookies_lastused_idx => ['lastused'],
739
 
        ],
740
 
    },
741
 
 
742
 
    # "tokens" stores the tokens users receive when a password or email
743
 
    #     change is requested.  Tokens provide an extra measure of security
744
 
    #     for these changes.
745
 
    tokens => {
746
 
        FIELDS => [
747
 
            userid    => {TYPE => 'INT3'},
748
 
            issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
749
 
            token     => {TYPE => 'varchar(16)', NOTNULL => 1,
750
 
                          PRIMARYKEY => 1},
751
 
            tokentype => {TYPE => 'varchar(8)', NOTNULL => 1} ,
752
 
            eventdata => {TYPE => 'TINYTEXT'},
753
 
        ],
754
 
        INDEXES => [
755
 
            tokens_userid_idx => ['userid'],
756
 
        ],
757
 
    },
758
 
 
759
 
    # GROUPS
760
 
    # ------
761
 
 
762
 
    groups => {
763
 
        FIELDS => [
764
 
            id           => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
765
 
                             PRIMARYKEY => 1},
766
 
            name         => {TYPE => 'varchar(255)', NOTNULL => 1},
767
 
            description  => {TYPE => 'TEXT', NOTNULL => 1},
768
 
            isbuggroup   => {TYPE => 'BOOLEAN', NOTNULL => 1},
769
 
            userregexp   => {TYPE => 'TINYTEXT', NOTNULL => 1,
770
 
                             DEFAULT => "''"},
771
 
            isactive     => {TYPE => 'BOOLEAN', NOTNULL => 1,
772
 
                             DEFAULT => 'TRUE'},
773
 
        ],
774
 
        INDEXES => [
775
 
            groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
776
 
        ],
777
 
    },
778
 
 
779
 
    group_control_map => {
780
 
        FIELDS => [
781
 
            group_id      => {TYPE => 'INT3', NOTNULL => 1},
782
 
            product_id    => {TYPE => 'INT3', NOTNULL => 1},
783
 
            entry         => {TYPE => 'BOOLEAN', NOTNULL => 1},
784
 
            membercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
785
 
            othercontrol  => {TYPE => 'BOOLEAN', NOTNULL => 1},
786
 
            canedit       => {TYPE => 'BOOLEAN', NOTNULL => 1},
787
 
            editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1,
788
 
                               DEFAULT => 'FALSE'},
789
 
            editbugs      => {TYPE => 'BOOLEAN', NOTNULL => 1,
790
 
                              DEFAULT => 'FALSE'},
791
 
            canconfirm    => {TYPE => 'BOOLEAN', NOTNULL => 1,
792
 
                              DEFAULT => 'FALSE'},
793
 
        ],
794
 
        INDEXES => [
795
 
            group_control_map_product_id_idx =>
796
 
            {FIELDS => [qw(product_id group_id)], TYPE => 'UNIQUE'},
797
 
            group_control_map_group_id_idx    => ['group_id'],
798
 
        ],
799
 
    },
800
 
 
801
 
    # "user_group_map" determines the groups that a user belongs to
802
 
    # directly or due to regexp and which groups can be blessed by a user.
803
 
    #
804
 
    # grant_type:
805
 
    # if GRANT_DIRECT - record was explicitly granted
806
 
    # if GRANT_DERIVED - record was derived from expanding a group hierarchy
807
 
    # if GRANT_REGEXP - record was created by evaluating a regexp
808
 
    user_group_map => {
809
 
        FIELDS => [
810
 
            user_id    => {TYPE => 'INT3', NOTNULL => 1},
811
 
            group_id   => {TYPE => 'INT3', NOTNULL => 1},
812
 
            isbless    => {TYPE => 'BOOLEAN', NOTNULL => 1,
813
 
                           DEFAULT => 'FALSE'},
814
 
            grant_type => {TYPE => 'INT1', NOTNULL => 1,
815
 
                           DEFAULT => GRANT_DIRECT},
816
 
        ],
817
 
        INDEXES => [
818
 
            user_group_map_user_id_idx =>
819
 
                {FIELDS => [qw(user_id group_id grant_type isbless)],
820
 
                 TYPE => 'UNIQUE'},
821
 
        ],
822
 
    },
823
 
 
824
 
    # This table determines which groups are made a member of another
825
 
    # group, given the ability to bless another group, or given
826
 
    # visibility to another groups existence and membership
827
 
    # grant_type:
828
 
    # if GROUP_MEMBERSHIP - member groups are made members of grantor
829
 
    # if GROUP_BLESS - member groups may grant membership in grantor
830
 
    # if GROUP_VISIBLE - member groups may see grantor group
831
 
    group_group_map => {
832
 
        FIELDS => [
833
 
            member_id  => {TYPE => 'INT3', NOTNULL => 1},
834
 
            grantor_id => {TYPE => 'INT3', NOTNULL => 1},
835
 
            grant_type => {TYPE => 'INT1', NOTNULL => 1,
836
 
                           DEFAULT => GROUP_MEMBERSHIP},
837
 
        ],
838
 
        INDEXES => [
839
 
            group_group_map_member_id_idx =>
840
 
                {FIELDS => [qw(member_id grantor_id grant_type)],
841
 
                 TYPE => 'UNIQUE'},
842
 
        ],
843
 
    },
844
 
 
845
 
    # This table determines which groups a user must be a member of
846
 
    # in order to see a bug.
847
 
    bug_group_map => {
848
 
        FIELDS => [
849
 
            bug_id   => {TYPE => 'INT3', NOTNULL => 1},
850
 
            group_id => {TYPE => 'INT3', NOTNULL => 1},
851
 
        ],
852
 
        INDEXES => [
853
 
            bug_group_map_bug_id_idx   =>
854
 
                {FIELDS => [qw(bug_id group_id)], TYPE => 'UNIQUE'},
855
 
            bug_group_map_group_id_idx => ['group_id'],
856
 
        ],
857
 
    },
858
 
 
859
 
    # This table determines which groups a user must be a member of
860
 
    # in order to see a named query somebody else shares.
861
 
    namedquery_group_map => {
862
 
        FIELDS => [
863
 
            namedquery_id => {TYPE => 'INT3', NOTNULL => 1},
864
 
            group_id      => {TYPE => 'INT3', NOTNULL => 1},
865
 
        ],
866
 
        INDEXES => [
867
 
            namedquery_group_map_namedquery_id_idx   =>
868
 
                {FIELDS => [qw(namedquery_id)], TYPE => 'UNIQUE'},
869
 
            namedquery_group_map_group_id_idx => ['group_id'],
870
 
        ],
871
 
    },
872
 
 
873
 
    category_group_map => {
874
 
        FIELDS => [
875
 
            category_id => {TYPE => 'INT2', NOTNULL => 1},
876
 
            group_id    => {TYPE => 'INT3', NOTNULL => 1},
877
 
        ],
878
 
        INDEXES => [
879
 
            category_group_map_category_id_idx =>
880
 
                {FIELDS => [qw(category_id group_id)], TYPE => 'UNIQUE'},
881
 
        ],
882
 
    },
883
 
 
884
 
 
885
 
    # PRODUCTS
886
 
    # --------
887
 
 
888
 
    classifications => {
889
 
        FIELDS => [
890
 
            id          => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
891
 
                            PRIMARYKEY => 1},
892
 
            name        => {TYPE => 'varchar(64)', NOTNULL => 1},
893
 
            description => {TYPE => 'MEDIUMTEXT'},
894
 
            sortkey     => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
895
 
        ],
896
 
        INDEXES => [
897
 
            classifications_name_idx => {FIELDS => ['name'],
898
 
                                           TYPE => 'UNIQUE'},
899
 
        ],
900
 
    },
901
 
 
902
 
    products => {
903
 
        FIELDS => [
904
 
            id                => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
905
 
                                  PRIMARYKEY => 1},
906
 
            name              => {TYPE => 'varchar(64)', NOTNULL => 1},
907
 
            classification_id => {TYPE => 'INT2', NOTNULL => 1,
908
 
                                  DEFAULT => '1'},
909
 
            description       => {TYPE => 'MEDIUMTEXT'},
910
 
            milestoneurl      => {TYPE => 'TINYTEXT', NOTNULL => 1,
911
 
                                  DEFAULT => "''"},
912
 
            disallownew       => {TYPE => 'BOOLEAN', NOTNULL => 1,
913
 
                                  DEFAULT => 0},
914
 
            votesperuser      => {TYPE => 'INT2', NOTNULL => 1,
915
 
                                  DEFAULT => 0},
916
 
            maxvotesperbug    => {TYPE => 'INT2', NOTNULL => 1,
917
 
                                  DEFAULT => '10000'},
918
 
            votestoconfirm    => {TYPE => 'INT2', NOTNULL => 1,
919
 
                                  DEFAULT => 0},
920
 
            defaultmilestone  => {TYPE => 'varchar(20)',
921
 
                                  NOTNULL => 1, DEFAULT => "'---'"},
922
 
        ],
923
 
        INDEXES => [
924
 
            products_name_idx   => {FIELDS => ['name'],
925
 
                                    TYPE => 'UNIQUE'},
926
 
        ],
927
 
    },
928
 
 
929
 
    components => {
930
 
        FIELDS => [
931
 
            id               => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
932
 
                                 PRIMARYKEY => 1},
933
 
            name             => {TYPE => 'varchar(64)', NOTNULL => 1},
934
 
            product_id       => {TYPE => 'INT2', NOTNULL => 1},
935
 
            initialowner     => {TYPE => 'INT3', NOTNULL => 1},
936
 
            initialqacontact => {TYPE => 'INT3'},
937
 
            description      => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
938
 
        ],
939
 
        INDEXES => [
940
 
            components_product_id_idx => {FIELDS => [qw(product_id name)],
941
 
                                          TYPE => 'UNIQUE'},
942
 
            components_name_idx   => ['name'],
943
 
        ],
944
 
    },
945
 
 
946
 
    # CHARTS
947
 
    # ------
948
 
 
949
 
    series => {
950
 
        FIELDS => [
951
 
            series_id   => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
952
 
                            PRIMARYKEY => 1},
953
 
            creator     => {TYPE => 'INT3'},
954
 
            category    => {TYPE => 'INT2', NOTNULL => 1},
955
 
            subcategory => {TYPE => 'INT2', NOTNULL => 1},
956
 
            name        => {TYPE => 'varchar(64)', NOTNULL => 1},
957
 
            frequency   => {TYPE => 'INT2', NOTNULL => 1},
958
 
            last_viewed => {TYPE => 'DATETIME'},
959
 
            query       => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
960
 
            is_public   => {TYPE => 'BOOLEAN', NOTNULL => 1,
961
 
                            DEFAULT => 'FALSE'},
962
 
        ],
963
 
        INDEXES => [
964
 
            series_creator_idx  =>
965
 
                {FIELDS => [qw(creator category subcategory name)],
966
 
                 TYPE => 'UNIQUE'},
967
 
        ],
968
 
    },
969
 
 
970
 
    series_data => {
971
 
        FIELDS => [
972
 
            series_id    => {TYPE => 'INT3', NOTNULL => 1},
973
 
            series_date  => {TYPE => 'DATETIME', NOTNULL => 1},
974
 
            series_value => {TYPE => 'INT3', NOTNULL => 1},
975
 
        ],
976
 
        INDEXES => [
977
 
            series_data_series_id_idx =>
978
 
                {FIELDS => [qw(series_id series_date)],
979
 
                 TYPE => 'UNIQUE'},
980
 
        ],
981
 
    },
982
 
 
983
 
    series_categories => {
984
 
        FIELDS => [
985
 
            id   => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
986
 
                     PRIMARYKEY => 1},
987
 
            name => {TYPE => 'varchar(64)', NOTNULL => 1},
988
 
        ],
989
 
        INDEXES => [
990
 
            series_categories_name_idx => {FIELDS => ['name'],
991
 
                                           TYPE => 'UNIQUE'},
992
 
        ],
993
 
    },
994
 
 
995
 
    # WHINE SYSTEM
996
 
    # ------------
997
 
 
998
 
    whine_queries => {
999
 
        FIELDS => [
1000
 
            id            => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
1001
 
                              NOTNULL => 1},
1002
 
            eventid       => {TYPE => 'INT3', NOTNULL => 1},
1003
 
            query_name    => {TYPE => 'varchar(64)', NOTNULL => 1,
1004
 
                              DEFAULT => "''"},
1005
 
            sortkey       => {TYPE => 'INT2', NOTNULL => 1,
1006
 
                              DEFAULT => '0'},
1007
 
            onemailperbug => {TYPE => 'BOOLEAN', NOTNULL => 1,
1008
 
                              DEFAULT => 'FALSE'},
1009
 
            title         => {TYPE => 'varchar(128)', NOTNULL => 1,
1010
 
                              DEFAULT => "''"},
1011
 
        ],
1012
 
        INDEXES => [
1013
 
            whine_queries_eventid_idx => ['eventid'],
1014
 
        ],
1015
 
    },
1016
 
 
1017
 
    whine_schedules => {
1018
 
        FIELDS => [
1019
 
            id          => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
1020
 
                            NOTNULL => 1},
1021
 
            eventid     => {TYPE => 'INT3', NOTNULL => 1},
1022
 
            run_day     => {TYPE => 'varchar(32)'},
1023
 
            run_time    => {TYPE => 'varchar(32)'},
1024
 
            run_next    => {TYPE => 'DATETIME'},
1025
 
            mailto      => {TYPE => 'INT3', NOTNULL => 1},
1026
 
            mailto_type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
1027
 
        ],
1028
 
        INDEXES => [
1029
 
            whine_schedules_run_next_idx => ['run_next'],
1030
 
            whine_schedules_eventid_idx  => ['eventid'],
1031
 
        ],
1032
 
    },
1033
 
 
1034
 
    whine_events => {
1035
 
        FIELDS => [
1036
 
            id           => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
1037
 
                             NOTNULL => 1},
1038
 
            owner_userid => {TYPE => 'INT3', NOTNULL => 1},
1039
 
            subject      => {TYPE => 'varchar(128)'},
1040
 
            body         => {TYPE => 'MEDIUMTEXT'},
1041
 
        ],
1042
 
    },
1043
 
 
1044
 
    # QUIPS
1045
 
    # -----
1046
 
 
1047
 
    quips => {
1048
 
        FIELDS => [
1049
 
            quipid   => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
1050
 
                         PRIMARYKEY => 1},
1051
 
            userid   => {TYPE => 'INT3'},
1052
 
            quip     => {TYPE => 'TEXT', NOTNULL => 1},
1053
 
            approved => {TYPE => 'BOOLEAN', NOTNULL => 1,
1054
 
                         DEFAULT => 'TRUE'},
1055
 
        ],
1056
 
    },
1057
 
 
1058
 
    # SETTINGS
1059
 
    # --------
1060
 
    # setting          - each global setting will have exactly one entry
1061
 
    #                    in this table.
1062
 
    # setting_value    - stores the list of acceptable values for each
1063
 
    #                    setting, and a sort index that controls the order
1064
 
    #                    in which the values are displayed.
1065
 
    # profile_setting  - If a user has chosen to use a value other than the
1066
 
    #                    global default for a given setting, it will be
1067
 
    #                    stored in this table. Note: even if a setting is
1068
 
    #                    later changed so is_enabled = false, the stored
1069
 
    #                    value will remain in case it is ever enabled again.
1070
 
    #
1071
 
    setting => {
1072
 
        FIELDS => [
1073
 
            name          => {TYPE => 'varchar(32)', NOTNULL => 1,
1074
 
                              PRIMARYKEY => 1}, 
1075
 
            default_value => {TYPE => 'varchar(32)', NOTNULL => 1},
1076
 
            is_enabled    => {TYPE => 'BOOLEAN', NOTNULL => 1,
1077
 
                              DEFAULT => 'TRUE'},
1078
 
            subclass      => {TYPE => 'varchar(32)'},
1079
 
        ],
1080
 
    },
1081
 
 
1082
 
    setting_value => {
1083
 
        FIELDS => [
1084
 
            name        => {TYPE => 'varchar(32)', NOTNULL => 1},
1085
 
            value       => {TYPE => 'varchar(32)', NOTNULL => 1},
1086
 
            sortindex   => {TYPE => 'INT2', NOTNULL => 1},
1087
 
        ],
1088
 
        INDEXES => [
1089
 
            setting_value_nv_unique_idx  => {FIELDS => [qw(name value)],
1090
 
                                             TYPE => 'UNIQUE'},
1091
 
            setting_value_ns_unique_idx  => {FIELDS => [qw(name sortindex)],
1092
 
                                             TYPE => 'UNIQUE'},
1093
 
        ],
1094
 
     },
1095
 
 
1096
 
    profile_setting => {
1097
 
        FIELDS => [
1098
 
            user_id       => {TYPE => 'INT3', NOTNULL => 1},
1099
 
            setting_name  => {TYPE => 'varchar(32)', NOTNULL => 1},
1100
 
            setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
1101
 
        ],
1102
 
        INDEXES => [
1103
 
            profile_setting_value_unique_idx  => {FIELDS => [qw(user_id setting_name)],
1104
 
                                                  TYPE => 'UNIQUE'},
1105
 
        ],
1106
 
     },
1107
 
 
1108
 
    # SCHEMA STORAGE
1109
 
    # --------------
1110
 
 
1111
 
    bz_schema => {
1112
 
        FIELDS => [
1113
 
            schema_data => {TYPE => 'LONGBLOB', NOTNULL => 1},
1114
 
            version     => {TYPE => 'decimal(3,2)', NOTNULL => 1},
1115
 
        ],
1116
 
    },
1117
 
 
1118
 
};
1119
 
 
1120
 
use constant FIELD_TABLE_SCHEMA => {
1121
 
    FIELDS => [
1122
 
        id       => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
1123
 
                     PRIMARYKEY => 1},
1124
 
        value    => {TYPE => 'varchar(64)', NOTNULL => 1},
1125
 
        sortkey  => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
1126
 
        isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
1127
 
                     DEFAULT => 'TRUE'},
1128
 
    ],
1129
 
    # Note that bz_add_field_table should prepend the table name
1130
 
    # to these index names.
1131
 
    INDEXES => [
1132
 
        value_idx   => {FIELDS => ['value'], TYPE => 'UNIQUE'},
1133
 
        sortkey_idx => ['sortkey', 'value'],
1134
 
    ],
1135
 
};
1136
 
#--------------------------------------------------------------------------
1137
 
 
1138
 
=head1 METHODS
1139
 
 
1140
 
Note: Methods which can be implemented generically for all DBs are
1141
 
implemented in this module. If needed, they can be overridden with
1142
 
DB-specific code in a subclass. Methods which are prefixed with C<_>
1143
 
are considered protected. Subclasses may override these methods, but
1144
 
other modules should not invoke these methods directly.
1145
 
 
1146
 
=cut
1147
 
 
1148
 
#--------------------------------------------------------------------------
1149
 
sub new {
1150
 
 
1151
 
=over
1152
 
 
1153
 
=item C<new>
1154
 
 
1155
 
 Description: Public constructor method used to instantiate objects of this
1156
 
              class. However, it also can be used as a factory method to
1157
 
              instantiate database-specific subclasses when an optional
1158
 
              driver argument is supplied.
1159
 
 Parameters:  $driver (optional) - Used to specify the type of database.
1160
 
              This routine C<die>s if no subclass is found for the specified
1161
 
              driver.
1162
 
              $schema (optional) - A reference to a hash. Callers external
1163
 
                  to this package should never use this parameter.
1164
 
 Returns:     new instance of the Schema class or a database-specific subclass
1165
 
 
1166
 
=cut
1167
 
 
1168
 
    my $this = shift;
1169
 
    my $class = ref($this) || $this;
1170
 
    my $driver = shift;
1171
 
 
1172
 
    if ($driver) {
1173
 
        (my $subclass = $driver) =~ s/^(\S)/\U$1/;
1174
 
        $class .= '::' . $subclass;
1175
 
        eval "require $class;";
1176
 
        die "The $class class could not be found ($subclass " .
1177
 
            "not supported?): $@" if ($@);
1178
 
    }
1179
 
    die "$class is an abstract base class. Instantiate a subclass instead."
1180
 
      if ($class eq __PACKAGE__);
1181
 
 
1182
 
    my $self = {};
1183
 
    bless $self, $class;
1184
 
    $self = $self->_initialize(@_);
1185
 
 
1186
 
    return($self);
1187
 
 
1188
 
} #eosub--new
1189
 
#--------------------------------------------------------------------------
1190
 
sub _initialize {
1191
 
 
1192
 
=item C<_initialize>
1193
 
 
1194
 
 Description: Protected method that initializes an object after
1195
 
              instantiation with the abstract schema. All subclasses should
1196
 
              override this method. The typical subclass implementation
1197
 
              should first call the C<_initialize> method of the superclass,
1198
 
              then do any database-specific initialization (especially
1199
 
              define the database-specific implementation of the all
1200
 
              abstract data types), and then call the C<_adjust_schema>
1201
 
              method.
1202
 
 Parameters:  $abstract_schema (optional) - A reference to a hash. If 
1203
 
                  provided, this hash will be used as the internal
1204
 
                  representation of the abstract schema instead of our
1205
 
                  default abstract schema. This is intended for internal 
1206
 
                  use only by deserialize_abstract.
1207
 
 Returns:     the instance of the Schema class
1208
 
 
1209
 
=cut
1210
 
 
1211
 
    my $self = shift;
1212
 
    my $abstract_schema = shift;
1213
 
 
1214
 
    if (!$abstract_schema) {
1215
 
        # While ABSTRACT_SCHEMA cannot be modified, $abstract_schema can be.
1216
 
        # So, we dclone it to prevent anything from mucking with the constant.
1217
 
        $abstract_schema = dclone(ABSTRACT_SCHEMA);
1218
 
 
1219
 
        # Let extensions add tables, but make sure they can't modify existing
1220
 
        # tables. If we don't lock/unlock keys, lock_value complains.
1221
 
        lock_keys(%$abstract_schema);
1222
 
        foreach my $table (keys %{ABSTRACT_SCHEMA()}) {
1223
 
            lock_value(%$abstract_schema, $table) 
1224
 
                if exists $abstract_schema->{$table};
1225
 
        }
1226
 
        unlock_keys(%$abstract_schema);
1227
 
        Bugzilla::Hook::process('db_schema-abstract_schema', 
1228
 
                                { schema => $abstract_schema });
1229
 
        unlock_hash(%$abstract_schema);
1230
 
    }
1231
 
 
1232
 
    $self->{schema} = dclone($abstract_schema);
1233
 
    $self->{abstract_schema} = $abstract_schema;
1234
 
 
1235
 
    return $self;
1236
 
 
1237
 
} #eosub--_initialize
1238
 
#--------------------------------------------------------------------------
1239
 
sub _adjust_schema {
1240
 
 
1241
 
=item C<_adjust_schema>
1242
 
 
1243
 
 Description: Protected method that alters the abstract schema at
1244
 
              instantiation-time to be database-specific. It is a generic
1245
 
              enough routine that it can be defined here in the base class.
1246
 
              It takes the abstract schema and replaces the abstract data
1247
 
              types with database-specific data types.
1248
 
 Parameters:  none
1249
 
 Returns:     the instance of the Schema class
1250
 
 
1251
 
=cut
1252
 
 
1253
 
    my $self = shift;
1254
 
 
1255
 
    # The _initialize method has already set up the db_specific hash with
1256
 
    # the information on how to implement the abstract data types for the
1257
 
    # instantiated DBMS-specific subclass.
1258
 
    my $db_specific = $self->{db_specific};
1259
 
 
1260
 
    # Loop over each table in the abstract database schema.
1261
 
    foreach my $table (keys %{ $self->{schema} }) {
1262
 
        my %fields = (@{ $self->{schema}{$table}{FIELDS} });
1263
 
        # Loop over the field definitions in each table.
1264
 
        foreach my $field_def (values %fields) {
1265
 
            # If the field type is an abstract data type defined in the
1266
 
            # $db_specific hash, replace it with the DBMS-specific data type
1267
 
            # that implements it.
1268
 
            if (exists($db_specific->{$field_def->{TYPE}})) {
1269
 
                $field_def->{TYPE} = $db_specific->{$field_def->{TYPE}};
1270
 
            }
1271
 
            # Replace abstract default values (such as 'TRUE' and 'FALSE')
1272
 
            # with their database-specific implementations.
1273
 
            if (exists($field_def->{DEFAULT})
1274
 
                && exists($db_specific->{$field_def->{DEFAULT}})) {
1275
 
                $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}};
1276
 
            }
1277
 
        }
1278
 
    }
1279
 
 
1280
 
    return $self;
1281
 
 
1282
 
} #eosub--_adjust_schema
1283
 
#--------------------------------------------------------------------------
1284
 
sub get_type_ddl {
1285
 
 
1286
 
=item C<get_type_ddl>
1287
 
 
1288
 
 Description: Public method to convert abstract (database-generic) field
1289
 
              specifiers to database-specific data types suitable for use
1290
 
              in a C<CREATE TABLE> or C<ALTER TABLE> SQL statment. If no
1291
 
              database-specific field type has been defined for the given
1292
 
              field type, then it will just return the same field type.
1293
 
 Parameters:  a hash or a reference to a hash of a field containing the
1294
 
              following keys: C<TYPE> (required), C<NOTNULL> (optional),
1295
 
              C<DEFAULT> (optional), C<PRIMARYKEY> (optional), C<REFERENCES>
1296
 
              (optional)
1297
 
 Returns:     a DDL string suitable for describing a field in a
1298
 
              C<CREATE TABLE> or C<ALTER TABLE> SQL statement
1299
 
 
1300
 
=cut
1301
 
 
1302
 
    my $self = shift;
1303
 
    my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ };
1304
 
 
1305
 
    my $type = $finfo->{TYPE};
1306
 
    die "A valid TYPE was not specified for this column." unless ($type);
1307
 
 
1308
 
    my $default = $finfo->{DEFAULT};
1309
 
    # Replace any abstract default value (such as 'TRUE' or 'FALSE')
1310
 
    # with its database-specific implementation.
1311
 
    if ( defined $default && exists($self->{db_specific}->{$default}) ) {
1312
 
        $default = $self->{db_specific}->{$default};
1313
 
    }
1314
 
 
1315
 
    my $fkref = $self->{enable_references} ? $finfo->{REFERENCES} : undef;
1316
 
    my $type_ddl = $self->convert_type($type);
1317
 
    # DEFAULT attribute must appear before any column constraints
1318
 
    # (e.g., NOT NULL), for Oracle
1319
 
    $type_ddl .= " DEFAULT $default" if (defined($default));
1320
 
    $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
1321
 
    $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
1322
 
    $type_ddl .= "\n\t\t\t\tREFERENCES $fkref" if $fkref;
1323
 
 
1324
 
    return($type_ddl);
1325
 
 
1326
 
} #eosub--get_type_ddl
1327
 
 
1328
 
sub convert_type {
1329
 
 
1330
 
=item C<convert_type>
1331
 
 
1332
 
Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
1333
 
 
1334
 
=cut
1335
 
 
1336
 
    my ($self, $type) = @_;
1337
 
    return $self->{db_specific}->{$type} || $type;
1338
 
}
1339
 
 
1340
 
sub get_column {
1341
 
=item C<get_column($table, $column)>
1342
 
 
1343
 
 Description: Public method to get the abstract definition of a column.
1344
 
 Parameters:  $table - the table name
1345
 
              $column - a column in the table
1346
 
 Returns:     a hashref containing information about the column, including its
1347
 
              type (C<TYPE>), whether or not it can be null (C<NOTNULL>),
1348
 
              its default value if it has one (C<DEFAULT), etc.
1349
 
              Returns undef if the table or column does not exist.
1350
 
 
1351
 
=cut
1352
 
 
1353
 
    my($self, $table, $column) = @_;
1354
 
 
1355
 
    # Prevent a possible dereferencing of an undef hash, if the
1356
 
    # table doesn't exist.
1357
 
    if (exists $self->{schema}->{$table}) {
1358
 
        my %fields = (@{ $self->{schema}{$table}{FIELDS} });
1359
 
        return $fields{$column};
1360
 
    }
1361
 
    return undef;
1362
 
} #eosub--get_column
1363
 
 
1364
 
sub get_table_list {
1365
 
 
1366
 
=item C<get_table_list>
1367
 
 
1368
 
 Description: Public method for discovering what tables should exist in the
1369
 
              Bugzilla database.
1370
 
 Parameters:  none
1371
 
 Returns:     an array of table names
1372
 
 
1373
 
=cut
1374
 
 
1375
 
    my $self = shift;
1376
 
 
1377
 
    return(sort(keys %{ $self->{schema} }));
1378
 
 
1379
 
} #eosub--get_table_list
1380
 
#--------------------------------------------------------------------------
1381
 
sub get_table_columns {
1382
 
 
1383
 
=item C<get_table_columns>
1384
 
 
1385
 
 Description: Public method for discovering what columns are in a given
1386
 
              table in the Bugzilla database.
1387
 
 Parameters:  $table - the table name
1388
 
 Returns:     array of column names
1389
 
 
1390
 
=cut
1391
 
 
1392
 
    my($self, $table) = @_;
1393
 
    my @ddl = ();
1394
 
 
1395
 
    my $thash = $self->{schema}{$table};
1396
 
    die "Table $table does not exist in the database schema."
1397
 
        unless (ref($thash));
1398
 
 
1399
 
    my @columns = ();
1400
 
    my @fields = @{ $thash->{FIELDS} };
1401
 
    while (@fields) {
1402
 
        push(@columns, shift(@fields));
1403
 
        shift(@fields);
1404
 
    }
1405
 
 
1406
 
    return @columns;
1407
 
 
1408
 
} #eosub--get_table_columns
1409
 
 
1410
 
sub get_table_indexes_abstract {
1411
 
    my ($self, $table) = @_;
1412
 
    my $table_def = $self->get_table_abstract($table);
1413
 
    my %indexes = @{$table_def->{INDEXES} || []};
1414
 
    return \%indexes;
1415
 
}
1416
 
 
1417
 
sub get_create_database_sql {
1418
 
    my ($self, $name) = @_;
1419
 
    return ("CREATE DATABASE $name");
1420
 
}
1421
 
 
1422
 
sub get_table_ddl {
1423
 
 
1424
 
=item C<get_table_ddl>
1425
 
 
1426
 
 Description: Public method to generate the SQL statements needed to create
1427
 
              the a given table and its indexes in the Bugzilla database.
1428
 
              Subclasses may override or extend this method, if needed, but
1429
 
              subclasses probably should override C<_get_create_table_ddl>
1430
 
              or C<_get_create_index_ddl> instead.
1431
 
 Parameters:  $table - the table name
1432
 
 Returns:     an array of strings containing SQL statements
1433
 
 
1434
 
=cut
1435
 
 
1436
 
    my($self, $table) = @_;
1437
 
    my @ddl = ();
1438
 
 
1439
 
    die "Table $table does not exist in the database schema."
1440
 
        unless (ref($self->{schema}{$table}));
1441
 
 
1442
 
    my $create_table = $self->_get_create_table_ddl($table);
1443
 
    push(@ddl, $create_table) if $create_table;
1444
 
 
1445
 
    my @indexes = @{ $self->{schema}{$table}{INDEXES} || [] };
1446
 
    while (@indexes) {
1447
 
        my $index_name = shift(@indexes);
1448
 
        my $index_info = shift(@indexes);
1449
 
        my $index_sql  = $self->get_add_index_ddl($table, $index_name, 
1450
 
                                                  $index_info);
1451
 
        push(@ddl, $index_sql) if $index_sql;
1452
 
    }
1453
 
 
1454
 
    push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} })
1455
 
      if (ref($self->{schema}{$table}{DB_EXTRAS}));
1456
 
 
1457
 
    return @ddl;
1458
 
 
1459
 
} #eosub--get_table_ddl
1460
 
#--------------------------------------------------------------------------
1461
 
sub _get_create_table_ddl {
1462
 
 
1463
 
=item C<_get_create_table_ddl>
1464
 
 
1465
 
 Description: Protected method to generate the "create table" SQL statement
1466
 
              for a given table.
1467
 
 Parameters:  $table - the table name
1468
 
 Returns:     a string containing the DDL statement for the specified table
1469
 
 
1470
 
=cut
1471
 
 
1472
 
    my($self, $table) = @_;
1473
 
 
1474
 
    my $thash = $self->{schema}{$table};
1475
 
    die "Table $table does not exist in the database schema."
1476
 
        unless (ref($thash));
1477
 
 
1478
 
    my $create_table = "CREATE TABLE $table \(\n";
1479
 
 
1480
 
    my @fields = @{ $thash->{FIELDS} };
1481
 
    while (@fields) {
1482
 
        my $field = shift(@fields);
1483
 
        my $finfo = shift(@fields);
1484
 
        $create_table .= "\t$field\t" . $self->get_type_ddl($finfo);
1485
 
        $create_table .= "," if (@fields);
1486
 
        $create_table .= "\n";
1487
 
    }
1488
 
 
1489
 
    $create_table .= "\)";
1490
 
 
1491
 
    return($create_table)
1492
 
 
1493
 
} #eosub--_get_create_table_ddl
1494
 
#--------------------------------------------------------------------------
1495
 
sub _get_create_index_ddl {
1496
 
 
1497
 
=item C<_get_create_index_ddl>
1498
 
 
1499
 
 Description: Protected method to generate a "create index" SQL statement
1500
 
              for a given table and index.
1501
 
 Parameters:  $table_name - the name of the table
1502
 
              $index_name - the name of the index
1503
 
              $index_fields - a reference to an array of field names
1504
 
              $index_type (optional) - specify type of index (e.g., UNIQUE)
1505
 
 Returns:     a string containing the DDL statement
1506
 
 
1507
 
=cut
1508
 
 
1509
 
    my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
1510
 
 
1511
 
    my $sql = "CREATE ";
1512
 
    $sql .= "$index_type " if ($index_type && $index_type eq 'UNIQUE');
1513
 
    $sql .= "INDEX $index_name ON $table_name \(" .
1514
 
      join(", ", @$index_fields) . "\)";
1515
 
 
1516
 
    return($sql);
1517
 
 
1518
 
} #eosub--_get_create_index_ddl
1519
 
#--------------------------------------------------------------------------
1520
 
 
1521
 
sub get_add_column_ddl {
1522
 
 
1523
 
=item C<get_add_column_ddl($table, $column, \%definition, $init_value)>
1524
 
 
1525
 
 Description: Generate SQL to add a column to a table.
1526
 
 Params:      $table - The table containing the column.
1527
 
              $column - The name of the column being added.
1528
 
              \%definition - The new definition for the column,
1529
 
                  in standard C<ABSTRACT_SCHEMA> format.
1530
 
              $init_value - (optional) An initial value to set 
1531
 
                            the column to. Should already be SQL-quoted
1532
 
                            if necessary.
1533
 
 Returns:     An array of SQL statements.
1534
 
 
1535
 
=cut
1536
 
 
1537
 
    my ($self, $table, $column, $definition, $init_value) = @_;
1538
 
    my @statements;
1539
 
    push(@statements, "ALTER TABLE $table ADD COLUMN $column " .
1540
 
        $self->get_type_ddl($definition));
1541
 
 
1542
 
    # XXX - Note that although this works for MySQL, most databases will fail
1543
 
    # before this point, if we haven't set a default.
1544
 
    (push(@statements, "UPDATE $table SET $column = $init_value"))
1545
 
        if defined $init_value;
1546
 
 
1547
 
    return (@statements);
1548
 
}
1549
 
 
1550
 
sub get_add_index_ddl {
1551
 
 
1552
 
=item C<get_add_index_ddl>
1553
 
 
1554
 
 Description: Gets SQL for creating an index.
1555
 
              NOTE: Subclasses should not override this function. Instead,
1556
 
              if they need to specify a custom CREATE INDEX statement, 
1557
 
              they should override C<_get_create_index_ddl>
1558
 
 Params:      $table - The name of the table the index will be on.
1559
 
              $name  - The name of the new index.
1560
 
              $definition - An index definition. Either a hashref 
1561
 
                            with FIELDS and TYPE or an arrayref 
1562
 
                            containing a list of columns.
1563
 
 Returns:     An array of SQL statements that will create the 
1564
 
              requested index.
1565
 
 
1566
 
=cut
1567
 
 
1568
 
    my ($self, $table, $name, $definition) = @_;
1569
 
 
1570
 
    my ($index_fields, $index_type);
1571
 
    # Index defs can be arrays or hashes
1572
 
    if (ref($definition) eq 'HASH') {
1573
 
        $index_fields = $definition->{FIELDS};
1574
 
        $index_type = $definition->{TYPE};
1575
 
    } else {
1576
 
        $index_fields = $definition;
1577
 
        $index_type = '';
1578
 
    }
1579
 
    
1580
 
    return $self->_get_create_index_ddl($table, $name, $index_fields, 
1581
 
                                        $index_type);
1582
 
}
1583
 
 
1584
 
sub get_alter_column_ddl {
1585
 
 
1586
 
=item C<get_alter_column_ddl($table, $column, \%definition)>
1587
 
 
1588
 
 Description: Generate SQL to alter a column in a table.
1589
 
              The column that you are altering must exist,
1590
 
              and the table that it lives in must exist.
1591
 
 Params:      $table - The table containing the column.
1592
 
              $column - The name of the column being changed.
1593
 
              \%definition - The new definition for the column,
1594
 
                  in standard C<ABSTRACT_SCHEMA> format.
1595
 
              $set_nulls_to - A value to set NULL values to, if
1596
 
                  your new definition is NOT NULL and contains
1597
 
                  no DEFAULT, and when there is a possibility
1598
 
                  that the column could contain NULLs. $set_nulls_to
1599
 
                  should be already SQL-quoted if necessary.
1600
 
 Returns:     An array of SQL statements.
1601
 
 
1602
 
=cut
1603
 
 
1604
 
    my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
1605
 
 
1606
 
    my @statements;
1607
 
    my $old_def = $self->get_column_abstract($table, $column);
1608
 
    my $specific = $self->{db_specific};
1609
 
 
1610
 
    # If the types have changed, we have to deal with that.
1611
 
    if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
1612
 
        push(@statements, $self->_get_alter_type_sql($table, $column, 
1613
 
                                                     $new_def, $old_def));
1614
 
    }
1615
 
 
1616
 
    my $default = $new_def->{DEFAULT};
1617
 
    my $default_old = $old_def->{DEFAULT};
1618
 
    # This first condition prevents "uninitialized value" errors.
1619
 
    if (!defined $default && !defined $default_old) {
1620
 
        # Do Nothing
1621
 
    }
1622
 
    # If we went from having a default to not having one
1623
 
    elsif (!defined $default && defined $default_old) {
1624
 
        push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
1625
 
                        . " DROP DEFAULT");
1626
 
    }
1627
 
    # If we went from no default to a default, or we changed the default.
1628
 
    elsif ( (defined $default && !defined $default_old) || 
1629
 
            ($default ne $default_old) ) 
1630
 
    {
1631
 
        $default = $specific->{$default} if exists $specific->{$default};
1632
 
        push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
1633
 
                         . " SET DEFAULT $default");
1634
 
    }
1635
 
 
1636
 
    # If we went from NULL to NOT NULL.
1637
 
    if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
1638
 
        my $setdefault;
1639
 
        # Handle any fields that were NULL before, if we have a default,
1640
 
        $setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
1641
 
        # But if we have a set_nulls_to, that overrides the DEFAULT 
1642
 
        # (although nobody would usually specify both a default and 
1643
 
        # a set_nulls_to.)
1644
 
        $setdefault = $set_nulls_to if defined $set_nulls_to;
1645
 
        if (defined $setdefault) {
1646
 
            push(@statements, "UPDATE $table SET $column = $setdefault"
1647
 
                            . "  WHERE $column IS NULL");
1648
 
        }
1649
 
        push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
1650
 
                        . " SET NOT NULL");
1651
 
    }
1652
 
    # If we went from NOT NULL to NULL
1653
 
    elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
1654
 
        push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
1655
 
                        . " DROP NOT NULL");
1656
 
    }
1657
 
 
1658
 
    # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
1659
 
    if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
1660
 
        push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
1661
 
    }
1662
 
    # If we went from being a PK to not being a PK
1663
 
    elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
1664
 
        push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
1665
 
    }
1666
 
 
1667
 
    return @statements;
1668
 
}
1669
 
 
1670
 
sub get_drop_index_ddl {
1671
 
 
1672
 
=item C<get_drop_index_ddl($table, $name)>
1673
 
 
1674
 
 Description: Generates SQL statements to drop an index.
1675
 
 Params:      $table - The table the index is on.
1676
 
              $name  - The name of the index being dropped.
1677
 
 Returns:     An array of SQL statements.
1678
 
 
1679
 
=cut
1680
 
 
1681
 
    my ($self, $table, $name) = @_;
1682
 
 
1683
 
    # Although ANSI SQL-92 doesn't specify a method of dropping an index,
1684
 
    # many DBs support this syntax.
1685
 
    return ("DROP INDEX $name");
1686
 
}
1687
 
 
1688
 
sub get_drop_column_ddl {
1689
 
 
1690
 
=item C<get_drop_column_ddl($table, $column)>
1691
 
 
1692
 
 Description: Generate SQL to drop a column from a table.
1693
 
 Params:      $table - The table containing the column.
1694
 
              $column - The name of the column being dropped.
1695
 
 Returns:     An array of SQL statements.
1696
 
 
1697
 
=cut
1698
 
 
1699
 
    my ($self, $table, $column) = @_;
1700
 
    return ("ALTER TABLE $table DROP COLUMN $column");
1701
 
}
1702
 
 
1703
 
=item C<get_drop_table_ddl($table)>
1704
 
 
1705
 
 Description: Generate SQL to drop a table from the database.
1706
 
 Params:      $table - The name of the table to drop.
1707
 
 Returns:     An array of SQL statements.
1708
 
 
1709
 
=cut
1710
 
 
1711
 
sub get_drop_table_ddl {
1712
 
    my ($self, $table) = @_;
1713
 
    return ("DROP TABLE $table");
1714
 
}
1715
 
 
1716
 
sub get_rename_column_ddl {
1717
 
 
1718
 
=item C<get_rename_column_ddl($table, $old_name, $new_name)>
1719
 
 
1720
 
 Description: Generate SQL to change the name of a column in a table.
1721
 
              NOTE: ANSI SQL contains no simple way to rename a column,
1722
 
                    so this function is ABSTRACT and must be implemented
1723
 
                    by subclasses.
1724
 
 Params:      $table - The table containing the column to be renamed.
1725
 
              $old_name - The name of the column being renamed.
1726
 
              $new_name - The name the column is changing to.
1727
 
 Returns:     An array of SQL statements.
1728
 
 
1729
 
=cut
1730
 
 
1731
 
    die "ANSI SQL has no way to rename a column, and your database driver\n"
1732
 
        . " has not implemented a method.";
1733
 
}
1734
 
 
1735
 
 
1736
 
sub get_rename_table_sql {
1737
 
 
1738
 
=item C<get_rename_table_sql>
1739
 
 
1740
 
=over
1741
 
 
1742
 
=item B<Description>
1743
 
 
1744
 
Gets SQL to rename a table in the database.
1745
 
 
1746
 
=item B<Params>
1747
 
 
1748
 
=over
1749
 
 
1750
 
=item C<$old_name> - The current name of the table.
1751
 
 
1752
 
=item C<$new_name> - The new name of the table.
1753
 
 
1754
 
=back
1755
 
 
1756
 
=item B<Returns>: An array of SQL statements to rename a table.
1757
 
 
1758
 
=back
1759
 
 
1760
 
=cut
1761
 
 
1762
 
    my ($self, $old_name, $new_name) = @_;
1763
 
    return ("ALTER TABLE $old_name RENAME TO $new_name");
1764
 
}
1765
 
 
1766
 
=item C<delete_table($name)>
1767
 
 
1768
 
 Description: Deletes a table from this Schema object.
1769
 
              Dies if you try to delete a table that doesn't exist.
1770
 
 Params:      $name - The name of the table to delete.
1771
 
 Returns:     nothing
1772
 
 
1773
 
=cut
1774
 
 
1775
 
sub delete_table {
1776
 
    my ($self, $name) = @_;
1777
 
 
1778
 
    die "Attempted to delete nonexistent table '$name'." unless
1779
 
        $self->get_table_abstract($name);
1780
 
 
1781
 
    delete $self->{abstract_schema}->{$name};
1782
 
    delete $self->{schema}->{$name};
1783
 
}
1784
 
 
1785
 
sub get_column_abstract {
1786
 
 
1787
 
=item C<get_column_abstract($table, $column)>
1788
 
 
1789
 
 Description: A column definition from the abstract internal schema.
1790
 
              cross-database format.
1791
 
 Params:      $table - The name of the table
1792
 
              $column - The name of the column that you want
1793
 
 Returns:     A hash reference. For the format, see the docs for
1794
 
              C<ABSTRACT_SCHEMA>.
1795
 
              Returns undef if the column or table does not exist.
1796
 
 
1797
 
=cut
1798
 
 
1799
 
    my ($self, $table, $column) = @_;
1800
 
 
1801
 
    # Prevent a possible dereferencing of an undef hash, if the
1802
 
    # table doesn't exist.
1803
 
    if ($self->get_table_abstract($table)) {
1804
 
        my %fields = (@{ $self->{abstract_schema}{$table}{FIELDS} });
1805
 
        return $fields{$column};
1806
 
    }
1807
 
    return undef;
1808
 
}
1809
 
 
1810
 
=item C<get_indexes_on_column_abstract($table, $column)>
1811
 
 
1812
 
 Description: Gets a list of indexes that are on a given column.
1813
 
 Params:      $table - The table the column is on.
1814
 
              $column - The name of the column.
1815
 
 Returns:     Indexes in the standard format of an INDEX
1816
 
              entry on a table. That is, key-value pairs
1817
 
              where the key is the index name and the value
1818
 
              is the index definition.
1819
 
              If there are no indexes on that column, we return
1820
 
              undef.
1821
 
 
1822
 
=cut
1823
 
 
1824
 
sub get_indexes_on_column_abstract {
1825
 
    my ($self, $table, $column) = @_;
1826
 
    my %ret_hash;
1827
 
 
1828
 
    my $table_def = $self->get_table_abstract($table);
1829
 
    if ($table_def && exists $table_def->{INDEXES}) {
1830
 
        my %indexes = (@{ $table_def->{INDEXES} });
1831
 
        foreach my $index_name (keys %indexes) {
1832
 
            my $col_list;
1833
 
            # Get the column list, depending on whether the index
1834
 
            # is in hashref or arrayref format.
1835
 
            if (ref($indexes{$index_name}) eq 'HASH') {
1836
 
                $col_list = $indexes{$index_name}->{FIELDS};
1837
 
            } else {
1838
 
                $col_list = $indexes{$index_name};
1839
 
            }
1840
 
 
1841
 
            if(grep($_ eq $column, @$col_list)) {
1842
 
                $ret_hash{$index_name} = dclone($indexes{$index_name});
1843
 
            }
1844
 
        }
1845
 
    }
1846
 
 
1847
 
    return %ret_hash;
1848
 
}
1849
 
 
1850
 
sub get_index_abstract {
1851
 
 
1852
 
=item C<get_index_abstract($table, $index)>
1853
 
 
1854
 
 Description: Returns an index definition from the internal abstract schema.
1855
 
 Params:      $table - The table the index is on.
1856
 
              $index - The name of the index.
1857
 
 Returns:     A hash reference representing an index definition.
1858
 
              See the C<ABSTRACT_SCHEMA> docs for details.
1859
 
              Returns undef if the index does not exist.
1860
 
 
1861
 
=cut
1862
 
 
1863
 
    my ($self, $table, $index) = @_;
1864
 
 
1865
 
    # Prevent a possible dereferencing of an undef hash, if the
1866
 
    # table doesn't exist.
1867
 
    my $index_table = $self->get_table_abstract($table);
1868
 
    if ($index_table && exists $index_table->{INDEXES}) {
1869
 
        my %indexes = (@{ $index_table->{INDEXES} });
1870
 
        return $indexes{$index};
1871
 
    }
1872
 
    return undef;
1873
 
}
1874
 
 
1875
 
=item C<get_table_abstract($table)>
1876
 
 
1877
 
 Description: Gets the abstract definition for a table in this Schema
1878
 
              object.
1879
 
 Params:      $table - The name of the table you want a definition for.
1880
 
 Returns:     An abstract table definition, or undef if the table doesn't
1881
 
              exist.
1882
 
 
1883
 
=cut
1884
 
 
1885
 
sub get_table_abstract {
1886
 
    my ($self, $table) = @_;
1887
 
    return $self->{abstract_schema}->{$table};
1888
 
}
1889
 
 
1890
 
=item C<add_table($name, \%definition)>
1891
 
 
1892
 
 Description: Creates a new table in this Schema object.
1893
 
              If you do not specify a definition, we will
1894
 
              simply create an empty table.
1895
 
 Params:      $name - The name for the new table.
1896
 
              \%definition (optional) - An abstract definition for
1897
 
                  the new table.
1898
 
 Returns:     nothing
1899
 
 
1900
 
=cut
1901
 
 
1902
 
sub add_table {
1903
 
    my ($self, $name, $definition) = @_;
1904
 
    (die "Table already exists: $name")
1905
 
        if exists $self->{abstract_schema}->{$name};
1906
 
    if ($definition) {
1907
 
        $self->{abstract_schema}->{$name} = dclone($definition);
1908
 
        $self->{schema} = dclone($self->{abstract_schema});
1909
 
        $self->_adjust_schema();
1910
 
    }
1911
 
    else {
1912
 
        $self->{abstract_schema}->{$name} = {FIELDS => []};
1913
 
        $self->{schema}->{$name}          = {FIELDS => []};
1914
 
    }
1915
 
}
1916
 
 
1917
 
 
1918
 
 
1919
 
sub rename_table {
1920
 
 
1921
 
=item C<rename_table>
1922
 
 
1923
 
Renames a table from C<$old_name> to C<$new_name> in this Schema object.
1924
 
 
1925
 
=cut
1926
 
 
1927
 
 
1928
 
    my ($self, $old_name, $new_name) = @_;
1929
 
    my $table = $self->get_table_abstract($old_name);
1930
 
    $self->delete_table($old_name);
1931
 
    $self->add_table($new_name, $table);
1932
 
}
1933
 
 
1934
 
sub delete_column {
1935
 
 
1936
 
=item C<delete_column($table, $column)>
1937
 
 
1938
 
 Description: Deletes a column from this Schema object.
1939
 
 Params:      $table - Name of the table that the column is in.
1940
 
                       The table must exist, or we will fail.
1941
 
              $column  - Name of the column to delete.
1942
 
 Returns:     nothing
1943
 
 
1944
 
=cut
1945
 
 
1946
 
    my ($self, $table, $column) = @_;
1947
 
 
1948
 
    my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
1949
 
    my $name_position = lsearch($abstract_fields, $column);
1950
 
    die "Attempted to delete nonexistent column ${table}.${column}" 
1951
 
        if $name_position == -1;
1952
 
    # Delete the key/value pair from the array.
1953
 
    splice(@$abstract_fields, $name_position, 2);
1954
 
 
1955
 
    $self->{schema} = dclone($self->{abstract_schema});
1956
 
    $self->_adjust_schema();
1957
 
}
1958
 
 
1959
 
sub rename_column {
1960
 
 
1961
 
=item C<rename_column($table, $old_name, $new_name)>
1962
 
 
1963
 
 Description: Renames a column on a table in the Schema object.
1964
 
              The column that you are renaming must exist.
1965
 
 Params:      $table - The table the column is on.
1966
 
              $old_name - The current name of the column.
1967
 
              $new_name - The new name of hte column.
1968
 
 Returns:     nothing
1969
 
 
1970
 
=cut
1971
 
 
1972
 
    my ($self, $table, $old_name, $new_name) = @_;
1973
 
    my $def = $self->get_column_abstract($table, $old_name);
1974
 
    die "Renaming a column that doesn't exist" if !$def;
1975
 
    $self->delete_column($table, $old_name);
1976
 
    $self->set_column($table, $new_name, $def);
1977
 
}
1978
 
 
1979
 
sub set_column {
1980
 
 
1981
 
=item C<set_column($table, $column, \%new_def)>
1982
 
 
1983
 
 Description: Changes the definition of a column in this Schema object.
1984
 
              If the column doesn't exist, it will be added.
1985
 
              The table that you specify must already exist in the Schema.
1986
 
              NOTE: This does not affect the database on the disk.
1987
 
              Use the C<Bugzilla::DB> "Schema Modification Methods"
1988
 
              if you want to do that.
1989
 
 Params:      $table - The name of the table that the column is on.
1990
 
              $column - The name of the column.
1991
 
              \%new_def - The new definition for the column, in 
1992
 
                  C<ABSTRACT_SCHEMA> format.
1993
 
 Returns:     nothing
1994
 
 
1995
 
=cut
1996
 
 
1997
 
    my ($self, $table, $column, $new_def) = @_;
1998
 
 
1999
 
    my $fields = $self->{abstract_schema}{$table}{FIELDS};
2000
 
    $self->_set_object($table, $column, $new_def, $fields);
2001
 
}
2002
 
 
2003
 
sub set_index {
2004
 
 
2005
 
=item C<set_index($table, $name, $definition)>
2006
 
 
2007
 
 Description: Changes the definition of an index in this Schema object.
2008
 
              If the index doesn't exist, it will be added.
2009
 
              The table that you specify must already exist in the Schema.
2010
 
              NOTE: This does not affect the database on the disk.
2011
 
              Use the C<Bugzilla::DB> "Schema Modification Methods"
2012
 
              if you want to do that.
2013
 
 Params:      $table      - The table the index is on.
2014
 
              $name       - The name of the index.
2015
 
              $definition - A hashref or an arrayref. An index 
2016
 
                            definition in C<ABSTRACT_SCHEMA> format.
2017
 
 Returns:     nothing
2018
 
 
2019
 
=cut
2020
 
 
2021
 
    my ($self, $table, $name, $definition) = @_;
2022
 
 
2023
 
    if ( exists $self->{abstract_schema}{$table}
2024
 
         && !exists $self->{abstract_schema}{$table}{INDEXES} ) {
2025
 
        $self->{abstract_schema}{$table}{INDEXES} = [];
2026
 
    }
2027
 
 
2028
 
    my $indexes = $self->{abstract_schema}{$table}{INDEXES};
2029
 
    $self->_set_object($table, $name, $definition, $indexes);
2030
 
}
2031
 
 
2032
 
# A private helper for set_index and set_column.
2033
 
# This does the actual "work" of those two functions.
2034
 
# $array_to_change is an arrayref.
2035
 
sub _set_object {
2036
 
    my ($self, $table, $name, $definition, $array_to_change) = @_;
2037
 
 
2038
 
    my $obj_position = lsearch($array_to_change, $name) + 1;
2039
 
    # If the object doesn't exist, then add it.
2040
 
    if (!$obj_position) {
2041
 
        push(@$array_to_change, $name);
2042
 
        push(@$array_to_change, $definition);
2043
 
    }
2044
 
    # We're modifying an existing object in the Schema.
2045
 
    else {
2046
 
        splice(@$array_to_change, $obj_position, 1, $definition);
2047
 
    }
2048
 
 
2049
 
    $self->{schema} = dclone($self->{abstract_schema});
2050
 
    $self->_adjust_schema();
2051
 
}
2052
 
 
2053
 
=item C<delete_index($table, $name)>
2054
 
 
2055
 
 Description: Removes an index definition from this Schema object.
2056
 
              If the index doesn't exist, we will fail.
2057
 
              The table that you specify must exist in the Schema.
2058
 
              NOTE: This does not affect the database on the disk.
2059
 
              Use the C<Bugzilla::DB> "Schema Modification Methods"
2060
 
              if you want to do that.
2061
 
 Params:      $table - The table the index is on.
2062
 
              $name  - The name of the index that we're removing.
2063
 
 Returns:     nothing
2064
 
 
2065
 
=cut
2066
 
 
2067
 
sub delete_index {
2068
 
    my ($self, $table, $name) = @_;
2069
 
 
2070
 
    my $indexes = $self->{abstract_schema}{$table}{INDEXES};
2071
 
    my $name_position = lsearch($indexes, $name);
2072
 
    die "Attempted to delete nonexistent index $name on the $table table" 
2073
 
        if $name_position == -1;
2074
 
    # Delete the key/value pair from the array.
2075
 
    splice(@$indexes, $name_position, 2);
2076
 
    $self->{schema} = dclone($self->{abstract_schema});
2077
 
    $self->_adjust_schema();
2078
 
}
2079
 
 
2080
 
sub columns_equal {
2081
 
 
2082
 
=item C<columns_equal($col_one, $col_two)>
2083
 
 
2084
 
 Description: Tells you if two columns have entirely identical definitions.
2085
 
              The TYPE field's value will be compared case-insensitive.
2086
 
              However, all other fields will be case-sensitive.
2087
 
 Params:      $col_one, $col_two - The columns to compare. Hash 
2088
 
                  references, in C<ABSTRACT_SCHEMA> format.
2089
 
 Returns:     C<1> if the columns are identical, C<0> if they are not.
2090
 
 
2091
 
=back
2092
 
 
2093
 
=cut
2094
 
 
2095
 
    my $self = shift;
2096
 
    my $col_one = dclone(shift);
2097
 
    my $col_two = dclone(shift);
2098
 
 
2099
 
    $col_one->{TYPE} = uc($col_one->{TYPE});
2100
 
    $col_two->{TYPE} = uc($col_two->{TYPE});
2101
 
 
2102
 
    my @col_one_array = %$col_one;
2103
 
    my @col_two_array = %$col_two;
2104
 
 
2105
 
    my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
2106
 
 
2107
 
    # If there are no differences between the arrays,
2108
 
    # then they are equal.
2109
 
    return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
2110
 
}
2111
 
 
2112
 
 
2113
 
=head1 SERIALIZATION/DESERIALIZATION
2114
 
 
2115
 
=over 4
2116
 
 
2117
 
=item C<serialize_abstract()>
2118
 
 
2119
 
 Description: Serializes the "abstract" schema into a format
2120
 
              that deserialize_abstract() can read in. This is
2121
 
              a method, called on a Schema instance.
2122
 
 Parameters:  none
2123
 
 Returns:     A scalar containing the serialized, abstract schema.
2124
 
              Do not attempt to manipulate this data directly,
2125
 
              as the format may change at any time in the future.
2126
 
              The only thing you should do with the returned value
2127
 
              is either store it somewhere (coupled with appropriate 
2128
 
              SCHEMA_VERSION) or deserialize it.
2129
 
 
2130
 
=cut
2131
 
 
2132
 
sub serialize_abstract {
2133
 
    my ($self) = @_;
2134
 
    
2135
 
    # Make it ok to eval
2136
 
    local $Data::Dumper::Purity = 1;
2137
 
    
2138
 
    # Avoid cross-refs
2139
 
    local $Data::Dumper::Deepcopy = 1;
2140
 
    
2141
 
    # Always sort keys to allow textual compare
2142
 
    local $Data::Dumper::Sortkeys = 1;
2143
 
    
2144
 
    return Dumper($self->{abstract_schema});
2145
 
}
2146
 
 
2147
 
=item C<deserialize_abstract($serialized, $version)>
2148
 
 
2149
 
 Description: Used for when you've read a serialized Schema off the disk,
2150
 
              and you want a Schema object that represents that data.
2151
 
 Params:      $serialized - scalar. The serialized data.
2152
 
              $version - A number in the format X.YZ. The "version"
2153
 
                  of the Schema that did the serialization.
2154
 
                  See the docs for C<SCHEMA_VERSION> for more details.
2155
 
 Returns:     A Schema object. It will have the methods of (and work 
2156
 
              in the same fashion as) the current version of Schema. 
2157
 
              However, it will represent the serialized data instead of
2158
 
              ABSTRACT_SCHEMA.
2159
 
=cut
2160
 
 
2161
 
sub deserialize_abstract {
2162
 
    my ($class, $serialized, $version) = @_;
2163
 
 
2164
 
    my $thawed_hash;
2165
 
    if (int($version) < 2) {
2166
 
        $thawed_hash = thaw($serialized);
2167
 
    }
2168
 
    else {
2169
 
        my $cpt = new Safe;
2170
 
        $cpt->reval($serialized) ||
2171
 
            die "Unable to restore cached schema: " . $@;
2172
 
        $thawed_hash = ${$cpt->varglob('VAR1')};
2173
 
    }
2174
 
 
2175
 
    return $class->new(undef, $thawed_hash);
2176
 
}
2177
 
 
2178
 
#####################################################################
2179
 
# Class Methods
2180
 
#####################################################################
2181
 
 
2182
 
=back
2183
 
 
2184
 
=head1 CLASS METHODS
2185
 
 
2186
 
These methods are generally called on the class instead of on a specific
2187
 
object.
2188
 
 
2189
 
=over
2190
 
 
2191
 
=item C<get_empty_schema()>
2192
 
 
2193
 
 Description: Returns a Schema that has no tables. In effect, this
2194
 
              Schema is totally "empty."
2195
 
 Params:      none
2196
 
 Returns:     A "empty" Schema object.
2197
 
 
2198
 
=back
2199
 
 
2200
 
=cut
2201
 
 
2202
 
sub get_empty_schema {
2203
 
    my ($class) = @_;
2204
 
    return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
2205
 
}
2206
 
 
2207
 
1;
2208
 
 
2209
 
__END__
2210
 
 
2211
 
=head1 ABSTRACT DATA TYPES
2212
 
 
2213
 
The size and range data provided here is only
2214
 
intended as a guide.  See your database's Bugzilla
2215
 
module (in this directory) for the most up-to-date
2216
 
values for these data types.  The following
2217
 
abstract data types are used:
2218
 
 
2219
 
=over 4
2220
 
 
2221
 
=item C<BOOLEAN>
2222
 
 
2223
 
Logical value 0 or 1 where 1 is true, 0 is false.
2224
 
 
2225
 
=item C<INT1>
2226
 
 
2227
 
Integer values (-128 - 127 or 0 - 255 unsigned).
2228
 
 
2229
 
=item C<INT2>
2230
 
 
2231
 
Integer values (-32,768 - 32767 or 0 - 65,535 unsigned).
2232
 
 
2233
 
=item C<INT3>
2234
 
 
2235
 
Integer values (-8,388,608 - 8,388,607 or 0 - 16,777,215 unsigned)
2236
 
 
2237
 
=item C<INT4>
2238
 
 
2239
 
Integer values (-2,147,483,648 - 2,147,483,647 or 0 - 4,294,967,295 
2240
 
unsigned)
2241
 
 
2242
 
=item C<SMALLSERIAL>
2243
 
 
2244
 
An auto-increment L</INT1>
2245
 
 
2246
 
=item C<MEDIUMSERIAL>
2247
 
 
2248
 
An auto-increment L</INT3>
2249
 
 
2250
 
=item C<INTSERIAL>
2251
 
 
2252
 
An auto-increment L</INT4>
2253
 
 
2254
 
=item C<TINYTEXT>
2255
 
 
2256
 
Variable length string of characters up to 255 (2^8 - 1) characters wide 
2257
 
or more depending on the character set used.
2258
 
 
2259
 
=item C<MEDIUMTEXT>
2260
 
 
2261
 
Variable length string of characters up to 16M (2^24 - 1) characters wide 
2262
 
or more depending on the character set used.
2263
 
 
2264
 
=item C<TEXT>
2265
 
 
2266
 
Variable length string of characters up to 64K (2^16 - 1) characters wide 
2267
 
or more depending on the character set used.
2268
 
 
2269
 
=item C<LONGBLOB>
2270
 
 
2271
 
Variable length string of binary data up to 4M (2^32 - 1) bytes wide
2272
 
 
2273
 
=item C<DATETIME>
2274
 
 
2275
 
DATETIME support varies from database to database, however, it's generally 
2276
 
safe to say that DATETIME entries support all date/time combinations greater
2277
 
than 1900-01-01 00:00:00.  Note that the format used is C<YYYY-MM-DD hh:mm:ss>
2278
 
to be safe, though it's possible that your database may not require
2279
 
leading zeros.  For greatest compatibility, however, please make sure dates 
2280
 
are formatted as above for queries to guarantee consistent results.
2281
 
 
2282
 
=back
2283
 
 
2284
 
Database-specific subclasses should define the implementation for these data
2285
 
types as a hash reference stored internally in the schema object as
2286
 
C<db_specific>. This is typically done in overridden L<_initialize> method.
2287
 
 
2288
 
The following abstract boolean values should also be defined on a
2289
 
database-specific basis:
2290
 
 
2291
 
=over 4
2292
 
 
2293
 
=item C<TRUE>
2294
 
 
2295
 
=item C<FALSE>
2296
 
 
2297
 
=back
2298
 
 
2299
 
=head1 SEE ALSO
2300
 
 
2301
 
L<Bugzilla::DB>
2302
 
 
2303
 
L<http://www.bugzilla.org/docs/developer.html#sql-schema>
2304
 
 
2305
 
=cut