123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565 |
- # -*- Mode: perl; indent-tabs-mode: nil -*-
- #
- # The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
- # Edward J. Sabol <edwardjsabol@iname.com>
- # Max Kanat-Alexander <mkanat@bugzilla.org>
- # Lance Larsh <lance.larsh@oracle.com>
- # Dennis Melentyev <dennis.melentyev@infopulse.com.ua>
- # Akamai Technologies <bugzilla-dev@akamai.com>
- package Bugzilla::DB::Schema;
- ###########################################################################
- #
- # Purpose: Object-oriented, DBMS-independent database schema for Bugzilla
- #
- # This is the base class implementing common methods and abstract schema.
- #
- ###########################################################################
- use strict;
- use Bugzilla::Error;
- use Bugzilla::Hook;
- use Bugzilla::Util;
- use Bugzilla::Constants;
- use Carp qw(confess);
- use Digest::MD5 qw(md5_hex);
- use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
- use Safe;
- # Historical, needed for SCHEMA_VERSION = '1.00'
- use Storable qw(dclone freeze thaw);
- # New SCHEMA_VERSION (2.00) use this
- use Data::Dumper;
- =head1 NAME
- Bugzilla::DB::Schema - Abstract database schema for Bugzilla
- =head1 SYNOPSIS
- # Obtain MySQL database schema.
- # Do not do this. Use Bugzilla::DB instead.
- use Bugzilla::DB::Schema;
- my $mysql_schema = new Bugzilla::DB::Schema('Mysql');
- # Recommended way to obtain database schema.
- use Bugzilla::DB;
- my $dbh = Bugzilla->dbh;
- my $schema = $dbh->_bz_schema();
- # Get the list of tables in the Bugzilla database.
- my @tables = $schema->get_table_list();
- # Get the SQL statements need to create the bugs table.
- my @statements = $schema->get_table_ddl('bugs');
- # Get the database-specific SQL data type used to implement
- # the abstract data type INT1.
- my $db_specific_type = $schema->sql_type('INT1');
- =head1 DESCRIPTION
- This module implements an object-oriented, abstract database schema.
- It should be considered package-private to the Bugzilla::DB module.
- That means that CGI scripts should never call any function in this
- module directly, but should instead rely on methods provided by
- Bugzilla::DB.
- =head1 NEW TO SCHEMA.PM?
- If this is your first time looking at Schema.pm, especially if
- you are making changes to the database, please take a look at
- L<http://www.bugzilla.org/docs/developer.html#sql-schema> to learn
- more about how this integrates into the rest of Bugzilla.
- =cut
- #--------------------------------------------------------------------------
- # Define the Bugzilla abstract database schema and version as constants.
- =head1 CONSTANTS
- =over
- =item C<SCHEMA_VERSION>
- The 'version' of the internal schema structure. This version number
- is incremented every time the the fundamental structure of Schema
- internals changes.
- This is NOT changed every time a table or a column is added. This
- number is incremented only if the internal structures of this
- Schema would be incompatible with the internal structures of a
- previous Schema version.
- In general, unless you are messing around with serialization
- and deserialization of the schema, you don't need to worry about
- this constant.
- =begin private
- An example of the use of the version number:
- Today, we store all individual columns like this:
- column_name => { TYPE => 'SOMETYPE', NOTNULL => 1 }
- Imagine that someday we decide that NOTNULL => 1 is bad, and we want
- to change it so that the schema instead uses NULL => 0.
- But we have a bunch of Bugzilla installations around the world with a
- serialized schema that has NOTNULL in it! When we deserialize that
- structure, it just WILL NOT WORK properly inside of our new Schema object.
- So, immediately after deserializing, we need to go through the hash
- and change all NOTNULLs to NULLs and so on.
- We know that we need to do that on deserializing because we know that
- version 1.00 used NOTNULL. Having made the change to NULL, we would now
- be version 1.01.
- =end private
- =item C<ABSTRACT_SCHEMA>
- The abstract database schema structure consists of a hash reference
- in which each key is the name of a table in the Bugzilla database.
- The value for each key is a hash reference containing the keys
- C<FIELDS> and C<INDEXES> which in turn point to array references
- containing information on the table's fields and indexes.
- A field hash reference should must contain the key C<TYPE>. Optional field
- keys include C<PRIMARYKEY>, C<NOTNULL>, and C<DEFAULT>.
- The C<INDEXES> array reference contains index names and information
- regarding the index. If the index name points to an array reference,
- then the index is a regular index and the array contains the indexed
- columns. If the index name points to a hash reference, then the hash
- must contain the key C<FIELDS>. It may also contain the key C<TYPE>,
- which can be used to specify the type of index such as UNIQUE or FULLTEXT.
- =back
- =head2 Referential Integrity
- Bugzilla::DB::Schema supports "foreign keys", a way of saying
- that "Column X may only contain values from Column Y in Table Z".
- For example, in Bugzilla, bugs.resolution should only contain
- values from the resolution.values field.
- It does this by adding an additional item to a column, called C<REFERENCES>.
- This is a hash with the following members:
- =over
- =item C<TABLE>
- The table the foreign key points at
- =item C<COLUMN>
- The column pointed at in that table.
- =item C<DELETE>
- What to do if the row in the parent table is deleted. Choices are
- C<RESTRICT>, C<CASCADE>, or C<SET NULL>.
- C<RESTRICT> means the deletion of the row in the parent table will
- be forbidden by the database if there is a row in I<this> table that
- still refers to it. This is the default, if you don't specify
- C<DELETE>.
- C<CASCADE> means that this row will be deleted along with that row.
- C<SET NULL> means that the column will be set to C<NULL> when the parent
- row is deleted. Note that this is only valid if the column can actually
- be set to C<NULL>. (That is, the column isn't C<NOT NULL>.)
- =item C<UPDATE>
- What to do if the value in the parent table is updated. You can set this
- to C<CASCADE> or C<RESTRICT>, which mean the same thing as they do for
- L</DELETE>. This variable defaults to C<CASCADE>, which means "also
- update this column in this table."
- =back
- =cut
- use constant SCHEMA_VERSION => '2.00';
- use constant ADD_COLUMN => 'ADD COLUMN';
- # This is a reasonable default that's true for both PostgreSQL and MySQL.
- use constant MAX_IDENTIFIER_LEN => 63;
- use constant ABSTRACT_SCHEMA => {
- # BUG-RELATED TABLES
- # ------------------
- # General Bug Information
- # -----------------------
- bugs => {
- FIELDS => [
- bug_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- assigned_to => {TYPE => 'INT3', NOTNULL => 1},
- bug_file_loc => {TYPE => 'MEDIUMTEXT'},
- bug_severity => {TYPE => 'varchar(64)', NOTNULL => 1},
- bug_status => {TYPE => 'varchar(64)', NOTNULL => 1},
- creation_ts => {TYPE => 'DATETIME'},
- delta_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
- op_sys => {TYPE => 'varchar(64)', NOTNULL => 1},
- priority => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- rep_platform => {TYPE => 'varchar(64)', NOTNULL => 1},
- reporter => {TYPE => 'INT3', NOTNULL => 1},
- version => {TYPE => 'varchar(64)', NOTNULL => 1},
- component_id => {TYPE => 'INT2', NOTNULL => 1},
- resolution => {TYPE => 'varchar(64)',
- NOTNULL => 1, DEFAULT => "''"},
- target_milestone => {TYPE => 'varchar(20)',
- NOTNULL => 1, DEFAULT => "'---'"},
- qa_contact => {TYPE => 'INT3'},
- status_whiteboard => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- votes => {TYPE => 'INT3', NOTNULL => 1,
- DEFAULT => '0'},
- # Note: keywords field is only a cache; the real data
- # comes from the keywords table
- keywords => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- lastdiffed => {TYPE => 'DATETIME'},
- everconfirmed => {TYPE => 'BOOLEAN', NOTNULL => 1},
- reporter_accessible => {TYPE => 'BOOLEAN',
- NOTNULL => 1, DEFAULT => 'TRUE'},
- cclist_accessible => {TYPE => 'BOOLEAN',
- NOTNULL => 1, DEFAULT => 'TRUE'},
- estimated_time => {TYPE => 'decimal(5,2)',
- NOTNULL => 1, DEFAULT => '0'},
- remaining_time => {TYPE => 'decimal(5,2)',
- NOTNULL => 1, DEFAULT => '0'},
- deadline => {TYPE => 'DATETIME'},
- alias => {TYPE => 'varchar(20)'},
- ],
- INDEXES => [
- bugs_alias_idx => {FIELDS => ['alias'],
- TYPE => 'UNIQUE'},
- bugs_assigned_to_idx => ['assigned_to'],
- bugs_creation_ts_idx => ['creation_ts'],
- bugs_delta_ts_idx => ['delta_ts'],
- bugs_bug_severity_idx => ['bug_severity'],
- bugs_bug_status_idx => ['bug_status'],
- bugs_op_sys_idx => ['op_sys'],
- bugs_priority_idx => ['priority'],
- bugs_product_id_idx => ['product_id'],
- bugs_reporter_idx => ['reporter'],
- bugs_version_idx => ['version'],
- bugs_component_id_idx => ['component_id'],
- bugs_resolution_idx => ['resolution'],
- bugs_target_milestone_idx => ['target_milestone'],
- bugs_qa_contact_idx => ['qa_contact'],
- bugs_votes_idx => ['votes'],
- ],
- },
- bugs_fulltext => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1, PRIMARYKEY => 1,
- REFERENCES => {TABLE => 'bugs',
- COLUMN => 'bug_id',
- DELETE => 'CASCADE'}},
- short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
- # Comments are stored all together in one column for searching.
- # This allows us to examine all comments together when deciding
- # the relevance of a bug in fulltext search.
- comments => {TYPE => 'LONGTEXT'},
- comments_noprivate => {TYPE => 'LONGTEXT'},
- ],
- INDEXES => [
- bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'],
- TYPE => 'FULLTEXT'},
- bugs_fulltext_comments_idx => {FIELDS => ['comments'],
- TYPE => 'FULLTEXT'},
- bugs_fulltext_comments_noprivate_idx => {
- FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
- ],
- },
- bugs_activity => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- attach_id => {TYPE => 'INT3'},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1},
- added => {TYPE => 'TINYTEXT'},
- removed => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- bugs_activity_bug_id_idx => ['bug_id'],
- bugs_activity_who_idx => ['who'],
- bugs_activity_bug_when_idx => ['bug_when'],
- bugs_activity_fieldid_idx => ['fieldid'],
- ],
- },
- cc => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- cc_bug_id_idx => {FIELDS => [qw(bug_id who)],
- TYPE => 'UNIQUE'},
- cc_who_idx => ['who'],
- ],
- },
- longdescs => {
- FIELDS => [
- comment_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- who => {TYPE => 'INT3', NOTNULL => 1},
- bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
- work_time => {TYPE => 'decimal(5,2)', NOTNULL => 1,
- DEFAULT => '0'},
- thetext => {TYPE => 'LONGTEXT', NOTNULL => 1},
- isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- already_wrapped => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- type => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- extra_data => {TYPE => 'varchar(255)'}
- ],
- INDEXES => [
- longdescs_bug_id_idx => ['bug_id'],
- longdescs_who_idx => [qw(who bug_id)],
- longdescs_bug_when_idx => ['bug_when'],
- ],
- },
- dependencies => {
- FIELDS => [
- blocked => {TYPE => 'INT3', NOTNULL => 1},
- dependson => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- dependencies_blocked_idx => ['blocked'],
- dependencies_dependson_idx => ['dependson'],
- ],
- },
- votes => {
- FIELDS => [
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- vote_count => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- votes_who_idx => ['who'],
- votes_bug_id_idx => ['bug_id'],
- ],
- },
- attachments => {
- FIELDS => [
- attach_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
- modification_time => {TYPE => 'DATETIME', NOTNULL => 1},
- description => {TYPE => 'TINYTEXT', NOTNULL => 1},
- mimetype => {TYPE => 'TINYTEXT', NOTNULL => 1},
- ispatch => {TYPE => 'BOOLEAN'},
- filename => {TYPE => 'varchar(100)', NOTNULL => 1},
- submitter_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- isurl => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- attachments_bug_id_idx => ['bug_id'],
- attachments_creation_ts_idx => ['creation_ts'],
- attachments_modification_time_idx => ['modification_time'],
- attachments_submitter_id_idx => ['submitter_id', 'bug_id'],
- ],
- },
- attach_data => {
- FIELDS => [
- id => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1},
- thedata => {TYPE => 'LONGBLOB', NOTNULL => 1},
- ],
- },
- duplicates => {
- FIELDS => [
- dupe_of => {TYPE => 'INT3', NOTNULL => 1},
- dupe => {TYPE => 'INT3', NOTNULL => 1,
- PRIMARYKEY => 1},
- ],
- },
- # Keywords
- # --------
- keyworddefs => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- ],
- INDEXES => [
- keyworddefs_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
- keywords => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- keywordid => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)],
- TYPE => 'UNIQUE'},
- keywords_keywordid_idx => ['keywordid'],
- ],
- },
- # Flags
- # -----
- # "flags" stores one record for each flag on each bug/attachment.
- flags => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- type_id => {TYPE => 'INT2', NOTNULL => 1},
- status => {TYPE => 'char(1)', NOTNULL => 1},
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- attach_id => {TYPE => 'INT3'},
- creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
- modification_date => {TYPE => 'DATETIME'},
- setter_id => {TYPE => 'INT3'},
- requestee_id => {TYPE => 'INT3'},
- ],
- INDEXES => [
- flags_bug_id_idx => [qw(bug_id attach_id)],
- flags_setter_id_idx => ['setter_id'],
- flags_requestee_id_idx => ['requestee_id'],
- flags_type_id_idx => ['type_id'],
- ],
- },
- # "flagtypes" defines the types of flags that can be set.
- flagtypes => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(50)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- cc_list => {TYPE => 'varchar(200)'},
- target_type => {TYPE => 'char(1)', NOTNULL => 1,
- DEFAULT => "'b'"},
- is_active => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- is_requestable => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_requesteeble => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- is_multiplicable => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- grant_group_id => {TYPE => 'INT3'},
- request_group_id => {TYPE => 'INT3'},
- ],
- },
- # "flaginclusions" and "flagexclusions" specify the products/components
- # a bug/attachment must belong to in order for flags of a given type
- # to be set for them.
- flaginclusions => {
- FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1},
- product_id => {TYPE => 'INT2'},
- component_id => {TYPE => 'INT2'},
- ],
- INDEXES => [
- flaginclusions_type_id_idx =>
- [qw(type_id product_id component_id)],
- ],
- },
- flagexclusions => {
- FIELDS => [
- type_id => {TYPE => 'INT2', NOTNULL => 1},
- product_id => {TYPE => 'INT2'},
- component_id => {TYPE => 'INT2'},
- ],
- INDEXES => [
- flagexclusions_type_id_idx =>
- [qw(type_id product_id component_id)],
- ],
- },
- # General Field Information
- # -------------------------
- fielddefs => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- type => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => FIELD_TYPE_UNKNOWN},
- custom => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- description => {TYPE => 'TINYTEXT', NOTNULL => 1},
- mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1},
- obsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- fielddefs_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- fielddefs_sortkey_idx => ['sortkey'],
- ],
- },
- # Per-product Field Values
- # ------------------------
- versions => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- versions_product_id_idx => {FIELDS => [qw(product_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
- milestones => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- value => {TYPE => 'varchar(20)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- ],
- INDEXES => [
- milestones_product_id_idx => {FIELDS => [qw(product_id value)],
- TYPE => 'UNIQUE'},
- ],
- },
- # Global Field Values
- # -------------------
- bug_status => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- bug_status_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- bug_status_sortkey_idx => ['sortkey', 'value'],
- ],
- },
- resolution => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- resolution_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- resolution_sortkey_idx => ['sortkey', 'value'],
- ],
- },
- bug_severity => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- bug_severity_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- bug_severity_sortkey_idx => ['sortkey', 'value'],
- ],
- },
- priority => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- priority_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- priority_sortkey_idx => ['sortkey', 'value'],
- ],
- },
- rep_platform => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- rep_platform_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- rep_platform_sortkey_idx => ['sortkey', 'value'],
- ],
- },
- op_sys => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- INDEXES => [
- op_sys_value_idx => {FIELDS => ['value'],
- TYPE => 'UNIQUE'},
- op_sys_sortkey_idx => ['sortkey', 'value'],
- ],
- },
- status_workflow => {
- FIELDS => [
- # On bug creation, there is no old value.
- old_status => {TYPE => 'INT2'},
- new_status => {TYPE => 'INT2', NOTNULL => 1},
- require_comment => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- status_workflow_idx => {FIELDS => ['old_status', 'new_status'],
- TYPE => 'UNIQUE'},
- ],
- },
- # USER INFO
- # ---------
- # General User Information
- # ------------------------
- profiles => {
- FIELDS => [
- userid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- login_name => {TYPE => 'varchar(255)', NOTNULL => 1},
- cryptpassword => {TYPE => 'varchar(128)'},
- realname => {TYPE => 'varchar(255)', NOTNULL => 1,
- DEFAULT => "''"},
- disabledtext => {TYPE => 'MEDIUMTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- disable_mail => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- extern_id => {TYPE => 'varchar(64)'},
- ],
- INDEXES => [
- profiles_login_name_idx => {FIELDS => ['login_name'],
- TYPE => 'UNIQUE'},
- ],
- },
- profiles_activity => {
- FIELDS => [
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- who => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- profiles_when => {TYPE => 'DATETIME', NOTNULL => 1},
- fieldid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'fielddefs',
- COLUMN => 'id'}},
- oldvalue => {TYPE => 'TINYTEXT'},
- newvalue => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- profiles_activity_userid_idx => ['userid'],
- profiles_activity_profiles_when_idx => ['profiles_when'],
- profiles_activity_fieldid_idx => ['fieldid'],
- ],
- },
- email_setting => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- relationship => {TYPE => 'INT1', NOTNULL => 1},
- event => {TYPE => 'INT1', NOTNULL => 1},
- ],
- INDEXES => [
- email_setting_user_id_idx =>
- {FIELDS => [qw(user_id relationship event)],
- TYPE => 'UNIQUE'},
- ],
- },
- watch => {
- FIELDS => [
- watcher => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- watched => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- watch_watcher_idx => {FIELDS => [qw(watcher watched)],
- TYPE => 'UNIQUE'},
- watch_watched_idx => ['watched'],
- ],
- },
- namedqueries => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- query => {TYPE => 'LONGTEXT', NOTNULL => 1},
- query_type => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
- ],
- INDEXES => [
- namedqueries_userid_idx => {FIELDS => [qw(userid name)],
- TYPE => 'UNIQUE'},
- ],
- },
- namedqueries_link_in_footer => {
- FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'namedqueries',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ],
- INDEXES => [
- namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)],
- TYPE => 'UNIQUE'},
- namedqueries_link_in_footer_userid_idx => ['user_id'],
- ],
- },
- component_cc => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- component_id => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- component_cc_user_id_idx => {FIELDS => [qw(component_id user_id)],
- TYPE => 'UNIQUE'},
- ],
- },
- # Authentication
- # --------------
- logincookies => {
- FIELDS => [
- cookie => {TYPE => 'varchar(16)', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- ipaddr => {TYPE => 'varchar(40)', NOTNULL => 1},
- lastused => {TYPE => 'DATETIME', NOTNULL => 1},
- ],
- INDEXES => [
- logincookies_lastused_idx => ['lastused'],
- ],
- },
- # "tokens" stores the tokens users receive when a password or email
- # change is requested. Tokens provide an extra measure of security
- # for these changes.
- tokens => {
- FIELDS => [
- userid => {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
- token => {TYPE => 'varchar(16)', NOTNULL => 1,
- PRIMARYKEY => 1},
- tokentype => {TYPE => 'varchar(8)', NOTNULL => 1} ,
- eventdata => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- tokens_userid_idx => ['userid'],
- ],
- },
- # GROUPS
- # ------
- groups => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(255)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- isbuggroup => {TYPE => 'BOOLEAN', NOTNULL => 1},
- userregexp => {TYPE => 'TINYTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- icon_url => {TYPE => 'TINYTEXT'},
- ],
- INDEXES => [
- groups_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},
- ],
- },
- group_control_map => {
- FIELDS => [
- group_id => {TYPE => 'INT3', NOTNULL => 1},
- product_id => {TYPE => 'INT3', NOTNULL => 1},
- entry => {TYPE => 'BOOLEAN', NOTNULL => 1},
- membercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
- othercontrol => {TYPE => 'BOOLEAN', NOTNULL => 1},
- canedit => {TYPE => 'BOOLEAN', NOTNULL => 1},
- editcomponents => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- editbugs => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- canconfirm => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- group_control_map_product_id_idx =>
- {FIELDS => [qw(product_id group_id)], TYPE => 'UNIQUE'},
- group_control_map_group_id_idx => ['group_id'],
- ],
- },
- # "user_group_map" determines the groups that a user belongs to
- # directly or due to regexp and which groups can be blessed by a user.
- #
- # grant_type:
- # if GRANT_DIRECT - record was explicitly granted
- # if GRANT_DERIVED - record was derived from expanding a group hierarchy
- # if GRANT_REGEXP - record was created by evaluating a regexp
- user_group_map => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
- isbless => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- grant_type => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => GRANT_DIRECT},
- ],
- INDEXES => [
- user_group_map_user_id_idx =>
- {FIELDS => [qw(user_id group_id grant_type isbless)],
- TYPE => 'UNIQUE'},
- ],
- },
- # This table determines which groups are made a member of another
- # group, given the ability to bless another group, or given
- # visibility to another groups existence and membership
- # grant_type:
- # if GROUP_MEMBERSHIP - member groups are made members of grantor
- # if GROUP_BLESS - member groups may grant membership in grantor
- # if GROUP_VISIBLE - member groups may see grantor group
- group_group_map => {
- FIELDS => [
- member_id => {TYPE => 'INT3', NOTNULL => 1},
- grantor_id => {TYPE => 'INT3', NOTNULL => 1},
- grant_type => {TYPE => 'INT1', NOTNULL => 1,
- DEFAULT => GROUP_MEMBERSHIP},
- ],
- INDEXES => [
- group_group_map_member_id_idx =>
- {FIELDS => [qw(member_id grantor_id grant_type)],
- TYPE => 'UNIQUE'},
- ],
- },
- # This table determines which groups a user must be a member of
- # in order to see a bug.
- bug_group_map => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- bug_group_map_bug_id_idx =>
- {FIELDS => [qw(bug_id group_id)], TYPE => 'UNIQUE'},
- bug_group_map_group_id_idx => ['group_id'],
- ],
- },
- # This table determines which groups a user must be a member of
- # in order to see a named query somebody else shares.
- namedquery_group_map => {
- FIELDS => [
- namedquery_id => {TYPE => 'INT3', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- namedquery_group_map_namedquery_id_idx =>
- {FIELDS => [qw(namedquery_id)], TYPE => 'UNIQUE'},
- namedquery_group_map_group_id_idx => ['group_id'],
- ],
- },
- category_group_map => {
- FIELDS => [
- category_id => {TYPE => 'INT2', NOTNULL => 1},
- group_id => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- category_group_map_category_id_idx =>
- {FIELDS => [qw(category_id group_id)], TYPE => 'UNIQUE'},
- ],
- },
- # PRODUCTS
- # --------
- classifications => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- description => {TYPE => 'MEDIUMTEXT'},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
- ],
- INDEXES => [
- classifications_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
- products => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- classification_id => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '1'},
- description => {TYPE => 'MEDIUMTEXT'},
- milestoneurl => {TYPE => 'TINYTEXT', NOTNULL => 1,
- DEFAULT => "''"},
- disallownew => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 0},
- votesperuser => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- maxvotesperbug => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '10000'},
- votestoconfirm => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => 0},
- defaultmilestone => {TYPE => 'varchar(20)',
- NOTNULL => 1, DEFAULT => "'---'"},
- ],
- INDEXES => [
- products_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
- components => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- product_id => {TYPE => 'INT2', NOTNULL => 1},
- initialowner => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid'}},
- initialqacontact => {TYPE => 'INT3',
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'SET NULL'}},
- description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- ],
- INDEXES => [
- components_product_id_idx => {FIELDS => [qw(product_id name)],
- TYPE => 'UNIQUE'},
- components_name_idx => ['name'],
- ],
- },
- # CHARTS
- # ------
- series => {
- FIELDS => [
- series_id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- creator => {TYPE => 'INT3'},
- category => {TYPE => 'INT2', NOTNULL => 1},
- subcategory => {TYPE => 'INT2', NOTNULL => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- frequency => {TYPE => 'INT2', NOTNULL => 1},
- last_viewed => {TYPE => 'DATETIME'},
- query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- is_public => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- ],
- INDEXES => [
- series_creator_idx =>
- {FIELDS => [qw(creator category subcategory name)],
- TYPE => 'UNIQUE'},
- ],
- },
- series_data => {
- FIELDS => [
- series_id => {TYPE => 'INT3', NOTNULL => 1},
- series_date => {TYPE => 'DATETIME', NOTNULL => 1},
- series_value => {TYPE => 'INT3', NOTNULL => 1},
- ],
- INDEXES => [
- series_data_series_id_idx =>
- {FIELDS => [qw(series_id series_date)],
- TYPE => 'UNIQUE'},
- ],
- },
- series_categories => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- name => {TYPE => 'varchar(64)', NOTNULL => 1},
- ],
- INDEXES => [
- series_categories_name_idx => {FIELDS => ['name'],
- TYPE => 'UNIQUE'},
- ],
- },
- # WHINE SYSTEM
- # ------------
- whine_queries => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- eventid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'whine_events',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- query_name => {TYPE => 'varchar(64)', NOTNULL => 1,
- DEFAULT => "''"},
- sortkey => {TYPE => 'INT2', NOTNULL => 1,
- DEFAULT => '0'},
- onemailperbug => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'FALSE'},
- title => {TYPE => 'varchar(128)', NOTNULL => 1,
- DEFAULT => "''"},
- ],
- INDEXES => [
- whine_queries_eventid_idx => ['eventid'],
- ],
- },
- whine_schedules => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- eventid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'whine_events',
- COLUMN => 'id',
- DELETE => 'CASCADE'}},
- run_day => {TYPE => 'varchar(32)'},
- run_time => {TYPE => 'varchar(32)'},
- run_next => {TYPE => 'DATETIME'},
- mailto => {TYPE => 'INT3', NOTNULL => 1},
- mailto_type => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'},
- ],
- INDEXES => [
- whine_schedules_run_next_idx => ['run_next'],
- whine_schedules_eventid_idx => ['eventid'],
- ],
- },
- whine_events => {
- FIELDS => [
- id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
- NOTNULL => 1},
- owner_userid => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- subject => {TYPE => 'varchar(128)'},
- body => {TYPE => 'MEDIUMTEXT'},
- ],
- },
- # QUIPS
- # -----
- quips => {
- FIELDS => [
- quipid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- userid => {TYPE => 'INT3'},
- quip => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
- approved => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- },
- # SETTINGS
- # --------
- # setting - each global setting will have exactly one entry
- # in this table.
- # setting_value - stores the list of acceptable values for each
- # setting, and a sort index that controls the order
- # in which the values are displayed.
- # profile_setting - If a user has chosen to use a value other than the
- # global default for a given setting, it will be
- # stored in this table. Note: even if a setting is
- # later changed so is_enabled = false, the stored
- # value will remain in case it is ever enabled again.
- #
- setting => {
- FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1,
- PRIMARYKEY => 1},
- default_value => {TYPE => 'varchar(32)', NOTNULL => 1},
- is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- subclass => {TYPE => 'varchar(32)'},
- ],
- },
- setting_value => {
- FIELDS => [
- name => {TYPE => 'varchar(32)', NOTNULL => 1},
- value => {TYPE => 'varchar(32)', NOTNULL => 1},
- sortindex => {TYPE => 'INT2', NOTNULL => 1},
- ],
- INDEXES => [
- setting_value_nv_unique_idx => {FIELDS => [qw(name value)],
- TYPE => 'UNIQUE'},
- setting_value_ns_unique_idx => {FIELDS => [qw(name sortindex)],
- TYPE => 'UNIQUE'},
- ],
- },
- profile_setting => {
- FIELDS => [
- user_id => {TYPE => 'INT3', NOTNULL => 1,
- REFERENCES => {TABLE => 'profiles',
- COLUMN => 'userid',
- DELETE => 'CASCADE'}},
- setting_name => {TYPE => 'varchar(32)', NOTNULL => 1},
- setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
- ],
- INDEXES => [
- profile_setting_value_unique_idx => {FIELDS => [qw(user_id setting_name)],
- TYPE => 'UNIQUE'},
- ],
- },
- # SCHEMA STORAGE
- # --------------
- bz_schema => {
- FIELDS => [
- schema_data => {TYPE => 'LONGBLOB', NOTNULL => 1},
- version => {TYPE => 'decimal(3,2)', NOTNULL => 1},
- ],
- },
- };
- use constant FIELD_TABLE_SCHEMA => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- # Note that bz_add_field_table should prepend the table name
- # to these index names.
- INDEXES => [
- value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
- sortkey_idx => ['sortkey', 'value'],
- ],
- };
- use constant MULTI_SELECT_VALUE_TABLE => {
- FIELDS => [
- bug_id => {TYPE => 'INT3', NOTNULL => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- ],
- INDEXES => [
- bug_id_idx => {FIELDS => [qw( bug_id value)], TYPE => 'UNIQUE'},
- ],
- };
- #--------------------------------------------------------------------------
- =head1 METHODS
- Note: Methods which can be implemented generically for all DBs are
- implemented in this module. If needed, they can be overridden with
- DB-specific code in a subclass. Methods which are prefixed with C<_>
- are considered protected. Subclasses may override these methods, but
- other modules should not invoke these methods directly.
- =cut
- #--------------------------------------------------------------------------
- sub new {
- =over
- =item C<new>
- Description: Public constructor method used to instantiate objects of this
- class. However, it also can be used as a factory method to
- instantiate database-specific subclasses when an optional
- driver argument is supplied.
- Parameters: $driver (optional) - Used to specify the type of database.
- This routine C<die>s if no subclass is found for the specified
- driver.
- $schema (optional) - A reference to a hash. Callers external
- to this package should never use this parameter.
- Returns: new instance of the Schema class or a database-specific subclass
- =cut
- my $this = shift;
- my $class = ref($this) || $this;
- my $driver = shift;
- if ($driver) {
- (my $subclass = $driver) =~ s/^(\S)/\U$1/;
- $class .= '::' . $subclass;
- eval "require $class;";
- die "The $class class could not be found ($subclass " .
- "not supported?): $@" if ($@);
- }
- die "$class is an abstract base class. Instantiate a subclass instead."
- if ($class eq __PACKAGE__);
- my $self = {};
- bless $self, $class;
- $self = $self->_initialize(@_);
- return($self);
- } #eosub--new
- #--------------------------------------------------------------------------
- sub _initialize {
- =item C<_initialize>
- Description: Protected method that initializes an object after
- instantiation with the abstract schema. All subclasses should
- override this method. The typical subclass implementation
- should first call the C<_initialize> method of the superclass,
- then do any database-specific initialization (especially
- define the database-specific implementation of the all
- abstract data types), and then call the C<_adjust_schema>
- method.
- Parameters: $abstract_schema (optional) - A reference to a hash. If
- provided, this hash will be used as the internal
- representation of the abstract schema instead of our
- default abstract schema. This is intended for internal
- use only by deserialize_abstract.
- Returns: the instance of the Schema class
- =cut
- my $self = shift;
- my $abstract_schema = shift;
- if (!$abstract_schema) {
- # While ABSTRACT_SCHEMA cannot be modified, $abstract_schema can be.
- # So, we dclone it to prevent anything from mucking with the constant.
- $abstract_schema = dclone(ABSTRACT_SCHEMA);
- # Let extensions add tables, but make sure they can't modify existing
- # tables. If we don't lock/unlock keys, lock_value complains.
- lock_keys(%$abstract_schema);
- foreach my $table (keys %{ABSTRACT_SCHEMA()}) {
- lock_value(%$abstract_schema, $table)
- if exists $abstract_schema->{$table};
- }
- unlock_keys(%$abstract_schema);
- Bugzilla::Hook::process('db_schema-abstract_schema',
- { schema => $abstract_schema });
- unlock_hash(%$abstract_schema);
- }
- $self->{schema} = dclone($abstract_schema);
- $self->{abstract_schema} = $abstract_schema;
- return $self;
- } #eosub--_initialize
- #--------------------------------------------------------------------------
- sub _adjust_schema {
- =item C<_adjust_schema>
- Description: Protected method that alters the abstract schema at
- instantiation-time to be database-specific. It is a generic
- enough routine that it can be defined here in the base class.
- It takes the abstract schema and replaces the abstract data
- types with database-specific data types.
- Parameters: none
- Returns: the instance of the Schema class
- =cut
- my $self = shift;
- # The _initialize method has already set up the db_specific hash with
- # the information on how to implement the abstract data types for the
- # instantiated DBMS-specific subclass.
- my $db_specific = $self->{db_specific};
- # Loop over each table in the abstract database schema.
- foreach my $table (keys %{ $self->{schema} }) {
- my %fields = (@{ $self->{schema}{$table}{FIELDS} });
- # Loop over the field definitions in each table.
- foreach my $field_def (values %fields) {
- # If the field type is an abstract data type defined in the
- # $db_specific hash, replace it with the DBMS-specific data type
- # that implements it.
- if (exists($db_specific->{$field_def->{TYPE}})) {
- $field_def->{TYPE} = $db_specific->{$field_def->{TYPE}};
- }
- # Replace abstract default values (such as 'TRUE' and 'FALSE')
- # with their database-specific implementations.
- if (exists($field_def->{DEFAULT})
- && exists($db_specific->{$field_def->{DEFAULT}})) {
- $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}};
- }
- }
- }
- return $self;
- } #eosub--_adjust_schema
- #--------------------------------------------------------------------------
- sub get_type_ddl {
- =item C<get_type_ddl>
- =over
- =item B<Description>
- Public method to convert abstract (database-generic) field specifiers to
- database-specific data types suitable for use in a C<CREATE TABLE> or
- C<ALTER TABLE> SQL statment. If no database-specific field type has been
- defined for the given field type, then it will just return the same field type.
- =item B<Parameters>
- =over
- =item C<$def> - A reference to a hash of a field containing the following keys:
- C<TYPE> (required), C<NOTNULL> (optional), C<DEFAULT> (optional),
- C<PRIMARYKEY> (optional), C<REFERENCES> (optional)
- =back
- =item B<Returns>
- A DDL string suitable for describing a field in a C<CREATE TABLE> or
- C<ALTER TABLE> SQL statement
- =back
- =cut
- my $self = shift;
- my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ };
- my $type = $finfo->{TYPE};
- confess "A valid TYPE was not specified for this column (got "
- . Dumper($finfo) . ")" unless ($type);
- my $default = $finfo->{DEFAULT};
- # Replace any abstract default value (such as 'TRUE' or 'FALSE')
- # with its database-specific implementation.
- if ( defined $default && exists($self->{db_specific}->{$default}) ) {
- $default = $self->{db_specific}->{$default};
- }
- my $type_ddl = $self->convert_type($type);
- # DEFAULT attribute must appear before any column constraints
- # (e.g., NOT NULL), for Oracle
- $type_ddl .= " DEFAULT $default" if (defined($default));
- $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL});
- $type_ddl .= " PRIMARY KEY" if ($finfo->{PRIMARYKEY});
- return($type_ddl);
- } #eosub--get_type_ddl
- sub get_fk_ddl {
- =item C<_get_fk_ddl>
- =over
- =item B<Description>
- Protected method. Translates the C<REFERENCES> item of a column into SQL.
- =item B<Params>
- =over
- =item C<$table> - The name of the table the reference is from.
- =item C<$column> - The name of the column the reference is from
- =item C<$references> - The C<REFERENCES> hashref from a column.
- =back
- =item B<Returns>
- SQL for to define the foreign key, or an empty string if C<$references>
- is undefined.
- =back
- =cut
- my ($self, $table, $column, $references) = @_;
- return "" if !$references;
- my $update = $references->{UPDATE} || 'CASCADE';
- my $delete = $references->{DELETE} || 'RESTRICT';
- my $to_table = $references->{TABLE} || confess "No table in reference";
- my $to_column = $references->{COLUMN} || confess "No column in reference";
- my $fk_name = $self->_get_fk_name($table, $column, $references);
- return "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
- . " REFERENCES $to_table($to_column)\n"
- . " ON UPDATE $update ON DELETE $delete";
- }
- # Generates a name for a Foreign Key. It's separate from get_fk_ddl
- # so that certain databases can override it (for shorter identifiers or
- # other reasons).
- sub _get_fk_name {
- my ($self, $table, $column, $references) = @_;
- my $to_table = $references->{TABLE};
- my $to_column = $references->{COLUMN};
- my $name = "fk_${table}_${column}_${to_table}_${to_column}";
- if (length($name) > $self->MAX_IDENTIFIER_LEN) {
- $name = 'fk_' . $self->_hash_identifier($name);
- }
- return $name;
- }
- sub _hash_identifier {
- my ($invocant, $value) = @_;
- # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something
- # longer in the future.
- return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7);
- }
- sub get_add_fk_sql {
- my ($self, $table, $column, $def) = @_;
- my $fk_string = $self->get_fk_ddl($table, $column, $def);
- return ("ALTER TABLE $table ADD $fk_string");
- }
- sub get_drop_fk_sql {
- my ($self, $table, $column, $references) = @_;
- my $fk_name = $self->_get_fk_name($table, $column, $references);
- return ("ALTER TABLE $table DROP CONSTRAINT $fk_name");
- }
- sub convert_type {
- =item C<convert_type>
- Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
- =cut
- my ($self, $type) = @_;
- return $self->{db_specific}->{$type} || $type;
- }
- sub get_column {
- =item C<get_column($table, $column)>
- Description: Public method to get the abstract definition of a column.
- Parameters: $table - the table name
- $column - a column in the table
- Returns: a hashref containing information about the column, including its
- type (C<TYPE>), whether or not it can be null (C<NOTNULL>),
- its default value if it has one (C<DEFAULT), etc.
- Returns undef if the table or column does not exist.
- =cut
- my($self, $table, $column) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- if (exists $self->{schema}->{$table}) {
- my %fields = (@{ $self->{schema}{$table}{FIELDS} });
- return $fields{$column};
- }
- return undef;
- } #eosub--get_column
- sub get_table_list {
- =item C<get_table_list>
- Description: Public method for discovering what tables should exist in the
- Bugzilla database.
- Parameters: none
- Returns: An array of table names, in alphabetical order.
- =cut
- my $self = shift;
- return sort keys %{$self->{schema}};
- }
- sub get_table_columns {
- =item C<get_table_columns>
- Description: Public method for discovering what columns are in a given
- table in the Bugzilla database.
- Parameters: $table - the table name
- Returns: array of column names
- =cut
- my($self, $table) = @_;
- my @ddl = ();
- my $thash = $self->{schema}{$table};
- die "Table $table does not exist in the database schema."
- unless (ref($thash));
- my @columns = ();
- my @fields = @{ $thash->{FIELDS} };
- while (@fields) {
- push(@columns, shift(@fields));
- shift(@fields);
- }
- return @columns;
- } #eosub--get_table_columns
- sub get_table_indexes_abstract {
- my ($self, $table) = @_;
- my $table_def = $self->get_table_abstract($table);
- my %indexes = @{$table_def->{INDEXES} || []};
- return \%indexes;
- }
- sub get_create_database_sql {
- my ($self, $name) = @_;
- return ("CREATE DATABASE $name");
- }
- sub get_table_ddl {
- =item C<get_table_ddl>
- Description: Public method to generate the SQL statements needed to create
- the a given table and its indexes in the Bugzilla database.
- Subclasses may override or extend this method, if needed, but
- subclasses probably should override C<_get_create_table_ddl>
- or C<_get_create_index_ddl> instead.
- Parameters: $table - the table name
- Returns: an array of strings containing SQL statements
- =cut
- my($self, $table) = @_;
- my @ddl = ();
- die "Table $table does not exist in the database schema."
- unless (ref($self->{schema}{$table}));
- my $create_table = $self->_get_create_table_ddl($table);
- push(@ddl, $create_table) if $create_table;
- my @indexes = @{ $self->{schema}{$table}{INDEXES} || [] };
- while (@indexes) {
- my $index_name = shift(@indexes);
- my $index_info = shift(@indexes);
- my $index_sql = $self->get_add_index_ddl($table, $index_name,
- $index_info);
- push(@ddl, $index_sql) if $index_sql;
- }
- push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} })
- if (ref($self->{schema}{$table}{DB_EXTRAS}));
- return @ddl;
- } #eosub--get_table_ddl
- #--------------------------------------------------------------------------
- sub _get_create_table_ddl {
- =item C<_get_create_table_ddl>
- Description: Protected method to generate the "create table" SQL statement
- for a given table.
- Parameters: $table - the table name
- Returns: a string containing the DDL statement for the specified table
- =cut
- my($self, $table) = @_;
- my $thash = $self->{schema}{$table};
- die "Table $table does not exist in the database schema."
- unless (ref($thash));
- my $create_table = "CREATE TABLE $table \(\n";
- my @fields = @{ $thash->{FIELDS} };
- while (@fields) {
- my $field = shift(@fields);
- my $finfo = shift(@fields);
- $create_table .= "\t$field\t" . $self->get_type_ddl($finfo);
- $create_table .= "," if (@fields);
- $create_table .= "\n";
- }
- $create_table .= "\)";
- return($create_table)
- } #eosub--_get_create_table_ddl
- #--------------------------------------------------------------------------
- sub _get_create_index_ddl {
- =item C<_get_create_index_ddl>
- Description: Protected method to generate a "create index" SQL statement
- for a given table and index.
- Parameters: $table_name - the name of the table
- $index_name - the name of the index
- $index_fields - a reference to an array of field names
- $index_type (optional) - specify type of index (e.g., UNIQUE)
- Returns: a string containing the DDL statement
- =cut
- my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
- my $sql = "CREATE ";
- $sql .= "$index_type " if ($index_type && $index_type eq 'UNIQUE');
- $sql .= "INDEX $index_name ON $table_name \(" .
- join(", ", @$index_fields) . "\)";
- return($sql);
- } #eosub--_get_create_index_ddl
- #--------------------------------------------------------------------------
- sub get_add_column_ddl {
- =item C<get_add_column_ddl($table, $column, \%definition, $init_value)>
- Description: Generate SQL to add a column to a table.
- Params: $table - The table containing the column.
- $column - The name of the column being added.
- \%definition - The new definition for the column,
- in standard C<ABSTRACT_SCHEMA> format.
- $init_value - (optional) An initial value to set
- the column to. Should already be SQL-quoted
- if necessary.
- Returns: An array of SQL statements.
- =cut
- my ($self, $table, $column, $definition, $init_value) = @_;
- my @statements;
- push(@statements, "ALTER TABLE $table ". $self->ADD_COLUMN ." $column " .
- $self->get_type_ddl($definition));
- # XXX - Note that although this works for MySQL, most databases will fail
- # before this point, if we haven't set a default.
- (push(@statements, "UPDATE $table SET $column = $init_value"))
- if defined $init_value;
- if (defined $definition->{REFERENCES}) {
- push(@statements, $self->get_add_fk_sql($table, $column,
- $definition->{REFERENCES}));
- }
- return (@statements);
- }
- sub get_add_index_ddl {
- =item C<get_add_index_ddl>
- Description: Gets SQL for creating an index.
- NOTE: Subclasses should not override this function. Instead,
- if they need to specify a custom CREATE INDEX statement,
- they should override C<_get_create_index_ddl>
- Params: $table - The name of the table the index will be on.
- $name - The name of the new index.
- $definition - An index definition. Either a hashref
- with FIELDS and TYPE or an arrayref
- containing a list of columns.
- Returns: An array of SQL statements that will create the
- requested index.
- =cut
- my ($self, $table, $name, $definition) = @_;
- my ($index_fields, $index_type);
- # Index defs can be arrays or hashes
- if (ref($definition) eq 'HASH') {
- $index_fields = $definition->{FIELDS};
- $index_type = $definition->{TYPE};
- } else {
- $index_fields = $definition;
- $index_type = '';
- }
-
- return $self->_get_create_index_ddl($table, $name, $index_fields,
- $index_type);
- }
- sub get_alter_column_ddl {
- =item C<get_alter_column_ddl($table, $column, \%definition)>
- Description: Generate SQL to alter a column in a table.
- The column that you are altering must exist,
- and the table that it lives in must exist.
- Params: $table - The table containing the column.
- $column - The name of the column being changed.
- \%definition - The new definition for the column,
- in standard C<ABSTRACT_SCHEMA> format.
- $set_nulls_to - A value to set NULL values to, if
- your new definition is NOT NULL and contains
- no DEFAULT, and when there is a possibility
- that the column could contain NULLs. $set_nulls_to
- should be already SQL-quoted if necessary.
- Returns: An array of SQL statements.
- =cut
- my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
- my @statements;
- my $old_def = $self->get_column_abstract($table, $column);
- my $specific = $self->{db_specific};
- # If the types have changed, we have to deal with that.
- if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
- push(@statements, $self->_get_alter_type_sql($table, $column,
- $new_def, $old_def));
- }
- my $default = $new_def->{DEFAULT};
- my $default_old = $old_def->{DEFAULT};
- # This first condition prevents "uninitialized value" errors.
- if (!defined $default && !defined $default_old) {
- # Do Nothing
- }
- # If we went from having a default to not having one
- elsif (!defined $default && defined $default_old) {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " DROP DEFAULT");
- }
- # If we went from no default to a default, or we changed the default.
- elsif ( (defined $default && !defined $default_old) ||
- ($default ne $default_old) )
- {
- $default = $specific->{$default} if exists $specific->{$default};
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
- . " SET DEFAULT $default");
- }
- # If we went from NULL to NOT NULL.
- if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
- my $setdefault;
- # Handle any fields that were NULL before, if we have a default,
- $setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
- # But if we have a set_nulls_to, that overrides the DEFAULT
- # (although nobody would usually specify both a default and
- # a set_nulls_to.)
- $setdefault = $set_nulls_to if defined $set_nulls_to;
- if (defined $setdefault) {
- push(@statements, "UPDATE $table SET $column = $setdefault"
- . " WHERE $column IS NULL");
- }
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " SET NOT NULL");
- }
- # If we went from NOT NULL to NULL
- elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
- push(@statements, "ALTER TABLE $table ALTER COLUMN $column"
- . " DROP NOT NULL");
- }
- # If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
- if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
- push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
- }
- # If we went from being a PK to not being a PK
- elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
- push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
- }
- return @statements;
- }
- sub get_drop_index_ddl {
- =item C<get_drop_index_ddl($table, $name)>
- Description: Generates SQL statements to drop an index.
- Params: $table - The table the index is on.
- $name - The name of the index being dropped.
- Returns: An array of SQL statements.
- =cut
- my ($self, $table, $name) = @_;
- # Although ANSI SQL-92 doesn't specify a method of dropping an index,
- # many DBs support this syntax.
- return ("DROP INDEX $name");
- }
- sub get_drop_column_ddl {
- =item C<get_drop_column_ddl($table, $column)>
- Description: Generate SQL to drop a column from a table.
- Params: $table - The table containing the column.
- $column - The name of the column being dropped.
- Returns: An array of SQL statements.
- =cut
- my ($self, $table, $column) = @_;
- return ("ALTER TABLE $table DROP COLUMN $column");
- }
- =item C<get_drop_table_ddl($table)>
- Description: Generate SQL to drop a table from the database.
- Params: $table - The name of the table to drop.
- Returns: An array of SQL statements.
- =cut
- sub get_drop_table_ddl {
- my ($self, $table) = @_;
- return ("DROP TABLE $table");
- }
- sub get_rename_column_ddl {
- =item C<get_rename_column_ddl($table, $old_name, $new_name)>
- Description: Generate SQL to change the name of a column in a table.
- NOTE: ANSI SQL contains no simple way to rename a column,
- so this function is ABSTRACT and must be implemented
- by subclasses.
- Params: $table - The table containing the column to be renamed.
- $old_name - The name of the column being renamed.
- $new_name - The name the column is changing to.
- Returns: An array of SQL statements.
- =cut
- die "ANSI SQL has no way to rename a column, and your database driver\n"
- . " has not implemented a method.";
- }
- sub get_rename_table_sql {
- =item C<get_rename_table_sql>
- =over
- =item B<Description>
- Gets SQL to rename a table in the database.
- =item B<Params>
- =over
- =item C<$old_name> - The current name of the table.
- =item C<$new_name> - The new name of the table.
- =back
- =item B<Returns>: An array of SQL statements to rename a table.
- =back
- =cut
- my ($self, $old_name, $new_name) = @_;
- return ("ALTER TABLE $old_name RENAME TO $new_name");
- }
- =item C<delete_table($name)>
- Description: Deletes a table from this Schema object.
- Dies if you try to delete a table that doesn't exist.
- Params: $name - The name of the table to delete.
- Returns: nothing
- =cut
- sub delete_table {
- my ($self, $name) = @_;
- die "Attempted to delete nonexistent table '$name'." unless
- $self->get_table_abstract($name);
- delete $self->{abstract_schema}->{$name};
- delete $self->{schema}->{$name};
- }
- sub get_column_abstract {
- =item C<get_column_abstract($table, $column)>
- Description: A column definition from the abstract internal schema.
- cross-database format.
- Params: $table - The name of the table
- $column - The name of the column that you want
- Returns: A hash reference. For the format, see the docs for
- C<ABSTRACT_SCHEMA>.
- Returns undef if the column or table does not exist.
- =cut
- my ($self, $table, $column) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- if ($self->get_table_abstract($table)) {
- my %fields = (@{ $self->{abstract_schema}{$table}{FIELDS} });
- return $fields{$column};
- }
- return undef;
- }
- =item C<get_indexes_on_column_abstract($table, $column)>
- Description: Gets a list of indexes that are on a given column.
- Params: $table - The table the column is on.
- $column - The name of the column.
- Returns: Indexes in the standard format of an INDEX
- entry on a table. That is, key-value pairs
- where the key is the index name and the value
- is the index definition.
- If there are no indexes on that column, we return
- undef.
- =cut
- sub get_indexes_on_column_abstract {
- my ($self, $table, $column) = @_;
- my %ret_hash;
- my $table_def = $self->get_table_abstract($table);
- if ($table_def && exists $table_def->{INDEXES}) {
- my %indexes = (@{ $table_def->{INDEXES} });
- foreach my $index_name (keys %indexes) {
- my $col_list;
- # Get the column list, depending on whether the index
- # is in hashref or arrayref format.
- if (ref($indexes{$index_name}) eq 'HASH') {
- $col_list = $indexes{$index_name}->{FIELDS};
- } else {
- $col_list = $indexes{$index_name};
- }
- if(grep($_ eq $column, @$col_list)) {
- $ret_hash{$index_name} = dclone($indexes{$index_name});
- }
- }
- }
- return %ret_hash;
- }
- sub get_index_abstract {
- =item C<get_index_abstract($table, $index)>
- Description: Returns an index definition from the internal abstract schema.
- Params: $table - The table the index is on.
- $index - The name of the index.
- Returns: A hash reference representing an index definition.
- See the C<ABSTRACT_SCHEMA> docs for details.
- Returns undef if the index does not exist.
- =cut
- my ($self, $table, $index) = @_;
- # Prevent a possible dereferencing of an undef hash, if the
- # table doesn't exist.
- my $index_table = $self->get_table_abstract($table);
- if ($index_table && exists $index_table->{INDEXES}) {
- my %indexes = (@{ $index_table->{INDEXES} });
- return $indexes{$index};
- }
- return undef;
- }
- =item C<get_table_abstract($table)>
- Description: Gets the abstract definition for a table in this Schema
- object.
- Params: $table - The name of the table you want a definition for.
- Returns: An abstract table definition, or undef if the table doesn't
- exist.
- =cut
- sub get_table_abstract {
- my ($self, $table) = @_;
- return $self->{abstract_schema}->{$table};
- }
- =item C<add_table($name, \%definition)>
- Description: Creates a new table in this Schema object.
- If you do not specify a definition, we will
- simply create an empty table.
- Params: $name - The name for the new table.
- \%definition (optional) - An abstract definition for
- the new table.
- Returns: nothing
- =cut
- sub add_table {
- my ($self, $name, $definition) = @_;
- (die "Table already exists: $name")
- if exists $self->{abstract_schema}->{$name};
- if ($definition) {
- $self->{abstract_schema}->{$name} = dclone($definition);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
- }
- else {
- $self->{abstract_schema}->{$name} = {FIELDS => []};
- $self->{schema}->{$name} = {FIELDS => []};
- }
- }
- sub rename_table {
- =item C<rename_table>
- Renames a table from C<$old_name> to C<$new_name> in this Schema object.
- =cut
- my ($self, $old_name, $new_name) = @_;
- my $table = $self->get_table_abstract($old_name);
- $self->delete_table($old_name);
- $self->add_table($new_name, $table);
- }
- sub delete_column {
- =item C<delete_column($table, $column)>
- Description: Deletes a column from this Schema object.
- Params: $table - Name of the table that the column is in.
- The table must exist, or we will fail.
- $column - Name of the column to delete.
- Returns: nothing
- =cut
- my ($self, $table, $column) = @_;
- my $abstract_fields = $self->{abstract_schema}{$table}{FIELDS};
- my $name_position = lsearch($abstract_fields, $column);
- die "Attempted to delete nonexistent column ${table}.${column}"
- if $name_position == -1;
- # Delete the key/value pair from the array.
- splice(@$abstract_fields, $name_position, 2);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
- }
- sub rename_column {
- =item C<rename_column($table, $old_name, $new_name)>
- Description: Renames a column on a table in the Schema object.
- The column that you are renaming must exist.
- Params: $table - The table the column is on.
- $old_name - The current name of the column.
- $new_name - The new name of hte column.
- Returns: nothing
- =cut
- my ($self, $table, $old_name, $new_name) = @_;
- my $def = $self->get_column_abstract($table, $old_name);
- die "Renaming a column that doesn't exist" if !$def;
- $self->delete_column($table, $old_name);
- $self->set_column($table, $new_name, $def);
- }
- sub set_column {
- =item C<set_column($table, $column, \%new_def)>
- Description: Changes the definition of a column in this Schema object.
- If the column doesn't exist, it will be added.
- The table that you specify must already exist in the Schema.
- NOTE: This does not affect the database on the disk.
- Use the C<Bugzilla::DB> "Schema Modification Methods"
- if you want to do that.
- Params: $table - The name of the table that the column is on.
- $column - The name of the column.
- \%new_def - The new definition for the column, in
- C<ABSTRACT_SCHEMA> format.
- Returns: nothing
- =cut
- my ($self, $table, $column, $new_def) = @_;
- my $fields = $self->{abstract_schema}{$table}{FIELDS};
- $self->_set_object($table, $column, $new_def, $fields);
- }
- sub set_index {
- =item C<set_index($table, $name, $definition)>
- Description: Changes the definition of an index in this Schema object.
- If the index doesn't exist, it will be added.
- The table that you specify must already exist in the Schema.
- NOTE: This does not affect the database on the disk.
- Use the C<Bugzilla::DB> "Schema Modification Methods"
- if you want to do that.
- Params: $table - The table the index is on.
- $name - The name of the index.
- $definition - A hashref or an arrayref. An index
- definition in C<ABSTRACT_SCHEMA> format.
- Returns: nothing
- =cut
- my ($self, $table, $name, $definition) = @_;
- if ( exists $self->{abstract_schema}{$table}
- && !exists $self->{abstract_schema}{$table}{INDEXES} ) {
- $self->{abstract_schema}{$table}{INDEXES} = [];
- }
- my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- $self->_set_object($table, $name, $definition, $indexes);
- }
- # A private helper for set_index and set_column.
- # This does the actual "work" of those two functions.
- # $array_to_change is an arrayref.
- sub _set_object {
- my ($self, $table, $name, $definition, $array_to_change) = @_;
- my $obj_position = lsearch($array_to_change, $name) + 1;
- # If the object doesn't exist, then add it.
- if (!$obj_position) {
- push(@$array_to_change, $name);
- push(@$array_to_change, $definition);
- }
- # We're modifying an existing object in the Schema.
- else {
- splice(@$array_to_change, $obj_position, 1, $definition);
- }
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
- }
- =item C<delete_index($table, $name)>
- Description: Removes an index definition from this Schema object.
- If the index doesn't exist, we will fail.
- The table that you specify must exist in the Schema.
- NOTE: This does not affect the database on the disk.
- Use the C<Bugzilla::DB> "Schema Modification Methods"
- if you want to do that.
- Params: $table - The table the index is on.
- $name - The name of the index that we're removing.
- Returns: nothing
- =cut
- sub delete_index {
- my ($self, $table, $name) = @_;
- my $indexes = $self->{abstract_schema}{$table}{INDEXES};
- my $name_position = lsearch($indexes, $name);
- die "Attempted to delete nonexistent index $name on the $table table"
- if $name_position == -1;
- # Delete the key/value pair from the array.
- splice(@$indexes, $name_position, 2);
- $self->{schema} = dclone($self->{abstract_schema});
- $self->_adjust_schema();
- }
- sub columns_equal {
- =item C<columns_equal($col_one, $col_two)>
- Description: Tells you if two columns have entirely identical definitions.
- The TYPE field's value will be compared case-insensitive.
- However, all other fields will be case-sensitive.
- Params: $col_one, $col_two - The columns to compare. Hash
- references, in C<ABSTRACT_SCHEMA> format.
- Returns: C<1> if the columns are identical, C<0> if they are not.
- =back
- =cut
- my $self = shift;
- my $col_one = dclone(shift);
- my $col_two = dclone(shift);
- $col_one->{TYPE} = uc($col_one->{TYPE});
- $col_two->{TYPE} = uc($col_two->{TYPE});
- # We don't care about foreign keys when comparing column definitions.
- delete $col_one->{REFERENCES};
- delete $col_two->{REFERENCES};
- my @col_one_array = %$col_one;
- my @col_two_array = %$col_two;
- my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
- # If there are no differences between the arrays, then they are equal.
- return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
- }
- =head1 SERIALIZATION/DESERIALIZATION
- =over 4
- =item C<serialize_abstract()>
- Description: Serializes the "abstract" schema into a format
- that deserialize_abstract() can read in. This is
- a method, called on a Schema instance.
- Parameters: none
- Returns: A scalar containing the serialized, abstract schema.
- Do not attempt to manipulate this data directly,
- as the format may change at any time in the future.
- The only thing you should do with the returned value
- is either store it somewhere (coupled with appropriate
- SCHEMA_VERSION) or deserialize it.
- =cut
- sub serialize_abstract {
- my ($self) = @_;
-
- # Make it ok to eval
- local $Data::Dumper::Purity = 1;
-
- # Avoid cross-refs
- local $Data::Dumper::Deepcopy = 1;
-
- # Always sort keys to allow textual compare
- local $Data::Dumper::Sortkeys = 1;
-
- return Dumper($self->{abstract_schema});
- }
- =item C<deserialize_abstract($serialized, $version)>
- Description: Used for when you've read a serialized Schema off the disk,
- and you want a Schema object that represents that data.
- Params: $serialized - scalar. The serialized data.
- $version - A number in the format X.YZ. The "version"
- of the Schema that did the serialization.
- See the docs for C<SCHEMA_VERSION> for more details.
- Returns: A Schema object. It will have the methods of (and work
- in the same fashion as) the current version of Schema.
- However, it will represent the serialized data instead of
- ABSTRACT_SCHEMA.
- =cut
- sub deserialize_abstract {
- my ($class, $serialized, $version) = @_;
- my $thawed_hash;
- if (int($version) < 2) {
- $thawed_hash = thaw($serialized);
- }
- else {
- my $cpt = new Safe;
- $cpt->reval($serialized) ||
- die "Unable to restore cached schema: " . $@;
- $thawed_hash = ${$cpt->varglob('VAR1')};
- }
- return $class->new(undef, $thawed_hash);
- }
- #####################################################################
- # Class Methods
- #####################################################################
- =back
- =head1 CLASS METHODS
- These methods are generally called on the class instead of on a specific
- object.
- =over
- =item C<get_empty_schema()>
- Description: Returns a Schema that has no tables. In effect, this
- Schema is totally "empty."
- Params: none
- Returns: A "empty" Schema object.
- =back
- =cut
- sub get_empty_schema {
- my ($class) = @_;
- return $class->deserialize_abstract(Dumper({}), SCHEMA_VERSION);
- }
- 1;
- __END__
- =head1 ABSTRACT DATA TYPES
- The size and range data provided here is only
- intended as a guide. See your database's Bugzilla
- module (in this directory) for the most up-to-date
- values for these data types. The following
- abstract data types are used:
- =over 4
- =item C<BOOLEAN>
- Logical value 0 or 1 where 1 is true, 0 is false.
- =item C<INT1>
- Integer values (-128 - 127 or 0 - 255 unsigned).
- =item C<INT2>
- Integer values (-32,768 - 32767 or 0 - 65,535 unsigned).
- =item C<INT3>
- Integer values (-8,388,608 - 8,388,607 or 0 - 16,777,215 unsigned)
- =item C<INT4>
- Integer values (-2,147,483,648 - 2,147,483,647 or 0 - 4,294,967,295
- unsigned)
- =item C<SMALLSERIAL>
- An auto-increment L</INT1>
- =item C<MEDIUMSERIAL>
- An auto-increment L</INT3>
- =item C<INTSERIAL>
- An auto-increment L</INT4>
- =item C<TINYTEXT>
- Variable length string of characters up to 255 (2^8 - 1) characters wide.
- =item C<MEDIUMTEXT>
- Variable length string of characters up to 4000 characters wide.
- May be longer on some databases.
- =item C<LONGTEXT>
- Variable length string of characters up to 16M (2^24 - 1) characters wide.
- =item C<LONGBLOB>
- Variable length string of binary data up to 4M (2^32 - 1) bytes wide
- =item C<DATETIME>
- DATETIME support varies from database to database, however, it's generally
- safe to say that DATETIME entries support all date/time combinations greater
- than 1900-01-01 00:00:00. Note that the format used is C<YYYY-MM-DD hh:mm:ss>
- to be safe, though it's possible that your database may not require
- leading zeros. For greatest compatibility, however, please make sure dates
- are formatted as above for queries to guarantee consistent results.
- =back
- Database-specific subclasses should define the implementation for these data
- types as a hash reference stored internally in the schema object as
- C<db_specific>. This is typically done in overridden L<_initialize> method.
- The following abstract boolean values should also be defined on a
- database-specific basis:
- =over 4
- =item C<TRUE>
- =item C<FALSE>
- =back
- =head1 SEE ALSO
- L<Bugzilla::DB>
- L<http://www.bugzilla.org/docs/developer.html#sql-schema>
- =cut
|