Schema.pm 88 KB

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