Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/schema/PRESUBMIT.py b/schema/PRESUBMIT.py
new file mode 100644
index 0000000..e07745c
--- /dev/null
+++ b/schema/PRESUBMIT.py
@@ -0,0 +1,35 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Presubmit script just for Monorail's SQL files."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+
+def AlterTableCheck(input_api, output_api): # pragma: no cover
+ this_dir = input_api.PresubmitLocalPath()
+ sql_files = set(x for x in input_api.os_listdir(this_dir)
+ if (x.endswith('.sql') and x != 'queries.sql'))
+ log_file = input_api.os_path.join(this_dir, 'alter-table-log.txt')
+ affected_files = set(f.LocalPath() for f in input_api.AffectedTextFiles())
+
+ if (any(f in affected_files for f in sql_files) ^
+ (log_file in affected_files)):
+ return [output_api.PresubmitPromptOrNotify(
+ 'It looks like you have modified the sql schema without updating\n'
+ 'the alter-table-log, or vice versa. Are you sure you want to do this?')
+ ]
+ return []
+
+
+def CheckChangeOnUpload(input_api, output_api): # pragma: no cover
+ output = AlterTableCheck(input_api, output_api)
+ return output
+
+
+def CheckChangeOnCommit(input_api, output_api): # pragma: no cover
+ output = AlterTableCheck(input_api, output_api)
+ return output
diff --git a/schema/alter-table-log.txt b/schema/alter-table-log.txt
new file mode 100644
index 0000000..26c21dd
--- /dev/null
+++ b/schema/alter-table-log.txt
@@ -0,0 +1,1744 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+This file contains a log of ALTER TABLE statements that need to be executed
+to bring a Monorail SQL database up to the current schema.
+
+================================================================
+2012-05-24: Added more Project fields.
+
+ALTER TABLE Project ADD COLUMN read_only_reason VARCHAR(80);
+ALTER TABLE Project ADD COLUMN issue_notify_address VARCHAR(80);
+ALTER TABLE Project ADD COLUMN attachment_bytes_used INT DEFAULT 0;
+ALTER TABLE Project ADD COLUMN attachment_quota INT DEFAULT 52428800;
+ALTER TABLE Project ADD COLUMN moved_to VARCHAR(250);
+ALTER TABLE Project ADD COLUMN process_inbound_email BOOLEAN DEFAULT FALSE;
+
+================================================================
+2012-06-01: Added inbound_message for issue comments
+
+ALTER TABLE Comment ADD COLUMN inbound_message TEXT;
+
+
+================================================================
+2012-06-05: Removed send_notifications_from_user because Monorail will
+not offer that feature any time soon.
+
+ALTER TABLE ProjectIssueConfig DROP COLUMN send_notifications_from_user;
+
+
+================================================================
+2012-06-05: Add initial subscription options.
+
+ALTER TABLE User2SavedQuery ADD COLUMN subscription_mode
+ ENUM ('noemail', 'immediate') DEFAULT 'noemail' NOT NULL;
+
+
+================================================================
+2012-07-02: Revised project states and added state_reason and delete_time
+
+ALTER TABLE Project MODIFY COLUMN state ENUM ('live', 'archived', 'deletable')
+NOT NULL;
+
+ALTER TABLE Project ADD COLUMN state_reason VARCHAR(80);
+ALTER TABLE Project ADD COLUMN delete_time INT;
+
+
+================================================================
+2012-07-05: Added action limits and dismissed cues
+
+CREATE TABLE ActionLimit (
+ user_id INT NOT NULL AUTO_INCREMENT,
+ action_kind ENUM (
+ 'project_creation', 'issue_comment', 'issue_attachment',
+ 'issue_bulk_edit'),
+ recent_count INT,
+ reset_timestamp INT,
+ lifetime_count INT,
+ lifetime_limit INT,
+
+ PRIMARY KEY (user_id, action_kind)
+) ENGINE=INNODB;
+
+
+CREATE TABLE DismissedCues (
+ user_id INT NOT NULL AUTO_INCREMENT,
+ cue VARCHAR(40), -- names of the cue cards that the user has dismissed.
+
+ INDEX (user_id)
+) ENGINE=INNODB;
+
+
+ALTER TABLE User ADD COLUMN ignore_action_limits BOOLEAN DEFAULT FALSE;
+
+================================================================
+2012-07-11: No longer using Counter table.
+
+DROP TABLE Counter;
+
+================================================================
+2012-09-06: Drop AttachmentContent, put blobkey in Attachment
+and drop some redundant columns.
+
+Note: This loses attachment data that might currently be in your
+instance. Good thing these schema refinements are getting done
+before launch.
+
+ALTER TABLE Attachment DROP COLUMN attachment_id;
+ALTER TABLE Attachment DROP COLUMN comment_created;
+ALTER TABLE Attachment ADD COLUMN blobkey VARCHAR(1024) NOT NULL;
+
+DROP TABLE AttachmentContent;
+
+ALTER TABLE IssueUpdate DROP COLUMN comment_created;
+
+
+================================================================
+2012-11-01: Add Components to IssueUpdate enum.
+
+alter table IssueUpdate modify field ENUM ('summary', 'status', 'owner',
+'cc', 'labels', 'blockedon', 'blocking', 'mergedinto', 'project',
+'components') NOT NULL;
+
+
+================================================================
+2012-12-10: Add template admins and field admins
+
+
+CREATE TABLE FieldDef2Admin (
+ field_id INT NOT NULL,
+ admin_id INT NOT NULL,
+
+ PRIMARY KEY (field_id, admin_id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (admin_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+CREATE TABLE Template2Admin (
+ template_id INT NOT NULL,
+ admin_id INT NOT NULL,
+
+ PRIMARY KEY (template_id, admin_id),
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (admin_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+================================================================
+2012-12-14: Add a table of custom field values
+
+ALTER TABLE FieldDef MODIFY field_type ENUM (
+ 'enum_type', 'int_type', 'str_type', 'user_type') NOT NULL;
+
+CREATE TABLE Issue2FieldValue (
+ iid INT NOT NULL,
+ field_id INT NOT NULL,
+
+ int_value INT,
+ str_value VARCHAR(1024),
+ user_id INT,
+
+ derived BOOLEAN DEFAULT FALSE,
+
+ INDEX (iid, field_id),
+ INDEX (field_id, int_value),
+ INDEX (field_id, str_value),
+ INDEX (field_id, user_id),
+
+ FOREIGN KEY (iid) REFERENCES Issue(id),
+ -- FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+================================================================
+2012-12-18: persistence for update objects on custom fields
+
+ALTER TABLE IssueUpdate MODIFY field ENUM (
+ 'summary', 'status', 'owner', 'cc', 'labels', 'blockedon', 'blocking', 'mergedinto',
+ 'project', 'components', 'custom' ) NOT NULL;
+
+ALTER TABLE IssueUpdate ADD custom_field_name VARCHAR(255);
+
+
+================================================================
+2012-12-27: Rename component owner to component admin
+
+DROP TABLE Component2Owner;
+
+CREATE TABLE Component2Admin (
+ component_id SMALLINT UNSIGNED NOT NULL,
+ admin_id INT NOT NULL,
+
+ PRIMARY KEY (component_id, admin_id),
+
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id),
+ FOREIGN KEY (admin_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+================================================================
+2013-01-20: add field applicability predicate
+
+ALTER TABLE FieldDef ADD applicable_type VARCHAR(80);
+ALTER TABLE FieldDef ADD applicable_predicate TEXT;
+
+================================================================
+2013-01-25: add field validation details
+
+ALTER TABLE FieldDef ADD max_value INT;
+ALTER TABLE FieldDef ADD min_value INT;
+ALTER TABLE FieldDef ADD regex VARCHAR(80);
+ALTER TABLE FieldDef ADD needs_member BOOLEAN;
+ALTER TABLE FieldDef ADD needs_perm VARCHAR(80);
+
+
+================================================================
+2013-02-11: add grant and notify to user-valued fields
+
+ALTER TABLE FieldDef ADD grants_perm VARCHAR(80);
+ALTER TABLE FieldDef ADD notify_on ENUM ('never', 'any_comment') DEFAULT 'never' NOT NULL;
+
+
+================================================================
+2013-03-17: Add Template2FieldValue
+
+CREATE TABLE Template2FieldValue (
+ template_id INT NOT NULL,
+ field_id INT NOT NULL,
+
+ int_value INT,
+ str_value VARCHAR(1024),
+ user_id INT,
+
+ INDEX (template_id, field_id),
+
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ -- FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+================================================================
+2013-05-08: eliminated same_org_only
+
+-- This needs to be done on all shards.
+UPDATE Project SET access = 'members_only' WHERE access = 'same_org_only';
+ALTER TABLE Project MODIFY COLUMN access ENUM ('anyone', 'members_only');
+
+================================================================
+2013-05-08: implemented recent activity timestamp
+
+-- This needs to be done on all shards.
+ALTER TABLE Project ADD recent_activity_timestamp INT;
+
+================================================================
+2013-07-01: use BIGINT for Invalidate timesteps
+
+ALTER TABLE Invalidate MODIFY COLUMN timestep BIGINT NOT NULL AUTO_INCREMENT;
+
+
+================================================================
+2013-07-23: renamed to avoid "participant"
+
+RENAME TABLE ParticipantDuty TO MemberDuty;
+RENAME TABLE ParticipantNotes TO MemberNotes;
+
+================================================================
+2013-08-22: renamed issue_id to local_id
+
+-- On primary and all shards
+ALTER TABLE Issue CHANGE issue_id local_id INT NOT NULL;
+
+-- On primary only
+ALTER TABLE IssueFormerLocations CHANGE issue_id local_id INT NOT NULL;
+
+================================================================
+2013-08-24: renamed iid to issue_id
+
+-- On primary and all shards
+
+ALTER TABLE IssueSummary DROP FOREIGN KEY IssueSummary_ibfk_1;
+ALTER TABLE IssueSummary CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE IssueSummary ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE Issue2Label DROP FOREIGN KEY Issue2Label_ibfk_1;
+ALTER TABLE Issue2Label CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Issue2Label ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE Issue2Component DROP FOREIGN KEY Issue2Component_ibfk_1;
+ALTER TABLE Issue2Component CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Issue2Component ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE Issue2Cc DROP FOREIGN KEY Issue2Cc_ibfk_1;
+ALTER TABLE Issue2Cc CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Issue2Cc ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE Issue2Notify DROP FOREIGN KEY Issue2Notify_ibfk_1;
+ALTER TABLE Issue2Notify CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Issue2Notify ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE IssueStar DROP FOREIGN KEY IssueStar_ibfk_1;
+ALTER TABLE IssueStar CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE IssueStar ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE IssueRelation DROP FOREIGN KEY IssueRelation_ibfk_1;
+ALTER TABLE IssueRelation CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE IssueRelation ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE IssueRelation CHANGE dst_iid dst_issue_id INT NOT NULL;
+
+ALTER TABLE Issue2FieldValue DROP FOREIGN KEY Issue2FieldValue_ibfk_1;
+ALTER TABLE Issue2FieldValue CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Issue2FieldValue ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+-- On primary only
+ALTER TABLE Comment DROP FOREIGN KEY Comment_ibfk_2;
+ALTER TABLE Comment CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Comment ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE Attachment DROP FOREIGN KEY Attachment_ibfk_1;
+ALTER TABLE Attachment CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE Attachment ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+ALTER TABLE IssueUpdate DROP FOREIGN KEY IssueUpdate_ibfk_1;
+ALTER TABLE IssueUpdate CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE IssueUpdate ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+-- I was missing a foreign key constraint here. Adding now.
+ALTER TABLE IssueFormerLocations CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE IssueFormerLocations ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+-- I was missing a foreign key constraint here. Adding now.
+ALTER TABLE ReindexQueue CHANGE iid issue_id INT NOT NULL;
+ALTER TABLE ReindexQueue ADD FOREIGN KEY (issue_id) REFERENCES Issue(id);
+
+
+================================================================
+2013-08-30: added per-project email sending flag
+
+-- On primary and all shards
+ALTER TABLE Project ADD COLUMN deliver_outbound_email BOOLEAN DEFAULT FALSE;
+
+
+================================================================
+2013-10-30: renamed prompts to templates
+
+ALTER TABLE ProjectIssueConfig
+CHANGE default_prompt_for_developers default_template_for_developers INT NOT NULL;
+
+ALTER TABLE ProjectIssueConfig
+CHANGE default_prompt_for_users default_template_for_users INT NOT NULL;
+
+ALTER TABLE Template
+CHANGE prompt_name name VARCHAR(255) NOT NULL,
+CHANGE prompt_text content TEXT,
+CHANGE prompt_summary summary TEXT,
+CHANGE prompt_summary_must_be_edited summary_must_be_edited BOOLEAN,
+CHANGE prompt_owner_id owner_id INT,
+CHANGE prompt_status status VARCHAR(255),
+CHANGE prompt_members_only members_only BOOLEAN;
+
+
+================================================================
+2013-11-18: add LocalIDCounter to primary DB only, and fill in values.
+
+CREATE TABLE LocalIDCounter (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ used_local_id INT NOT NULL,
+
+ PRIMARY KEY (project_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+-- Note: this ignores former issue locations, so it can only be run
+-- now, before the "move issue" feature is offered.
+REPLACE INTO LocalIDCounter
+SELECT project_id, MAX(local_id)
+FROM Issue
+GROUP BY project_id;
+
+================================================================
+2015-06-12: add issue_id to Invalidate's enum for kind.
+
+ALTER TABLE Invalidate CHANGE kind kind ENUM('user', 'project', 'issue', 'issue_id');
+
+================================================================
+2015-07-24: Rename blobkey to gcs_object_id because we are using
+Google Cloud storage now.
+
+ALTER TABLE Attachment CHANGE blobkey gcs_object_id VARCHAR(1024) NOT NULL;
+
+===============================================================
+2015-08-14: Use MurmurHash3 to deterministically generate user ids.
+
+-- First, drop foreign key constraints, then alter the keys, then
+-- add back the foreign key constraints.
+
+ALTER TABLE User2Project DROP FOREIGN KEY user2project_ibfk_2;
+ALTER TABLE ExtraPerm DROP FOREIGN KEY extraperm_ibfk_2;
+ALTER TABLE MemberNotes DROP FOREIGN KEY membernotes_ibfk_2;
+ALTER TABLE UserStar DROP FOREIGN KEY userstar_ibfk_1;
+ALTER TABLE UserStar DROP FOREIGN KEY userstar_ibfk_2;
+ALTER TABLE ProjectStar DROP FOREIGN KEY projectstar_ibfk_1;
+ALTER TABLE UserGroup DROP FOREIGN KEY usergroup_ibfk_1;
+ALTER TABLE UserGroup DROP FOREIGN KEY usergroup_ibfk_2;
+ALTER TABLE UserGroupSettings DROP FOREIGN KEY usergroupsettings_ibfk_1;
+ALTER TABLE QuickEditHistory DROP FOREIGN KEY quickedithistory_ibfk_2;
+ALTER TABLE QuickEditMostRecent DROP FOREIGN KEY quickeditmostrecent_ibfk_2;
+ALTER TABLE Issue DROP FOREIGN KEY issue_ibfk_2;
+ALTER TABLE Issue DROP FOREIGN KEY issue_ibfk_3;
+ALTER TABLE Issue DROP FOREIGN KEY issue_ibfk_4;
+ALTER TABLE Issue2Cc DROP FOREIGN KEY issue2cc_ibfk_2;
+ALTER TABLE IssueStar DROP FOREIGN KEY issuestar_ibfk_1; -- ?
+ALTER TABLE Issue2FieldValue DROP FOREIGN KEY issue2fieldvalue_ibfk_2;
+ALTER TABLE Comment DROP FOREIGN KEY comment_ibfk_3;
+ALTER TABLE Comment DROP FOREIGN KEY comment_ibfk_4;
+ALTER TABLE FieldDef2Admin DROP FOREIGN KEY fielddef2admin_ibfk_2;
+ALTER TABLE Template2Admin DROP FOREIGN KEY template2admin_ibfk_2;
+ALTER TABLE Template2FieldValue DROP FOREIGN KEY template2fieldvalue_ibfk_2;
+ALTER TABLE Component2Admin DROP FOREIGN KEY component2admin_ibfk_2;
+ALTER TABLE Component2Cc DROP FOREIGN KEY component2cc_ibfk_2;
+ALTER TABLE User2SavedQuery DROP FOREIGN KEY user2savedquery_ibfk_1;
+
+
+ALTER TABLE User MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE ActionLimit MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE DismissedCues MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE User2Project MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE ExtraPerm MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE MemberNotes MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE UserStar MODIFY starred_user_id INT UNSIGNED NOT NULL,
+ MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE ProjectStar MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE UserGroup MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE UserGroup MODIFY group_id INT UNSIGNED NOT NULL;
+ALTER TABLE UserGroupSettings MODIFY group_id INT UNSIGNED NOT NULL;
+ALTER TABLE QuickEditHistory MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE QuickEditMostRecent MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE Issue MODIFY reporter_id INT UNSIGNED NOT NULL,
+ MODIFY owner_id INT UNSIGNED,
+ MODIFY derived_owner_id INT UNSIGNED;
+ALTER TABLE Issue2Cc MODIFY cc_id INT UNSIGNED NOT NULL;
+ALTER TABLE IssueStar MODIFY user_id INT UNSIGNED NOT NULL;
+ALTER TABLE Issue2FieldValue MODIFY user_id INT UNSIGNED;
+ALTER TABLE Comment MODIFY commenter_id INT UNSIGNED NOT NULL;
+ALTER TABLE Comment MODIFY deleted_by INT UNSIGNED;
+ALTER TABLE IssueUpdate MODIFY added_user_id INT UNSIGNED,
+ MODIFY removed_user_id INT UNSIGNED;
+ALTER TABLE Template MODIFY owner_id INT UNSIGNED;
+ALTER TABLE FieldDef2Admin MODIFY admin_id INT UNSIGNED NOT NULL;
+ALTER TABLE Template2Admin MODIFY admin_id INT UNSIGNED NOT NULL;
+ALTER TABLE Template2FieldValue MODIFY user_id INT UNSIGNED;
+ALTER TABLE Component2Admin MODIFY admin_id INT UNSIGNED NOT NULL;
+ALTER TABLE Component2Cc MODIFY cc_id INT UNSIGNED NOT NULL;
+ALTER TABLE User2SavedQuery MODIFY user_id INT UNSIGNED NOT NULL;
+
+ALTER TABLE User2Project ADD CONSTRAINT user2project_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE ExtraPerm ADD CONSTRAINT extraperm_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE MemberNotes ADD CONSTRAINT membernotes_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE UserStar ADD CONSTRAINT userstar_ibfk_1 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE UserStar ADD CONSTRAINT userstar_ibfk_2 FOREIGN KEY (starred_user_id) REFERENCES User(user_id);
+ALTER TABLE ProjectStar ADD CONSTRAINT projectstar_ibfk_1 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE UserGroup ADD CONSTRAINT usergroup_ibfk_1 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE UserGroup ADD CONSTRAINT usergroup_ibfk_2 FOREIGN KEY (group_id) REFERENCES User(user_id);
+ALTER TABLE UserGroupSettings ADD CONSTRAINT usergroupsettings_ibfk_1 FOREIGN KEY (group_id) REFERENCES User(user_id);
+ALTER TABLE QuickEditHistory ADD CONSTRAINT quickedithistory_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE QuickEditMostRecent ADD CONSTRAINT quickeditmostrecent_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE Issue ADD CONSTRAINT issue_ibfk_2 FOREIGN KEY (reporter_id) REFERENCES User(user_id);
+ALTER TABLE Issue ADD CONSTRAINT issue_ibfk_3 FOREIGN KEY (owner_id) REFERENCES User(user_id);
+ALTER TABLE Issue ADD CONSTRAINT issue_ibfk_4 FOREIGN KEY (derived_owner_id) REFERENCES User(user_id);
+ALTER TABLE Issue2Cc ADD CONSTRAINT issue2cc_ibfk_2 FOREIGN KEY (cc_id) REFERENCES User(user_id);
+ALTER TABLE IssueStar ADD CONSTRAINT issuestar_ibfk_1 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE Issue2FieldValue ADD CONSTRAINT issue2fieldvalue_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE Comment ADD CONSTRAINT comment_ibfk_3 FOREIGN KEY (commenter_id) REFERENCES User(user_id);
+ALTER TABLE Comment ADD CONSTRAINT comment_ibfk_4 FOREIGN KEY (deleted_by) REFERENCES User(user_id);
+ALTER TABLE FieldDef2Admin ADD CONSTRAINT fielddef2admin_ibfk_2 FOREIGN KEY (admin_id) REFERENCES User(user_id);
+ALTER TABLE Template2Admin ADD CONSTRAINT template2admin_ibfk_2 FOREIGN KEY (admin_id) REFERENCES User(user_id);
+ALTER TABLE Template2FieldValue ADD CONSTRAINT template2fieldvalue_ibfk_2 FOREIGN KEY (user_id) REFERENCES User(user_id);
+ALTER TABLE Component2Admin ADD CONSTRAINT component2admin_ibfk_2 FOREIGN KEY (admin_id) REFERENCES User(user_id);
+ALTER TABLE Component2Cc ADD CONSTRAINT component2cc_ibfk_2 FOREIGN KEY (cc_id) REFERENCES User(user_id);
+ALTER TABLE User2SavedQuery ADD CONSTRAINT user2savedquery_ibfk_1 FOREIGN KEY (user_id) REFERENCES User(user_id);
+
+================================================================
+2015-08-20: Add obscure_email column to User.
+
+ALTER TABLE User ADD obscure_email BOOLEAN DEFAULT TRUE;
+
+================================================================
+2015-09-14: Add role column to UserGroup.
+
+ALTER TABLE UserGroup ADD COLUMN role ENUM ('owner', 'member') NOT NULL DEFAULT 'member';
+
+================================================================
+2015-09-14: Remove via_id column from UserGroup.
+
+ALTER TABLE UserGroup DROP COLUMN via_id;
+
+================================================================
+2015-09-14: Add foreign key constraints to Issue2Foo tables
+
+ALTER TABLE Issue ADD CONSTRAINT issue_ibfk_5 FOREIGN KEY (status_id) REFERENCES StatusDef(id);
+ALTER TABLE Issue2Component ADD CONSTRAINT issue2component_ibfk_2 FOREIGN KEY (component_id) REFERENCES ComponentDef(id);
+ALTER TABLE Issue2Label ADD CONSTRAINT issue2label_ibfk_2 FOREIGN KEY (label_id) REFERENCES LabelDef(id);
+ALTER TABLE Issue2FieldValue ADD CONSTRAINT issue2fieldvalue_ibfk_3 FOREIGN KEY (field_id) REFERENCES FieldDef(id);
+
+================================================================
+2015-09-16: Use Binary collation on Varchar unique keys
+
+ALTER TABLE StatusDef MODIFY status VARCHAR(80) BINARY NOT NULL;
+ALTER TABLE ComponentDef MODIFY path VARCHAR(255) BINARY NOT NULL;
+ALTER TABLE LabelDef MODIFY label VARCHAR(80) BINARY NOT NULL;
+ALTER TABLE FieldDef MODIFY field_name VARCHAR(80) BINARY NOT NULL;
+ALTER TABLE Template MODIFY name VARCHAR(255) BINARY NOT NULL;
+
+================================================================
+2015-09-16: Have components use the same ID schema as Labels/Statuses
+
+ALTER TABLE ComponentDef MODIFY id INT NOT NULL AUTO_INCREMENT;
+ALTER TABLE Component2Admin MODIFY component_id INT NOT NULL;
+ALTER TABLE Component2Cc MODIFY component_id INT NOT NULL;
+ALTER TABLE Issue2Component MODIFY component_id INT NOT NULL;
+
+================================================================
+2015-09-17: Introduce DanglingIssueRelation table
+
+ALTER TABLE IssueRelation ADD CONSTRAINT issuerelation_ibfk_2 FOREIGN KEY (dst_issue_id) REFERENCES Issue(id);
+
+CREATE TABLE DanglingIssueRelation (
+ issue_id INT NOT NULL,
+ dst_issue_project VARCHAR(80),
+ dst_issue_local_id INT,
+
+ -- This table uses 'blocking' so that it can guarantee the src issue
+ -- always exists, while the dst issue is always the dangling one.
+ kind ENUM ('blockedon', 'blocking', 'mergedinto') NOT NULL,
+
+ PRIMARY KEY (issue_id, dst_issue_project, dst_issue_local_id),
+ INDEX (issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+================================================================
+2015-09-18: Convert table char encodings to utf8.
+
+ALTER DATABASE monorail CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;
+ALTER TABLE Comment CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE ComponentDef CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE FieldDef CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE IssueSummary CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE LabelDef CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE MemberNotes CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE Project CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+ALTER TABLE StatusDef CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+
+================================================================
+2015-09-22: Make IssueRelation primary key more specific
+
+ALTER TABLE IssueRelation DROP PRIMARY KEY, ADD PRIMARY KEY (issue_id, dst_issue_id, kind);
+ALTER TABLE DanglingIssueRelation DROP PRIMARY KEY, ADD PRIMARY KEY (issue_id, dst_issue_project, dst_issue_local_id, kind);
+
+================================================================
+2015-09-29: Make cache_key unsigned so unsigned user ids can be invalidated.
+
+ALTER TABLE Invalidate MODIFY cache_key INT UNSIGNED NOT NULL;
+
+================================================================
+2015-09-29: Add external_group_type and external_group_name to UserGroupSettings
+
+ALTER TABLE UserGroupSettings ADD COLUMN external_group_type ENUM ('chrome_infra_auth', 'mdb');
+ALTER TABLE UserGroupSettings ADD COLUMN last_sync_time INT;
+
+================================================================
+2015-10-27: Eliminate Project.deliver_outbound_email because we have separate staging and prod instances.
+
+ALTER TABLE Project DROP COLUMN deliver_outbound_email;
+
+================================================================
+2015-10-27: Add SpamReport and is_spam fields to Issue and Comment
+
+ALTER TABLE Issue ADD COLUMN is_spam BOOL DEFAULT FALSE;
+ALTER TABLE Issue ADD INDEX (is_spam, project_id);
+
+ALTER TABLE Comment ADD COLUMN is_spam BOOL DEFAULT FALSE;
+ALTER TABLE Comment ADD INDEX (is_spam, project_id, created);
+
+-- Created whenever a user reports an issue or comment as spam.
+-- Note this is distinct from a SpamVerdict, which is issued by
+-- the system rather than a human user.
+CREATE TABLE SpamReport (
+ -- when this report was generated
+ created TIMESTAMP NOT NULL,
+ -- when the reported content was generated
+ content_created TIMESTAMP NOT NULL,
+ -- id of the reporting user
+ user_id INT UNSIGNED NOT NULL,
+ -- id of the reported user
+ reported_user_id INT UNSIGNED NOT NULL,
+ -- either this or issue_id must be set
+ comment_id INT,
+ -- either this or comment_id must be set
+ issue_id INT,
+
+ INDEX (issue_id),
+ INDEX (comment_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id)
+);
+
+================================================================
+2015-11-03: Add new external group type chromium_committers
+
+ALTER TABLE UserGroupSettings MODIFY COLUMN external_group_type ENUM ('chrome_infra_auth', 'mdb', 'chromium_committers');
+
+================================================================
+2015-11-4: Add SpamVerdict table.
+
+-- Any time a human or the system sets is_spam to true,
+-- or changes it from true to false, we want to have a
+-- record of who did it and why.
+CREATE TABLE SpamVerdict (
+ -- when this verdict was generated
+ created TIMESTAMP NOT NULL,
+
+ -- id of the reporting user, may be null if it was
+ -- an automatic classification.
+ user_id INT UNSIGNED,
+
+ -- either this or issue_id must be set
+ comment_id INT,
+
+ -- either this or comment_id must be set
+ issue_id INT,
+
+ INDEX (issue_id),
+ INDEX (comment_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id),
+
+ -- If the classifier issued the verdict, this should
+ -- be set.
+ classifier_confidence FLOAT,
+
+ -- This should reflect the new is_spam value that was applied
+ -- by this verdict, not the value it had prior.
+ is_spam BOOLEAN NOT NULL,
+
+ -- owner: a project owner marked it as spam
+ -- threshhold: number of SpamReports from non-members was exceeded.
+ -- classifier: the automatic classifier reports it as spam.
+ reason ENUM ("manual", "threshold", "classifier") NOT NULL
+);
+
+ALTER TABLE LocalIDCounter ADD used_spam_id int(11) NOT NULL;
+
+================================================================
+2015-11-13: Add Template2Component table.
+
+CREATE TABLE Template2Component (
+ template_id INT NOT NULL,
+ component_id INT NOT NULL,
+
+ PRIMARY KEY (template_id, component_id),
+
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id)
+) ENGINE=INNODB;
+
+================================================================
+2015-11-13: Add new external group type baggins
+
+ALTER TABLE UserGroupSettings MODIFY COLUMN external_group_type ENUM ('chrome_infra_auth', 'mdb', 'chromium_committers', 'baggins');
+
+================================================================
+2015-11-18: Add new action kind api_request in ActionLimit
+
+ALTER TABLE ActionLimit MODIFY COLUMN action_kind ENUM ('project_creation', 'issue_comment', 'issue_attachment', 'issue_bulk_edit', 'api_request');
+
+================================================================
+2015-11-24: Add shard column to Issue, add indexes, and UPDATE existing rows.
+
+ALTER TABLE Issue ADD COLUMN shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL;
+
+UPDATE Issue set shard = id % 10;
+
+ALTER TABLE Issue ADD INDEX (shard, status_id);
+ALTER TABLE Issue ADD INDEX (shard, project_id);
+
+================================================================
+2015-11-25: Remove external group type chromium_committers
+
+ALTER TABLE UserGroupSettings MODIFY COLUMN external_group_type ENUM ('chrome_infra_auth', 'mdb', 'baggins');
+
+================================================================
+2015-12-08: Modify handling of hidden well-known labels/statuses
+
+ALTER TABLE StatusDef ADD COLUMN hidden BOOLEAN DEFAULT FALSE;
+ALTER TABLE LabelDef ADD COLUMN hidden BOOLEAN DEFAULT FALSE;
+
+UPDATE StatusDef SET status=TRIM(LEADING '#' FROM status), hidden=TRUE WHERE status COLLATE UTF8_GENERAL_CI LIKE '#%';
+UPDATE LabelDef SET label=TRIM(LEADING '#' FROM label), hidden=TRUE WHERE label COLLATE UTF8_GENERAL_CI LIKE '#%';
+
+================================================================
+2015-12-11: Speed up moderation queue queries.
+
+ALTER TABLE SpamVerdict ADD INDEX(classifier_confidence);
+
+================================================================
+2015-12-14: Give components 'deprecated' col to match labels/statuses
+
+ALTER TABLE StatusDef CHANGE hidden deprecated BOOLEAN DEFAULT FALSE;
+ALTER TABLE LabelDef CHANGE hidden deprecated BOOLEAN DEFAULT FALSE;
+ALTER TABLE ComponentDef ADD COLUMN deprecated BOOLEAN DEFAULT FALSE;
+
+================================================================
+2015-12-14: Add table Group2Project
+
+CREATE TABLE Group2Project (
+ group_id INT UNSIGNED NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (group_id, project_id),
+
+ FOREIGN KEY (group_id) REFERENCES UserGroupSettings(group_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+================================================================
+2015-12-15: Increase maximum attachment quota bytes
+
+ALTER TABLE Project MODIFY attachment_bytes_used BIGINT DEFAULT 0;
+ALTER TABLE Project MODIFY attachment_quota BIGINT DEFAULT 0;
+
+================================================================
+2015-12-15: Simplify moderation queue queries.
+
+ALTER TABLE SpamVerdict ADD COLUMN overruled BOOL NOT NULL;
+ALTER TABLE SpamVerdict ADD COLUMN project_id INT NOT NULL;
+UPDATE SpamVerdict s JOIN Issue i ON i.id=s.issue_id SET s.project_id=i.project_id;
+
+================================================================
+2015-12-17: Add cols home_page and logo to table Project
+
+ALTER TABLE Project ADD COLUMN home_page VARCHAR(250);
+ALTER TABLE Project ADD COLUMN logo_gcs_id VARCHAR(250);
+ALTER TABLE Project ADD COLUMN logo_file_name VARCHAR(250);
+
+================================================================
+2015-12-28: Add component_required col to table Template;
+
+ALTER TABLE Template ADD component_required BOOLEAN DEFAULT FALSE;
+
+================================================================
+2016-01-05: Add issue_shard column to Issue2Label, Issue2Component,
+add indexes, and UPDATE existing rows.
+
+ALTER TABLE Issue2Component ADD COLUMN issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL;
+UPDATE Issue2Component set issue_shard = issue_id % 10;
+ALTER TABLE Issue2Component ADD INDEX (component_id, issue_shard);
+
+ALTER TABLE Issue2Label ADD COLUMN issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL;
+UPDATE Issue2Label set issue_shard = issue_id % 10;
+ALTER TABLE Issue2Label ADD INDEX (label_id, issue_shard);
+
+================================================================
+2016-01-06: Add period_soft_limit and period_hard_limit columns to ActionLimit
+
+ALTER TABLE ActionLimit ADD COLUMN period_soft_limit INT;
+ALTER TABLE ActionLimit ADD COLUMN period_hard_limit INT;
+
+================================================================
+2016-01-08: Add issue_shard column to Issue2FieldValue, Issue2Cc,
+add indexes, and UPDATE existing rows.
+
+ALTER TABLE Issue2FieldValue ADD COLUMN issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL;
+UPDATE Issue2FieldValue SET issue_shard = issue_id % 10;
+ALTER TABLE Issue2FieldValue ADD INDEX (field_id, issue_shard, int_value);
+ALTER TABLE Issue2FieldValue ADD INDEX (field_id, issue_shard, str_value(255));
+ALTER TABLE Issue2FieldValue ADD INDEX (field_id, issue_shard, user_id);
+
+ALTER TABLE Issue2Cc ADD COLUMN issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL;
+UPDATE Issue2Cc SET issue_shard = issue_id % 10;
+ALTER TABLE Issue2Cc ADD INDEX (cc_id, issue_shard);
+
+================================================================
+2015-12-17: Add documentation forwarding for /wiki urls
+
+ALTER TABLE Project ADD COLUMN docs_url VARCHAR(250);
+
+================================================================
+2015-12-17: Ensure SavedQueries never have null ids
+
+ALTER TABLE SavedQuery MODIFY id INT NOT NULL AUTO INCREMENT;
+
+================================================================
+2016-02-04: Add created, creator_id, modified, modifier_id for components
+
+ALTER TABLE ComponentDef ADD COLUMN created INT;
+ALTER TABLE ComponentDef ADD COLUMN creator_id INT UNSIGNED;
+ALTER TABLE ComponentDef ADD FOREIGN KEY (creator_id) REFERENCES User(user_id);
+ALTER TABLE ComponentDef ADD COLUMN modified INT;
+ALTER TABLE ComponentDef ADD COLUMN modifier_id INT UNSIGNED;
+ALTER TABLE ComponentDef ADD FOREIGN KEY (modifier_id) REFERENCES User(user_id);
+
+================================================================
+2016-02-19: Opt all privileged accounts into displaying full email.
+
+UPDATE User SET obscure_email = FALSE WHERE email LIKE "%@chromium.org";
+UPDATE User SET obscure_email = FALSE WHERE email LIKE "%@webrtc.org";
+UPDATE User SET obscure_email = FALSE WHERE email LIKE "%@google.com";
+
+================================================================
+2016-04-11: Increase email length limit to 255
+
+ALTER TABLE User MODIFY email VARCHAR(255);
+
+================================================================
+2016-04-14: Add forwarding for /source urls
+
+ALTER TABLE Project ADD COLUMN source_url VARCHAR(250);
+
+================================================================
+2016-04-27: Add prefs for compact email subject lines
+
+ALTER TABLE User ADD COLUMN email_compact_subject BOOLEAN DEFAULT FALSE;
+ALTER TABLE User ADD COLUMN email_view_widget BOOLEAN DEFAULT TRUE;
+
+================================================================
+2016-05-13: Add component labels
+
+CREATE TABLE Component2Label (
+ component_id INT NOT NULL,
+ label_id INT NOT NULL,
+
+ PRIMARY KEY (component_id, label_id),
+
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id),
+ FOREIGN KEY (label_id) REFERENCES LabelDef(id)
+) ENGINE=INNODB;
+
+================================================================
+2016-05-23: Add default search for members
+
+ALTER TABLE ProjectIssueConfig ADD COLUMN member_default_query TEXT;
+
+================================================================
+2016-06-17: Add is_description column to Comment
+
+Local:
+% pt-online-schema-change --alter "ADD COLUMN is_description BOOLEAN DEFAULT FALSE" D=monorail,t=Comment --host=localhost --user=root --alter-foreign-keys-method=rebuild_constraints --execute
+
+Staging/Production:
+% pt-online-schema-change --alter "ADD COLUMN is_description BOOLEAN DEFAULT FALSE" D=monorail,t=Comment,h=<primary IP address>,u=$USER,p=test --alter-forieign-keys-method=rebuild_constraints --recursion-method=hosts --execute
+
+================================================================
+2016-05-13: Add table AutocompleteExclusion
+
+CREATE TABLE AutocompleteExclusion (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (project_id, user_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+===============================================================
+2016-06-30: Update table character encodings to allow Emoji support.
+
+/* DO NOT RUN THESE STATEMENTS ON PROD OR STAGING. They are fine for localhost
+but be warned they will lock the db for some time if you have gigs of data in
+these tables */
+
+ALTER TABLE `monorail`.`Comment` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+ALTER TABLE `monorail`.`IssueUpdate` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+ALTER TABLE `monorail`.`IssueSummary` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+/* This is what I ran on production: */
+% pt-online-schema-change --alter "CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" D=monorail,t=Comment,h=<primary IP> --alter-foreign-keys-method=rebuild_constraints --no-drop-old-table --recursion-method=hosts --check-slave-lag=h=<one of the replicas' IP> --print --execute
+
+% pt-online-schema-change --alter "CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" D=monorail,t=IssueUpdate,h=<primary IP> --alter-foreign-keys-method=rebuild_constraints --no-drop-old-table --recursion-method=hosts --check-slave-lag=h=<one of the replicas' IP> --print --execute
+
+% pt-online-schema-change --alter "CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" D=monorail,t=IssueSummary,h=<primary IP> --alter-foreign-keys-method=rebuild_constraints --no-drop-old-table --recursion-method=hosts --check-slave-lag=h=<one of the replicas' IP> --print --execute
+
+/* And then these two which ran very quickly: */
+ALTER TABLE `monorail`.`Template` CHANGE `content` `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+ALTER TABLE `monorail`.`Template` CHANGE `summary` `summary` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+===============================================================
+2016-07-07: Add rank to IssueRelation
+
+ALTER TABLE IssueRelation ADD COLUMN rank BIGINT;
+
+==============================================================
+2016-07-13: Set default rank for blockedon relations
+
+UPDATE IssueRelation SET rank = 0 WHERE kind = 'blockedon';
+
+================================================================
+2016-08-01: Add timestamps for issue field changes
+
+DO NOT RUN THIS STATEMENT ON PROD OR STAGING. It is fine for localhost
+but be warned that it will lock the db for some time if you have gigs of data in
+these tables.
+ALTER TABLE Issue
+ ADD COLUMN owner_modified INT,
+ ADD COLUMN status_modified INT,
+ ADD COLUMN component_modified INT;
+
+Staging/Production:
+% pt-online-schema-change \
+ --alter "ADD COLUMN owner_modified INT, ADD COLUMN status_modified INT, ADD COLUMN component_modified INT" \
+ D=monorail,t=Issue,h=<primary IP address>,u=$USER,p=<your mysql password> \
+ --alter-foreign-keys-method=rebuild_constraints --recursion-method=hosts --execute
+
+==============================================================
+2016-08-05: Add tables Hotlist, Hotlist2Issue, Hotlist2User
+
+CREATE TABLE Hotlist (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(80) NOT NULL,
+
+ summary TEXT,
+ description TEXT,
+
+ is_private BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Hotlist2Issue (
+ hotlist_id INT UNSIGNED NOT NULL,
+ issue_id INT NOT NULL,
+
+ rank BIGINT NOT NULL,
+
+ PRIMARY KEY (hotlist_id, issue_id),
+ INDEX (hotlist_id),
+ INDEX (issue_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Hotlist2User (
+ hotlist_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ role_name ENUM ('owner', 'member', 'follower') NOT NULL,
+
+ PRIMARY KEY (hotlist_id, user_id),
+ INDEX (hotlist_id),
+ INDEX (user_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+==============================================================
+2016-08-10: Improve Hotlist schema
+
+ALTER TABLE Hotlist ADD COLUMN default_col_spec TEXT;
+
+ALTER TABLE Hotlist2User CHANGE role_name
+ role_name ENUM('owner', 'editor', 'follower');
+
+==============================================================
+2016-08-15: Add hotlist to Invalidate table
+
+ALTER TABLE Invalidate CHANGE kind
+ kind ENUM('user', 'project', 'issue', 'issue_id', 'hotlist');
+
+================================================================
+2016-09-21: Create the CommentContent table with emoji support.
+
+CREATE TABLE CommentContent (
+ id INT NOT NULL AUTO_INCREMENT,
+ -- TODO(jrobbins): drop comment_id after Comment.commentcontent_id is added.
+ comment_id INT NOT NULL, -- Note: no forign key reference.
+ content MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
+ inbound_message MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY (comment_id) -- TODO: drop this too.
+) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+To copy comment strings from Comment to CommentContent, use
+the SQL procedure in monorail/tools/copy-comment-to-commentcontent.sql.
+
+If you need to roll back, you can reverse the process by reading
+and carefully using the SQL procedure in
+monorail/tools/copy-new-commentcontent-back-to-comment.sql.
+
+Optionally, after you have all comment content strings in
+CommentContent, you can reduce the size of the Comment table by using
+the procedure in monorail/tools/null-comment-table-strings.sql.
+This can make it faster to make more changes to the Comment table.
+
+================================================================
+2016-09-29: Drop was_escaped after Comment table is made smaller
+
+ALTER TABLE Comment DROP COLUMN was_escaped;
+
+================================================================
+2016-10-03: Add date-type custom fields
+
+ALTER TABLE Issue2FieldValue ADD date_value INT;
+ALTER TABLE Issue2FieldValue ADD INDEX (field_id, issue_shard, date_value);
+ALTER TABLE Template2FieldValue ADD date_value INT;
+ALTER TABLE FieldDef CHANGE field_type field_type ENUM (
+ 'enum_type', 'int_type', 'str_type', 'user_type', 'date_type') NOT NULL;
+
+================================================================
+2016-10-13: Follow-up on splitting the Comment table
+
+ALTER TABLE Comment
+ DROP COLUMN content,
+ DROP COLUMN inbound_message,
+ ADD COLUMN commentcontent_id INT;
+
+ALTER TABLE Comment
+ ADD FOREIGN KEY (commentcontent_id) REFERENCES CommentContent(id);
+
+After making those schema changes, run the commands in
+tools/backfill-commentcontent-id.sql to fill in commentcontent_id
+for existing comments.
+
+================================================================
+2016-10-13: Add new User fields
+
+ALTER TABLE User
+ ADD COLUMN last_visit_timestamp INT,
+ ADD COLUMN email_bounce_timestamp INT,
+ ADD COLUMN vacation_message VARCHAR(80);
+
+================================================================
+2016-11-30: Drop unique key constraint on CommentContent.comment_id.
+This is a prerequiste for deleting the code that sets a value for
+that column. This resolves one TODO from 2016-09-21. Later the
+column itself can be dropped, which is the other TODO from 2016-09-21.
+
+ALTER TABLE CommentContent DROP INDEX comment_id;
+
+================================================================
+2016-12-20: Add a table to keep track of hotlists that users have
+starred.
+
+CREATE TABLE HotlistStar (
+ hotlist_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (hotlist_id, user_id),
+ INDEX (user_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+================================================================
+2017-12-04: Add two new columns to Hotlist2Issue.
+
+ALTER TABLE Hotlist2Issue
+ ADD COLUMN adder_id INT UNSIGNED,
+ ADD COLUMN added INT,
+ ADD FOREIGN KEY (adder_id) REFERENCES User(user_id);
+
+================================================================
+2017-01-30: Add one new column to SpamVerdict.
+
+ALTER TABLE SpamVerdict CHANGE reason
+ reason ENUM ("manual", "threshold", "classifier", "fail_open") NOT NULL;
+
+================================================================
+2017-02-1: Add two tables to keep track of hotlists and bugs
+that users have visited
+
+CREATE TABLE HotlistVisitHistory (
+ hotlist_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ viewed INT NOT NULL,
+
+ PRIMARY KEY (user_id, hotlist_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+CREATE TABLE IssueVisitHistory (
+ issue_id INT NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ viewed INT NOT NULL,
+
+ PRIMARY KEY (user_id, issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+================================================================
+2017-02-16: Add 'note' column to Hotlist2Issue table.
+
+ALTER TABLE Hotlist2Issue ADD COLUMN note TEXT;
+
+
+================================================================
+2017-02-23: Add 'is_niche' column to FieldDef table.
+
+ALTER TABLE FieldDef ADD COLUMN is_niche BOOLEAN;
+
+
+================================================================
+2017-03-05: Add 'ping_who' column to FieldDef table.
+
+ALTER TABLE FieldDef
+ ADD COLUMN date_action ENUM ('no_action', 'ping_owner_only', 'ping_participants');
+
+
+================================================================
+2017-05-02: Add index to make commentby: query term faster.
+
+ALTER TABLE Comment ADD INDEX (commenter_id, deleted_by, issue_id);
+
+
+================================================================
+2017-05-12: Add user preference to ping issue starrers.
+
+ALTER TABLE User ADD COLUMN notify_starred_ping BOOLEAN DEFAULT FALSE;
+
+================================================================
+2017-06-15: Add table to map @google.com to @chromium.org accounts.
+
+CREATE TABLE LinkedAccount (
+ parent_email VARCHAR(255) NOT NULL, -- lowercase
+ child_email VARCHAR(255) NOT NULL, -- lowercase
+
+ KEY (parent_email),
+ UNIQUE KEY (child_email)
+) ENGINE=INNODB;
+
+================================================================
+2017-11-14: Add field_type ENUM url_type to FieldDef.
+
+ALTER TABLE FieldDef MODIFY field_type ENUM (
+ 'enum_type', 'int_type', 'str_type', 'user_type', 'date_type', 'url_type') NOT NULL;
+
+ALTER TABLE Issue2FieldValue ADD COLUMN url_value VARCHAR(1024);
+ALTER TABLE Issue2FieldValue ADD INDEX (field_id, url_value);
+
+================================================================
+2017-11-22: Add url_value column to Template2FieldValue table.
+
+ALTER TABLE Template2FieldValue ADD COLUMN url_value VARCHAR(1024);
+
+================================================================
+2018-01-22: Add table to keep track of the latest timestamp that issues with
+their component data were collected and uploaded to GCS.
+
+CREATE TABLE ComponentIssueClosedIndex (
+ closed_index INT NOT NULL,
+ PRIMARY KEY (closed_index)
+) ENGINE=INNODB;
+
+================================================================
+2018-01-22: Add approval tables and approval_type to FieldDef.
+
+ALTER TABLE FieldDef MODIFY field_type ENUM (
+ 'enum_type', 'int_type', 'str_type', 'user_type', 'date_type', 'url_type', 'approval_type') NOT NULL;
+
+CREATE TABLE ApprovalStatusDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ field_id INT NOT NULL,
+ status VARCHAR(80) BINARY NOT NULL,
+ docstring TEXT,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY (field_id, status),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id)
+) ENGINE=INNODB;
+
+CREATE TABLE Issue2ApprovalValue (
+ issue_id INT NOT NULL,
+ issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ field_id INT NOT NULL,
+ status_id INT NOT NULL,
+ setter_id INT UNSIGNED,
+ set_on INT,
+
+ PRIMARY KEY (issue_id, field_id),
+ INDEX (field_id, issue_shard, status_id),
+ INDEX (field_id, issue_shard, setter_id),
+ INDEX (field_id, issue_shard, set_on),
+
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (status_id) REFERENCES ApprovalStatusDef(id),
+ FOREIGN KEY (setter_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+CREATE TABLE Approval2Approvers (
+ field_id INT NOT NULL,
+ approver_id INT UNSIGNED NOT NULL,
+ issue_id INT,
+ issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+
+ PRIMARY KEY (issue_id, field_id, approver_id),
+ INDEX (approver_id, field_id, issue_shard),
+
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (approver_id) REFERENCES User(user_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+================================================================
+2018-01-29: Add is_deleted column to ComponentDef table and remove
+uniqueness constraint for component names in a project.
+
+ALTER TABLE ComponentDef ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
+ALTER TABLE ComponentDef ADD INDEX project_id2 (project_id, path);
+ALTER TABLE ComponentDef DROP INDEX project_id;
+
+================================================================
+2018-01-30: Add IssueSnapshot table and join tables
+
+CREATE TABLE IssueSnapshot (
+ id INT NOT NULL AUTO_INCREMENT,
+ issue_id INT NOT NULL,
+ shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ local_id INT NOT NULL,
+ reporter_id INT UNSIGNED NOT NULL,
+ owner_id INT UNSIGNED,
+ status_id INT NOT NULL,
+ period_start INT NOT NULL,
+ period_end INT NOT NULL,
+ is_open BOOLEAN DEFAULT TRUE,
+
+ PRIMARY KEY (id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (reporter_id) REFERENCES User(user_id),
+ FOREIGN KEY (owner_id) REFERENCES User(user_id),
+ FOREIGN KEY (status_id) REFERENCES StatusDef(id),
+ INDEX (shard, project_id, period_start, period_end),
+ UNIQUE KEY (issue_id, period_start, period_end)
+) ENGINE=INNODB;
+
+CREATE TABLE IssueSnapshot2Component (
+ issuesnapshot_id INT NOT NULL,
+ component_id INT NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, component_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id)
+) ENGINE=INNODB;
+
+CREATE TABLE IssueSnapshot2Label(
+ issuesnapshot_id INT NOT NULL,
+ label_id INT NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, label_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (label_id) REFERENCES LabelDef(id)
+) ENGINE=INNODB;
+
+CREATE TABLE IssueSnapshot2Cc(
+ issuesnapshot_id INT NOT NULL,
+ cc_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, cc_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (cc_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+===============================================================
+2018-01-29: Add approval_id column to FieldDef table
+
+ALTER TABLE FieldDef ADD COLUMN approval_id INT;
+
+===============================================================
+2018-02-08: Drop previous approval tables and add default approvers table
+
+DROP TABLE ApprovalStatusDef;
+DROP TABLE Approval2Approver;
+DROP TABLE Issue2ApprovalValue;
+
+CREATE TABLE ApprovalDef2Approver (
+ approval_id INT NOT NULL,
+ approver_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (approval_id, approver_id),
+
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (approver_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+==================================================================
+2018-02-09: Add project_id column to default approvers table
+
+ALTER TABLE ApprovalDef2Approver ADD project_id SMALLINT UNSIGNED NOT NULL;
+ALTER TABLE ApprovalDef2Approver ADD CONSTRAINT ApprovalDef2Approver_ibfk_3 FOREIGN KEY (project_id) REFERENCES Project(project_id);
+
+==================================================================
+2018-02-14: Expand IssueSnapshot time columns from INT to INT UNSIGNED
+ALTER TABLE IssueSnapshot MODIFY period_start INT UNSIGNED NOT NULL;
+ALTER TABLE IssueSnapshot MODIFY period_end INT UNSIGNED NOT NULL;
+
+
+================================================================
+2018-02-22: Relax some constraints on issue snapshots
+
+ALTER TABLE IssueSnapshot MODIFY status_id int;
+ALTER TABLE IssueSnapshot DROP INDEX issue_id;
+ALTER TABLE IssueSnapshot ADD INDEX (`issue_id`,`period_start`,`period_end`);
+
+================================================================
+2018-03-12: Add launch template milestones and approval tables
+
+CREATE TABLE Template2Milestone (
+ id INT NOT NULL AUTO_INCREMENT,
+ template_id INT NOT NULL,
+ name VARCHAR(255) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ PRIMARY KEY (id, template_id),
+ FOREIGN KEY (template_id) REFERENCES Template(id)
+) ENGINE=INNODB;
+
+CREATE TABLE Template2ApprovalValue (
+ approval_id INT NOT NULL,
+ template_id INT NOT NULL,
+ milestone_id INT NOT NULL,
+ launch_status ENUM ('NA', 'review_requested', 'started', 'need_info', 'approved', 'not_approved'),
+
+ PRIMARY KEY (approval_id, template_id, milestone_id),
+
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (milestone_id) REFERENCES Template2Milestone(id)
+) ENGINE=INNODB;
+
+
+================================================================
+2018-03-13: Edit approval state enum
+
+ALTER TABLE Template2ApprovalValue CHANGE launch_status status ENUM (
+ 'needs_review', 'na', 'review_requested', 'started', 'need_info', 'approved', 'not_approved');
+
+
+================================================================
+2018-03-14: Edit approval state enum *AGAIN*
+
+ALTER TABLE Template2ApprovalValue MODIFY status ENUM (
+ 'needs_review', 'na', 'review_requested', 'started', 'need_info', 'approved', 'not_approved', 'not_set');
+
+
+================================================================
+2018-03-15: Add Issue Approval and Mileston tables
+
+DROP TABLE IF EXISTS Approval2Approver;
+DROP TABLE IF EXISTS Issue2ApprovalValue;
+
+CREATE TABLE Issue2Milestone (
+ id INT NOT NULL AUTO_INCREMENT,
+ issue_id INT NOT NULL,
+ name VARCHAR(255) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ PRIMARY KEY (id, issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+CREATE TABLE Issue2ApprovalValue (
+ issue_id INT NOT NULL,
+ approval_id INT NOT NULL,
+ milestone_id INT NOT NULL,
+ status ENUM ('needs_review', 'na', 'review_requested', 'started', 'need_info', 'approved', 'not_approved', 'not_set') DEFAULT 'not_set' NOT NULL,
+ setter_id INT UNSIGNED,
+ set_on INT,
+
+ PRIMARY KEY (issue_id, approval_id, milestone_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (milestone_id) REFERENCES Issue2Milestone(id),
+ FOREIGN KEY (setter_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+CREATE TABLE IssueApproval2Approvers (
+ issue_id INT NOT NULL,
+ approval_id INT NOT NULL,
+ approver_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issue_id, approval_id, approver_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (approver_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+ALTER TABLE Template2ApprovalValue MODIFY status ENUM (
+ 'needs_review', 'na', 'review_requested', 'started', 'need_info', 'approved', 'not_approved', 'not_set') DEFAULT 'not_set' NOT NULL;
+
+================================================================
+2018-03-15: Soft-delete Hotlists.
+ALTER TABLE Hotlist ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
+
+===============================================================
+2018-03-19: Rename issue approvers table.
+
+RENAME TABLE IssueApproval2Approvers TO IssueApproval2Approver;
+
+================================================================
+2018-03-22: Add Hotlist support to IssueSnapshots.
+
+CREATE TABLE IssueSnapshot2Hotlist(
+ issuesnapshot_id INT NOT NULL,
+ hotlist_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, hotlist_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id)
+) ENGINE=INNODB;
+
+================================================================
+2018-03-23: Add ApprovalDef2Survey table.
+
+CREATE TABLE ApprovalDef2Survey (
+ approval_id INT NOT NULL,
+ survey TEXT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (approval_id, project_id),
+
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+===============================================================
+2018-03-24: Add IssueApproval2Comment table.
+
+CREATE TABLE IssueApproval2Comment (
+ approval_id INT NOT NULL,
+ comment_id INT NOT NULL,
+
+ PRIMARY KEY (comment_id),
+ INDEX (approval_id),
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id)
+) ENGINE=INNODB;
+
+===============================================================
+2018-03-29: Rename Milestones to Phases.
+
+CREATE TABLE Issue2Phase (
+ id INT NOT NULL AUTO_INCREMENT,
+ issue_id INT NOT NULL,
+ name VARCHAR(255) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ PRIMARY KEY (id, issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+CREATE TABLE Template2Phase (
+ id INT NOT NULL AUTO_INCREMENT,
+ template_id INT NOT NULL,
+ name VARCHAR(255) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ PRIMARY KEY (id, template_id),
+ FOREIGN KEY (template_id) REFERENCES Template(id)
+) ENGINE=INNODB;
+
+ALTER TABLE Issue2ApprovalValue DROP FOREIGN KEY Issue2ApprovalValue_ibfk_4;
+ALTER TABLE Issue2ApprovalValue ADD COLUMN phase_id int NOT NULL;
+CREATE INDEX IF NOT EXISTS phase_id ON Issue2ApprovalValue (phase_id);
+ALTER TABLE Issue2ApprovalValue ADD FOREIGN KEY (phase_id) REFERENCES Issue2Phase(id);
+
+ALTER TABLE Template2ApprovalValue DROP FOREIGN KEY Template2ApprovalValue_ibfk_3;
+ALTER TABLE Template2ApprovalValue ADD COLUMN phase_id int NOT NULL;
+CREATE INDEX IF NOT EXISTS phase_id ON Template2ApprovalValue (phase_id);
+ALTER TABLE Template2ApprovalValue ADD FOREIGN KEY (phase_id) REFERENCES Template2Phase(id);
+
+================================================================
+2018-04-18: Drop all milestone schema.
+
+ALTER TABLE Template2ApprovalValue DROP COLUMN milestone_id;
+ALTER TABLE Issue2ApprovalValue DROP COLUMN milestone_id;
+
+DROP TABLE Template2Milestone;
+DROP TABLE Issue2Milestone;
+
+================================================================
+2018-04-25: Add phase_id to X2ApprovalValue tables' primary keys.
+
+ALTER TABLE Template2ApprovalValue DROP PRIMARY KEY, ADD PRIMARY KEY(approval_id, template_id, phase_id);
+ALTER TABLE Issue2ApprovalValue DROP PRIMARY KEY, ADD PRIMARY KEY(issue_id, approval_id, phase_id);
+
+==================================================================
+2018-04-30: Rename Issue2Phase table to IssuePhaseDef: Part One
+
+CREATE TABLE IssuePhaseDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+ALTER TABLE Issue2ApprovalValue DROP FOREIGN KEY Issue2ApprovalValue_ibfk_4;
+ALTER TABLE Issue2ApprovalValue ADD FOREIGN KEY (phase_id) REFERENCES IssuePhaseDef(id);
+
+==================================================================
+2018-05-02: Add phase_id to Issue2FieldValue table.
+
+ALTER TABLE Issue2FieldValue ADD COLUMN phase_id INT;
+ALTER TABLE Issue2FieldValue ADD FOREIGN KEY (phase_id) REFERENCES IssuePhaseDef(id);
+
+===================================================================
+2018-5-01: Add is_phase_field to FieldDef.
+
+ALTER TABLE FieldDef ADD COLUMN is_phase_field BOOLEAN DEFAULT FALSE;
+
+===================================================================
+2018-5-11: Rename Issue2Phase table to IssuePhaseDef: Part Two, drop Issue2Phase
+
+DROP TABLE Issue2Phase;
+==================================================================
+2018-05-11: Restrict size of index field in Issue2FieldValue
+
+ALTER TABLE Issue2FieldValue DROP INDEX field_id_5;
+ALTER TABLE Issue2FieldValue ADD INDEX (field_id, issue_shard, url_value(255));
+
+==================================================================
+2018-05-18: Replace Template2Phase FK with IssuePhaseDef.
+
+TRUNCATE TABLE Template2ApprovalValue;
+ALTER TABLE Template2ApprovalValue DROP FOREIGN KEY Template2ApprovalValue_ibfk_3;
+ALTER TABLE Template2ApprovalValue ADD FOREIGN KEY (phase_id) REFERENCES IssuePhaseDef(id);
+
+================================================================
+2018-05-22: Add boolean columns to control autocomplete exclusions.
+
+ALTER TABLE AutocompleteExclusion
+ ADD COLUMN ac_exclude BOOLEAN DEFAULT TRUE,
+ ADD COLUMN no_expand BOOLEAN DEFAULT FALSE;
+
+==================================================================
+2018-05-30: Add comment to Invalidate table
+
+ALTER TABLE Invalidate CHANGE kind
+ kind ENUM('user', 'project', 'issue', 'issue_id', 'hotlist',
+ 'comment');
+
+=================================================================
+2018-06-05: Drop Template2Phase tbl and NOT NULL constraint for approval value phase_id columns.
+
+DROP TABLE Template2Phase;
+
+ALTER TABLE Issue2ApprovalValue DROP PRIMARY KEY, ADD PRIMARY KEY(issue_id, approval_id);
+ALTER TABLE Issue2ApprovalValue MODIFY COLUMN phase_id INT;
+
+ALTER TABLE Template2ApprovalValue DROP PRIMARY KEY, ADD PRIMARY KEY(approval_id, template_id);
+ALTER TABLE Template2ApprovalValue MODIFY COLUMN phase_id INT;
+
+=================================================================
+2018-06-22: Add 'template' to Invalidate.kind_enum
+
+ALTER TABLE Invalidate MODIFY COLUMN kind enum('user', 'project', 'issue', 'issue_id', 'hotlist', 'comment', 'template') NOT NULL;
+
+=================================================================
+2018-07-02: Add UserCommits table to keep track of commits.
+
+CREATE TABLE UserCommits (
+ commit_sha VARCHAR(40),
+ parent_sha VARCHAR(40),
+ author_id INT UNSIGNED NOT NULL,
+ commit_time INT NOT NULL,
+ commit_message TEXT,
+ commit_repo VARCHAR(255),
+
+ PRIMARY KEY (commit_sha),
+ INDEX (author_id, commit_time),
+ INDEX (commit_time)
+) ENGINE=INNODB;
+
+
+=================================================================
+2018-07-16: Drop parent_sha because it isn't needed in this table and give commit_repo a clearer name.
+
+ALTER TABLE UserCommits DROP COLUMN parent_sha;
+ALTER TABLE UserCommits CHANGE commit_repo commit_repo_url VARCHAR(255);
+
+
+================================================================
+2018-08-27: Allow computed external user groups, e.g., everyone@google.com.
+
+ALTER TABLE UserGroupSettings
+ MODIFY COLUMN
+ external_group_type ENUM ('chrome_infra_auth', 'mdb', 'baggins', 'computed');
+
+
+================================================================
+2018-09-24: Add 'usergroup to Invalidate.kind enum
+
+ALTER TABLE Invalidate MODIFY COLUMN kind enum(
+ 'user', 'usergroup', 'project', 'issue', 'issue_id',
+ 'hotlist', 'comment', 'template') NOT NULL;
+
+================================================================
+2018-10-30: Fix ApprovalValue status enum for 'review_started'
+
+ALTER TABLE Template2ApprovalValue MODIFY status ENUM (
+ 'needs_review', 'na', 'review_requested', 'review_started', 'need_info', 'approved', 'not_approved', 'not_set') DEFAULT 'not_set' NOT NULL;
+
+ALTER TABLE Issue2ApprovalValue MODIFY status ENUM (
+ 'needs_review', 'na', 'review_requested', 'review_started', 'need_info', 'approved', 'not_approved', 'not_set') DEFAULT 'not_set' NOT NULL;
+
+
+================================================================
+2018-11-02: Redo LinkedAccount table.
+
+DROP TABLE LinkedAccount;
+CREATE TABLE LinkedAccount (
+ parent_id INT UNSIGNED NOT NULL,
+ child_id INT UNSIGNED NOT NULL,
+
+ KEY (parent_id),
+ UNIQUE KEY (child_id),
+ FOREIGN KEY (parent_id) REFERENCES User(user_id),
+ FOREIGN KEY (child_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+================================================================
+2018-12-03: Create LinkedAccountInvite table.
+
+CREATE TABLE LinkedAccountInvite (
+ parent_id INT UNSIGNED NOT NULL,
+ child_id INT UNSIGNED NOT NULL,
+
+ KEY (parent_id),
+ UNIQUE KEY (child_id),
+ FOREIGN KEY (parent_id) REFERENCES User(user_id),
+ FOREIGN KEY (child_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+==================================================================================
+2018-1-15: Add notify_group and notify_members bool col to UserGroupSettings table.
+
+ALTER TABLE UserGroupSettings ADD COLUMN notify_members BOOLEAN DEFAULT TRUE;
+ALTER TABLE UserGroupSettings ADD COLUMN notify_group BOOLEAN DEFAULT FALSE;
+
+=================================================================
+2019-01-23: Add two new indexes to IssueSnapshot for performance.
+
+CREATE INDEX by_period_start ON IssueSnapshot (shard, project_id, status_id, period_start);
+CREATE INDEX by_period_end ON IssueSnapshot (shard, project_id, status_id, period_end);
+
+
+================================================================
+2019-01-25: Start a more flexible way of storing user preferences.
+
+CREATE TABLE UserPrefs (
+ user_id INT UNSIGNED NOT NULL,
+ name VARCHAR(40),
+ value VARCHAR(80),
+
+ UNIQUE KEY (user_id, name)
+) ENGINE=INNODB;
+
+================================================================
+2019-04-10: Set UserPrefs that indicate that privacy click-through was seen.
+This is part of phasing out DismissedCues.
+
+INSERT IGNORE INTO UserPrefs (user_id, name, value)
+SELECT user_id, cue, 'true'
+FROM DismissedCues;
+
+================================================================
+2019-05-13: Drop unused ActionLimit table.
+
+DROP TABLE ActionLimit;
+
+================================================================
+2019-05-24: Add ext_issue_identifier column to DanglingIssueRelation table.
+
+ALTER TABLE DanglingIssueRelation ADD COLUMN ext_issue_identifier VARCHAR(2048);
+ALTER TABLE DanglingIssueRelation ADD INDEX (ext_issue_identifier);
+
+================================================================
+2019-06-06: Allow full unicode labels.
+
+ALTER TABLE LabelDef CHANGE label label VARCHAR(80) BINARY NOT NULL COLLATE utf8mb4_unicode_ci;
+
+================================================================
+2019-06-07: Add indexes to reduce cases of using filesort
+
+ALTER TABLE HotlistVisitHistory ADD INDEX (user_id, viewed);
+ALTER TABLE ReindexQueue ADD INDEX (created);
+
+================================================================
+2019-06-13: Add ext_issue_identifier to DanglingIssueRelation PRIMARY KEY.
+
+ALTER TABLE DanglingIssueRelation MODIFY COLUMN ext_issue_identifier VARCHAR(255);
+ALTER TABLE DanglingIssueRelation DROP PRIMARY KEY, ADD PRIMARY KEY(issue_id, dst_issue_project, dst_issue_local_id, kind, ext_issue_identifier);
+
+================================================================
+2019-06-25: Add unique constraint on SpamReport.
+
+ALTER IGNORE TABLE SpamReport ADD UNIQUE (user_id, comment_id, issue_id);
+
+================================================================
+2019-07-03: Add CommentImporter table.
+
+CREATE TABLE CommentImporter (
+ comment_id INT NOT NULL,
+ importer_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (comment_id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id),
+ FOREIGN KEY (importer_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+================================================================
+2019-10-09: Drop DismissedCues because that data has been in UserPrefs since April.
+
+DROP TABLE DismissedCues;
+
+================================================================
+2019-10-24: Insert row representing deleted user.
+
+INSERT IGNORE INTO User (user_id, email) VALUES (1, '');
+
+==================================================================
+2019-11-21: Add hotlist_id to Invalidate table.
+
+ALTER TABLE Invalidate CHANGE kind
+ kind ENUM('user', 'usergroup', 'project', 'issue', 'issue_id', 'hotlist', 'comment', 'template', 'hotlist_id');
+
+
+================================================================
+2019-12-30: Set custom revision_url_format for pigweed project.
+
+UPDATE Project SET revision_url_format = 'https://pigweed-review.git.corp.google.com/q/{revnum}' WHERE project_name='pigweed';
+
+================================================================
+2020-02-19: Create table for editors of a field. Also, add column in FieldDef
+to indicate if the editors of that field are being restricted.
+
+CREATE TABLE FieldDef2Editor (
+ field_id INT NOT NULL,
+ editor_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (field_id, editor_id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (editor_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+ALTER TABLE FieldDef ADD COLUMN is_restricted_field BOOL DEFAULT FALSE;
+
+================================================================
+2020-05-14: Add option to force detailed notifications for projects.
+
+ALTER TABLE Project ADD COLUMN issue_notify_always_detailed BOOLEAN DEFAULT FALSE;
+
diff --git a/schema/framework.sql b/schema/framework.sql
new file mode 100644
index 0000000..4a35106
--- /dev/null
+++ b/schema/framework.sql
@@ -0,0 +1,36 @@
+-- Copyright 2016 The Chromium Authors. All Rights Reserved.
+--
+-- Use of this source code is governed by a BSD-style
+-- license that can be found in the LICENSE file or at
+-- https://developers.google.com/open-source/licenses/bsd
+
+
+-- Create app framework tables in the monorail DB.
+
+ALTER DATABASE monorail CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;
+
+-- This table allows frontends to selectively invalidate their RAM caches.
+-- On each incoming request, the frontend queries this table to get all rows
+-- that are newer than the last row that it saw. Then it processes each such
+-- row by dropping entries from its RAM caches, and remembers the new highest
+-- timestep that it has seen.
+CREATE TABLE Invalidate (
+ -- The time at which the invalidation took effect, by that time new data
+ -- should be available to retrieve to fill local caches as needed.
+ -- This is not a clock value, it is just an integer that counts up by one
+ -- on each change.
+ timestep BIGINT NOT NULL AUTO_INCREMENT,
+
+ -- Which kind of entity was invalidated? Each kind is broad, e.g.,
+ -- invalidating a project also invalidates all issue tracker config within
+ -- that project. But, they do not nest. E.g., invalidating a project does
+ -- not invalidate all issues in the project.
+ kind enum('user', 'usergroup', 'project', 'issue', 'issue_id',
+ 'hotlist', 'comment', 'template', 'hotlist_id') NOT NULL,
+
+ -- Which cache entry should be invalidated? Special value 0 indicates
+ -- that all entries should be invalidated.
+ cache_key INT UNSIGNED,
+
+ INDEX (timestep)
+) ENGINE=INNODB;
diff --git a/schema/project.sql b/schema/project.sql
new file mode 100644
index 0000000..cb3cd42
--- /dev/null
+++ b/schema/project.sql
@@ -0,0 +1,267 @@
+-- Copyright 2016 The Chromium Authors. All Rights Reserved.
+--
+-- Use of this source code is governed by a BSD-style
+-- license that can be found in the LICENSE file or at
+-- https://developers.google.com/open-source/licenses/bsd
+
+
+-- Create project-related tables in monorail db.
+
+
+-- The User table has the mapping from user_id to email addresses, and
+-- user settings information that is needed almost every time that
+-- we load a user. E.g., when showing issue owners on the list page.
+CREATE TABLE User (
+ user_id INT UNSIGNED NOT NULL,
+ email VARCHAR(255) NOT NULL, -- lowercase
+
+ is_site_admin BOOLEAN DEFAULT FALSE,
+ obscure_email BOOLEAN DEFAULT TRUE,
+
+ -- TODO(jrobbins): Move some of these to UserPrefs.
+ notify_issue_change BOOLEAN DEFAULT TRUE, -- Pref
+ notify_starred_issue_change BOOLEAN DEFAULT TRUE, -- Pref
+ email_compact_subject BOOLEAN DEFAULT FALSE, -- Pref
+ email_view_widget BOOLEAN DEFAULT TRUE, -- Pref
+ notify_starred_ping BOOLEAN DEFAULT FALSE, -- Pref
+ banned VARCHAR(80),
+ after_issue_update ENUM (
+ 'up_to_list', 'stay_same_issue', 'next_in_list'), -- Pref
+ keep_people_perms_open BOOLEAN DEFAULT FALSE, -- Pref
+ preview_on_hover BOOLEAN DEFAULT TRUE, -- Pref
+ ignore_action_limits BOOLEAN DEFAULT FALSE,
+ last_visit_timestamp INT,
+ email_bounce_timestamp INT,
+ vacation_message VARCHAR(80),
+
+ PRIMARY KEY (user_id),
+ UNIQUE KEY (email)
+) ENGINE=INNODB;
+
+-- Row to represent all deleted users i Monorail.
+INSERT IGNORE INTO User (user_id, email) VALUES (1, '');
+
+-- The UserPrefs table has open-ended key/value pairs that affect how
+-- we present information to that user when we generate a web page for
+-- that user or send an email to that user. E.g., ("code_font",
+-- "true") would mean that issue content should be shown to that user
+-- in a monospace font. Only non-default preference values are
+-- stored: users who have never set any preferences will have no rows.
+CREATE TABLE UserPrefs (
+ user_id INT UNSIGNED NOT NULL,
+ name VARCHAR(40),
+ value VARCHAR(80),
+
+ UNIQUE KEY (user_id, name)
+) ENGINE=INNODB;
+
+
+CREATE TABLE UserCommits (
+ commit_sha VARCHAR(40),
+ author_id INT UNSIGNED NOT NULL,
+ commit_time INT NOT NULL,
+ commit_message TEXT,
+ commit_repo_url VARCHAR(255),
+
+ PRIMARY KEY (commit_sha),
+ INDEX (author_id, commit_time),
+ INDEX (commit_time)
+) ENGINE=INNODB;
+
+CREATE TABLE Project (
+ project_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ project_name VARCHAR(80) NOT NULL,
+
+ summary TEXT,
+ description TEXT,
+
+ state ENUM ('live', 'archived', 'deletable') NOT NULL,
+ access ENUM ('anyone', 'members_only') NOT NULL,
+ read_only_reason VARCHAR(80), -- normally empty for read-write.
+ state_reason VARCHAR(80), -- optional reason for doomed project.
+ delete_time INT, -- if set, automatically transition to state deletable.
+
+ issue_notify_address VARCHAR(80),
+ attachment_bytes_used BIGINT DEFAULT 0,
+ attachment_quota BIGINT DEFAULT 0, -- 50 MB default set in python code.
+
+ cached_content_timestamp INT,
+ recent_activity_timestamp INT,
+ moved_to VARCHAR(250),
+ process_inbound_email BOOLEAN DEFAULT FALSE,
+
+ only_owners_remove_restrictions BOOLEAN DEFAULT FALSE,
+ only_owners_see_contributors BOOLEAN DEFAULT FALSE,
+
+ revision_url_format VARCHAR(250),
+
+ home_page VARCHAR(250),
+ docs_url VARCHAR(250),
+ source_url VARCHAR(250),
+ logo_gcs_id VARCHAR(250),
+ logo_file_name VARCHAR(250),
+
+ issue_notify_always_detailed BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (project_id),
+ UNIQUE KEY (project_name)
+) ENGINE=INNODB;
+
+
+CREATE TABLE User2Project (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ role_name ENUM ('owner', 'committer', 'contributor'),
+
+ PRIMARY KEY (project_id, user_id),
+ INDEX (user_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE LinkedAccount (
+ parent_id INT UNSIGNED NOT NULL,
+ child_id INT UNSIGNED NOT NULL,
+
+ KEY (parent_id),
+ UNIQUE KEY (child_id),
+ FOREIGN KEY (parent_id) REFERENCES User(user_id),
+ FOREIGN KEY (child_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE LinkedAccountInvite (
+ parent_id INT UNSIGNED NOT NULL,
+ child_id INT UNSIGNED NOT NULL,
+
+ KEY (parent_id),
+ UNIQUE KEY (child_id),
+ FOREIGN KEY (parent_id) REFERENCES User(user_id),
+ FOREIGN KEY (child_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ExtraPerm (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ perm VARCHAR(80),
+
+ PRIMARY KEY (project_id, user_id, perm),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE MemberNotes (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ notes TEXT,
+
+ PRIMARY KEY (project_id, user_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE AutocompleteExclusion (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ ac_exclude BOOLEAN DEFAULT TRUE,
+ no_expand BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (project_id, user_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE UserStar (
+ starred_user_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (starred_user_id, user_id),
+ INDEX (user_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id),
+ FOREIGN KEY (starred_user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ProjectStar (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (project_id, user_id),
+ INDEX (user_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE UserGroup (
+ user_id INT UNSIGNED NOT NULL,
+ group_id INT UNSIGNED NOT NULL,
+ role ENUM ('owner', 'member') NOT NULL DEFAULT 'member',
+
+ PRIMARY KEY (user_id, group_id),
+ INDEX (group_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id),
+ FOREIGN KEY (group_id) REFERENCES User(user_id)
+
+) ENGINE=INNODB;
+
+
+CREATE TABLE UserGroupSettings (
+ group_id INT UNSIGNED NOT NULL,
+
+ who_can_view_members ENUM ('owners', 'members', 'anyone'),
+
+ external_group_type ENUM (
+ 'chrome_infra_auth', 'mdb', 'baggins', 'computed'),
+ -- timestamps in seconds since the epoch.
+ last_sync_time INT,
+ notify_members BOOL DEFAULT TRUE,
+ notify_group BOOL DEFAULT FALSE,
+
+ PRIMARY KEY (group_id),
+ FOREIGN KEY (group_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Group2Project (
+ group_id INT UNSIGNED NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (group_id, project_id),
+
+ FOREIGN KEY (group_id) REFERENCES UserGroupSettings(group_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+-- These are quick-edit commands that the user can easily repeat.
+CREATE TABLE QuickEditHistory (
+ user_id INT UNSIGNED NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ slot_num SMALLINT UNSIGNED NOT NULL,
+
+ command VARCHAR(255) NOT NULL,
+ comment TEXT NOT NULL,
+
+ PRIMARY KEY (user_id, project_id, slot_num),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+-- This allows us to offer the most recent command to the user again
+-- as the default quick-edit command for next time.
+CREATE TABLE QuickEditMostRecent (
+ user_id INT UNSIGNED NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ slot_num SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (user_id, project_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
diff --git a/schema/tracker.sql b/schema/tracker.sql
new file mode 100644
index 0000000..b445129
--- /dev/null
+++ b/schema/tracker.sql
@@ -0,0 +1,956 @@
+-- Copyright 2016 The Chromium Authors. All Rights Reserved.
+--
+-- Use of this source code is governed by a BSD-style
+-- license that can be found in the LICENSE file or at
+-- https://developers.google.com/open-source/licenses/bsd
+
+
+-- Create issue-realted tables in monorail db.
+
+
+CREATE TABLE StatusDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ status VARCHAR(80) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+ means_open BOOLEAN,
+ docstring TEXT,
+ deprecated BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY (project_id, status),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ComponentDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ -- Note: parent components have paths that are prefixes of child components.
+ path VARCHAR(255) BINARY NOT NULL,
+ docstring TEXT,
+ deprecated BOOLEAN DEFAULT FALSE,
+ created INT,
+ creator_id INT UNSIGNED,
+ modified INT,
+ modifier_id INT UNSIGNED,
+ is_deleted BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (creator_id) REFERENCES User(user_id),
+ FOREIGN KEY (modifier_id) REFERENCES User(user_id),
+ INDEX project_id2 (project_id, path)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Component2Admin (
+ component_id INT NOT NULL,
+ admin_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (component_id, admin_id),
+
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id),
+ FOREIGN KEY (admin_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Component2Cc (
+ component_id INT NOT NULL,
+ cc_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (component_id, cc_id),
+
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id),
+ FOREIGN KEY (cc_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE LabelDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ label VARCHAR(80) BINARY NOT NULL COLLATE utf8mb4_unicode_ci,
+ rank SMALLINT UNSIGNED,
+ docstring TEXT,
+ deprecated BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY (project_id, label),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Component2Label (
+ component_id INT NOT NULL,
+ label_id INT NOT NULL,
+
+ PRIMARY KEY (component_id, label_id),
+
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id),
+ FOREIGN KEY (label_id) REFERENCES LabelDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE FieldDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ field_name VARCHAR(80) BINARY NOT NULL,
+ -- TODO(jrobbins): more types
+ field_type ENUM ('enum_type', 'int_type', 'str_type', 'user_type', 'date_type', 'url_type', 'approval_type') NOT NULL,
+ applicable_type VARCHAR(80), -- No value means: offered for all issue types
+ applicable_predicate TEXT, -- No value means: TRUE
+ is_required BOOLEAN, -- true means required if applicable
+ is_niche BOOLEAN, -- true means user must click to reveal widget
+ is_multivalued BOOLEAN,
+ -- TODO(jrobbins): access controls: restrict, grant
+ -- Validation for int_type fields
+ min_value INT,
+ max_value INT,
+ -- Validation for str_type fields
+ regex VARCHAR(80),
+ -- Validation for user_type fields
+ needs_member BOOLEAN, -- User value can only be set to users who are members
+ needs_perm VARCHAR(80), -- User value can only be set to users w/ that perm
+ grants_perm VARCHAR(80), -- User named in this field gains this perm in the issue
+ -- notification options for user_type fields
+ notify_on ENUM ('never', 'any_comment') DEFAULT 'never' NOT NULL,
+ -- notification options for date_type fields
+ date_action ENUM ('no_action', 'ping_owner_only', 'ping_participants'),
+
+ -- TODO(jrobbins): default value
+ -- TODO(jrobbins): deprecated boolean?
+ docstring TEXT,
+ is_deleted BOOLEAN, -- If true, reap this field def after all values reaped.
+ approval_id INT,
+ is_phase_field BOOLEAN DEFAULT FALSE,
+ is_restricted_field BOOLEAN DEFAULT FALSE, -- If true, editors are restricted to the FieldDef2Editors tbl.
+
+ PRIMARY KEY (id),
+ UNIQUE KEY (project_id, field_name),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE FieldDef2Admin (
+ field_id INT NOT NULL,
+ admin_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (field_id, admin_id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (admin_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE FieldDef2Editor (
+ field_id INT NOT NULL,
+ editor_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (field_id, editor_id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (editor_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Issue (
+ id INT NOT NULL AUTO_INCREMENT,
+ shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ local_id INT NOT NULL,
+
+ reporter_id INT UNSIGNED NOT NULL,
+ owner_id INT UNSIGNED,
+ status_id INT,
+
+ -- These are each timestamps in seconds since the epoch.
+ modified INT NOT NULL,
+ opened INT,
+ closed INT,
+ owner_modified INT,
+ status_modified INT,
+ component_modified INT,
+
+ derived_owner_id INT UNSIGNED,
+ derived_status_id INT,
+
+ deleted BOOLEAN,
+
+ -- These are denormalized fields that should be updated when child
+ -- records are added or removed for stars or attachments. If they
+ -- get out of sync, they can be updated via an UPDATE ... SELECT statement.
+ star_count INT DEFAULT 0,
+ attachment_count INT DEFAULT 0,
+
+ is_spam BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY(id),
+ UNIQUE KEY (project_id, local_id),
+ INDEX (shard, status_id),
+ INDEX (shard, project_id),
+
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (reporter_id) REFERENCES User(user_id),
+ FOREIGN KEY (owner_id) REFERENCES User(user_id),
+ FOREIGN KEY (status_id) REFERENCES StatusDef(id),
+ FOREIGN KEY (derived_owner_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+-- This is a parallel table to the Issue table because we don't want
+-- any very wide columns in the Issue table that would slow it down.
+CREATE TABLE IssueSummary (
+ issue_id INT NOT NULL,
+ summary mediumtext COLLATE utf8mb4_unicode_ci,
+
+ PRIMARY KEY (issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+
+CREATE TABLE Issue2Component (
+ issue_id INT NOT NULL,
+ issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ component_id INT NOT NULL,
+ derived BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (issue_id, component_id, derived),
+ INDEX (component_id, issue_shard),
+
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Issue2Label (
+ issue_id INT NOT NULL,
+ issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ label_id INT NOT NULL,
+ derived BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (issue_id, label_id, derived),
+ INDEX (label_id, issue_shard),
+
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (label_id) REFERENCES LabelDef(id)
+) ENGINE=INNODB;
+
+CREATE TABLE IssuePhaseDef (
+ id INT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) BINARY NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+CREATE TABLE Issue2FieldValue (
+ issue_id INT NOT NULL,
+ issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ field_id INT NOT NULL,
+
+ int_value INT,
+ str_value VARCHAR(1024),
+ user_id INT UNSIGNED,
+ date_value INT,
+ url_value VARCHAR(1024),
+
+ derived BOOLEAN DEFAULT FALSE,
+ phase_id INT,
+
+ INDEX (issue_id, field_id),
+ INDEX (field_id, issue_shard, int_value),
+ INDEX (field_id, issue_shard, str_value(255)),
+ INDEX (field_id, issue_shard, user_id),
+ INDEX (field_id, issue_shard, date_value),
+ INDEX (field_id, issue_shard, url_value(255)),
+
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id),
+ FOREIGN KEY (phase_id) REFERENCES IssuePhaseDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Issue2Cc (
+ issue_id INT NOT NULL,
+ issue_shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ cc_id INT UNSIGNED NOT NULL,
+ derived BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (issue_id, cc_id),
+ INDEX (cc_id, issue_shard),
+
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (cc_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Issue2Notify (
+ issue_id INT NOT NULL,
+ email VARCHAR(80) NOT NULL,
+
+ PRIMARY KEY (issue_id, email),
+
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueVisitHistory (
+ issue_id INT NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ viewed INT NOT NULL,
+
+ PRIMARY KEY (user_id, issue_id),
+ INDEX (user_id, viewed),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueStar (
+ issue_id INT NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issue_id, user_id),
+ INDEX (user_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueRelation (
+ issue_id INT NOT NULL,
+ dst_issue_id INT NOT NULL,
+
+ -- Read as: src issue is blocked on dst issue.
+ kind ENUM ('blockedon', 'mergedinto') NOT NULL,
+
+ rank BIGINT,
+
+ PRIMARY KEY (issue_id, dst_issue_id, kind),
+ INDEX (issue_id),
+ INDEX (dst_issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (dst_issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE DanglingIssueRelation (
+ issue_id INT NOT NULL,
+ dst_issue_project VARCHAR(80),
+ dst_issue_local_id INT,
+ ext_issue_identifier VARCHAR(255),
+
+ -- This table uses 'blocking' so that it can guarantee the src issue
+ -- always exists, while the dst issue is always the dangling one.
+ kind ENUM ('blockedon', 'blocking', 'mergedinto') NOT NULL,
+
+ PRIMARY KEY (issue_id, dst_issue_project, dst_issue_local_id, kind, ext_issue_identifier),
+ INDEX (issue_id),
+ INDEX (ext_issue_identifier),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE CommentContent (
+ id INT NOT NULL AUTO_INCREMENT,
+ -- TODO(jrobbins): drop comment_id after Comment.commentcontent_id is added.
+ comment_id INT NOT NULL, -- Note: no forign key reference.
+ content MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
+ inbound_message MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
+
+ PRIMARY KEY (id)
+) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+
+CREATE TABLE Comment (
+ id INT NOT NULL AUTO_INCREMENT,
+ issue_id INT NOT NULL,
+ created INT NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ commenter_id INT UNSIGNED NOT NULL,
+ commentcontent_id INT, -- TODO(jrobbins) make this NOT NULL.
+
+ deleted_by INT UNSIGNED,
+ is_spam BOOLEAN DEFAULT FALSE,
+ -- TODO(lukasperaza) Update first comments SET is_description=TRUE
+ is_description BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY(id),
+ INDEX (is_spam, project_id, created),
+ INDEX (commenter_id, created),
+ INDEX (commenter_id, deleted_by, issue_id),
+
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (commenter_id) REFERENCES User(user_id),
+ FOREIGN KEY (deleted_by) REFERENCES User(user_id),
+ FOREIGN KEY (commentcontent_id) REFERENCES CommentContent(id)
+) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+
+CREATE TABLE CommentImporter (
+ comment_id INT NOT NULL,
+ importer_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (comment_id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id),
+ FOREIGN KEY (importer_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Attachment (
+ id INT NOT NULL AUTO_INCREMENT,
+
+ issue_id INT NOT NULL,
+ comment_id INT,
+
+ filename VARCHAR(255) NOT NULL,
+ filesize INT NOT NULL,
+ mimetype VARCHAR(255) NOT NULL,
+ deleted BOOLEAN,
+ gcs_object_id VARCHAR(1024) NOT NULL,
+
+ PRIMARY KEY (id),
+ INDEX (issue_id),
+ INDEX (comment_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueUpdate (
+ id INT NOT NULL AUTO_INCREMENT,
+ issue_id INT NOT NULL,
+ comment_id INT,
+
+ field ENUM (
+ 'summary', 'status', 'owner', 'cc', 'labels', 'blockedon', 'blocking', 'mergedinto',
+ 'project', 'components', 'custom', 'is_spam' ) NOT NULL,
+ old_value MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
+ new_value MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
+ added_user_id INT UNSIGNED,
+ removed_user_id INT UNSIGNED,
+ custom_field_name VARCHAR(255),
+ is_spam BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (id),
+ INDEX (issue_id),
+ INDEX (comment_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+ -- FOREIGN KEY (added_user_id) REFERENCES User(user_id),
+ -- FOREIGN KEY (removed_user_id) REFERENCES User(user_id)
+) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+
+CREATE TABLE IssueFormerLocations (
+ issue_id INT NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ local_id INT NOT NULL,
+
+ INDEX (issue_id),
+ UNIQUE KEY (project_id, local_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Template (
+ id INT NOT NULL AUTO_INCREMENT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ name VARCHAR(255) BINARY NOT NULL,
+
+ content TEXT,
+ summary TEXT,
+ summary_must_be_edited BOOLEAN,
+ owner_id INT UNSIGNED,
+ status VARCHAR(255),
+ members_only BOOLEAN,
+ owner_defaults_to_member BOOLEAN,
+ component_required BOOLEAN DEFAULT FALSE,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY (project_id, name),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Template2Label (
+ template_id INT NOT NULL,
+ label VARCHAR(255) NOT NULL,
+
+ PRIMARY KEY (template_id, label),
+ FOREIGN KEY (template_id) REFERENCES Template(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Template2Admin (
+ template_id INT NOT NULL,
+ admin_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (template_id, admin_id),
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (admin_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Template2FieldValue (
+ template_id INT NOT NULL,
+ field_id INT NOT NULL,
+
+ int_value INT,
+ str_value VARCHAR(1024),
+ user_id INT UNSIGNED,
+ date_value INT,
+ url_value VARCHAR(1024),
+
+ INDEX (template_id, field_id),
+
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (field_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Template2Component (
+ template_id INT NOT NULL,
+ component_id INT NOT NULL,
+
+ PRIMARY KEY (template_id, component_id),
+
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Template2ApprovalValue (
+ approval_id INT NOT NULL,
+ template_id INT NOT NULL,
+ phase_id INT,
+ status ENUM ('needs_review', 'na', 'review_requested', 'review_started', 'need_info', 'approved', 'not_approved', 'not_set') DEFAULT 'not_set' NOT NULL,
+
+ PRIMARY KEY (approval_id, template_id),
+
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (template_id) REFERENCES Template(id),
+ FOREIGN KEY (phase_id) REFERENCES IssuePhaseDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ProjectIssueConfig (
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ statuses_offer_merge VARCHAR(255) NOT NULL,
+ exclusive_label_prefixes VARCHAR(255) NOT NULL,
+ default_template_for_developers INT NOT NULL,
+ default_template_for_users INT NOT NULL,
+ default_col_spec TEXT,
+ default_sort_spec TEXT,
+ default_x_attr TEXT,
+ default_y_attr TEXT,
+
+ member_default_query TEXT,
+ custom_issue_entry_url TEXT,
+
+ PRIMARY KEY (project_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE FilterRule (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ rank SMALLINT UNSIGNED,
+
+ -- TODO: or should this be broken down into structured fields?
+ predicate TEXT NOT NULL,
+ -- TODO: or should this be broken down into structured fields?
+ consequence TEXT NOT NULL,
+
+ INDEX (project_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+-- Each row in this table indicates an issue that needs to be reindexed
+-- in the GAE fulltext index by our batch indexing cron job.
+CREATE TABLE ReindexQueue (
+ issue_id INT NOT NULL,
+ created TIMESTAMP,
+
+ PRIMARY KEY (issue_id),
+ INDEX (created),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id)
+) ENGINE=INNODB;
+
+
+-- This holds counters with the highest issue local_id that is
+-- already used in each project. Clients should atomically increment
+-- the value for current project and then use the new counter value
+-- when creating an issue.
+CREATE TABLE LocalIDCounter (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ used_local_id INT NOT NULL,
+ used_spam_id INT NOT NULL,
+
+ PRIMARY KEY (project_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+-- This is a saved query. It can be configured by a project owner to
+-- be used by all visitors to that project. Or, it can be a a
+-- personal saved query that appears on a user's "Saved queries" page
+-- and executes in the scope of one or more projects.
+CREATE TABLE SavedQuery (
+ id INT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(80) NOT NULL,
+
+ -- For now, we only allow saved queries to be based off ane of the built-in
+ -- query scopes, and those can never be deleted, so there can be no nesting,
+ -- dangling references, and thus no need for cascading deletes.
+ base_query_id INT,
+ query TEXT NOT NULL,
+
+ PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+
+-- Rows for built-in queries. These are in the database soley so that
+-- foreign key constraints are satisfied. These rows ar never read or updated.
+INSERT IGNORE INTO SavedQuery VALUES
+ (1, 'All issues', 0, ''),
+ (2, 'Open issues', 0, 'is:open'),
+ (3, 'Open and owned by me', 0, 'is:open owner:me'),
+ (4, 'Open and reported by me', 0, 'is:open reporter:me'),
+ (5, 'Open and starred by me', 0, 'is:open is:starred'),
+ (6, 'New issues', 0, 'status:new'),
+ (7, 'Issues to verify', 0, 'status=fixed,done'),
+ (8, 'Open with comment by me', 0, 'is:open commentby:me');
+
+-- The sole purpose of this statement is to force user defined saved queries
+-- to have IDs greater than 100 so that 1-100 are reserved for built-ins.
+INSERT IGNORE INTO SavedQuery VALUES (100, '', 0, '');
+
+
+-- User personal queries default to executing in the context of the
+-- project where they were created, but the user can edit them to make
+-- them into cross-project queries. Project saved queries always
+-- implicitly execute in the context of a project.
+CREATE TABLE SavedQueryExecutesInProject (
+ query_id INT NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (query_id, project_id),
+ INDEX (project_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (query_id) REFERENCES SavedQuery(id)
+) ENGINE=INNODB;
+
+
+-- These are the queries edited by the project owner on the project
+-- admin pages.
+CREATE TABLE Project2SavedQuery (
+ project_id SMALLINT UNSIGNED NOT NULL,
+ rank SMALLINT UNSIGNED NOT NULL,
+ query_id INT NOT NULL,
+
+ -- TODO(jrobbins): visibility: owners, committers, contributors, anyone
+
+ PRIMARY KEY (project_id, rank),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (query_id) REFERENCES SavedQuery(id)
+) ENGINE=INNODB;
+
+
+-- These are personal saved queries.
+CREATE TABLE User2SavedQuery (
+ user_id INT UNSIGNED NOT NULL,
+ rank SMALLINT UNSIGNED NOT NULL,
+ query_id INT NOT NULL,
+
+ -- TODO(jrobbins): daily and weekly digests, and the ability to have
+ -- certain subscriptions go to username+SOMETHING@example.com.
+ subscription_mode ENUM ('noemail', 'immediate') DEFAULT 'noemail' NOT NULL,
+
+ PRIMARY KEY (user_id, rank),
+ FOREIGN KEY (user_id) REFERENCES User(user_id),
+ FOREIGN KEY (query_id) REFERENCES SavedQuery(id)
+) ENGINE=INNODB;
+
+
+-- Created whenever a user reports an issue or comment as spam.
+-- Note this is distinct from a SpamVerdict, which is issued by
+-- the system rather than a human user.
+CREATE TABLE SpamReport (
+ -- when this report was generated
+ created TIMESTAMP NOT NULL,
+ -- when the reported content was generated
+ -- TODO(jrobbins): needs default current_time in MySQL 5.7.
+ content_created TIMESTAMP NOT NULL,
+ -- id of the reporting user
+ user_id INT UNSIGNED NOT NULL,
+ -- id of the reported user
+ reported_user_id INT UNSIGNED NOT NULL,
+ -- either this or issue_id must be set
+ comment_id INT,
+ -- either this or comment_id must be set
+ issue_id INT,
+
+ INDEX (issue_id),
+ INDEX (comment_id),
+ UNIQUE (user_id, comment_id, issue_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id)
+) ENGINE=INNODB;
+
+
+-- Any time a human or the system sets is_spam to true,
+-- or changes it from true to false, we want to have a
+-- record of who did it and why.
+CREATE TABLE SpamVerdict (
+ -- when this verdict was generated
+ created TIMESTAMP NOT NULL,
+
+ -- id of the reporting user, may be null if it was
+ -- an automatic classification.
+ user_id INT UNSIGNED,
+
+ -- id of the containing project.
+ project_id INT NOT NULL,
+
+ -- either this or issue_id must be set.
+ comment_id INT,
+
+ -- either this or comment_id must be set.
+ issue_id INT,
+
+ -- If the classifier issued the verdict, this should be set.
+ classifier_confidence FLOAT,
+
+ -- This should reflect the new is_spam value that was applied
+ -- by this verdict, not the value it had prior.
+ is_spam BOOLEAN NOT NULL,
+
+ -- manual: a project owner marked it as spam.
+ -- threshhold: number of SpamReports from non-members was exceeded.
+ -- classifier: the automatic classifier reports it as spam.
+ -- fail_open: the classifier failed, resulting in a ham decision.
+ reason ENUM ("manual", "threshold", "classifier", "fail_open") NOT NULL,
+
+ overruled BOOL NOT NULL,
+
+ -- True indicates that the prediction service PRC failed and we gave up.
+ fail_open BOOL DEFAULT FALSE,
+
+ INDEX (issue_id),
+ INDEX (comment_id),
+ INDEX (classifier_confidence),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id)
+
+) ENGINE=INNODB;
+
+
+-- These are user-curated lists of issues which can be re-ordered to
+-- prioritize work.
+CREATE TABLE Hotlist (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(80) NOT NULL,
+
+ summary TEXT,
+ description TEXT,
+
+ is_private BOOLEAN DEFAULT FALSE,
+ is_deleted BOOLEAN DEFAULT FALSE,
+ default_col_spec TEXT,
+
+ PRIMARY KEY (id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Hotlist2Issue (
+ hotlist_id INT UNSIGNED NOT NULL,
+ issue_id INT NOT NULL,
+
+ rank BIGINT NOT NULL,
+ adder_id INT UNSIGNED,
+ added INT,
+ note TEXT,
+
+ PRIMARY KEY (hotlist_id, issue_id),
+ INDEX (hotlist_id),
+ INDEX (issue_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (adder_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE Hotlist2User (
+ hotlist_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ role_name ENUM ('owner', 'editor', 'follower') NOT NULL,
+
+ PRIMARY KEY (hotlist_id, user_id),
+ INDEX (hotlist_id),
+ INDEX (user_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE HotlistStar (
+ hotlist_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (hotlist_id, user_id),
+ INDEX (user_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE HotlistVisitHistory (
+ hotlist_id INT UNSIGNED NOT NULL,
+ user_id INT UNSIGNED NOT NULL,
+ viewed INT NOT NULL,
+
+ PRIMARY KEY (user_id, hotlist_id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id),
+ FOREIGN KEY (user_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ComponentIssueClosedIndex (
+ closed_index INT NOT NULL,
+ PRIMARY KEY (closed_index)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ApprovalDef2Approver (
+ approval_id INT NOT NULL,
+ approver_id INT UNSIGNED NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (approval_id, approver_id),
+
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (approver_id) REFERENCES User(user_id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE ApprovalDef2Survey (
+ approval_id INT NOT NULL,
+ survey TEXT,
+ project_id SMALLINT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (approval_id),
+
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id)
+) ENGINE=INNODB;
+
+CREATE TABLE Issue2ApprovalValue (
+ issue_id INT NOT NULL,
+ approval_id INT NOT NULL,
+ phase_id INT,
+ status ENUM ('needs_review', 'na', 'review_requested', 'review_started', 'need_info', 'approved', 'not_approved', 'not_set') DEFAULT 'not_set' NOT NULL,
+ setter_id INT UNSIGNED,
+ set_on INT,
+
+ PRIMARY KEY (issue_id, approval_id),
+ FOREIGN KEY (setter_id) REFERENCES User(user_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (phase_id) REFERENCES IssuePhaseDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueApproval2Approver (
+ issue_id INT NOT NULL,
+ approval_id INT NOT NULL,
+ approver_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issue_id, approval_id, approver_id),
+ FOREIGN KEY (issue_id) REFERENCES Issue(id),
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (approver_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueApproval2Comment (
+ approval_id INT NOT NULL,
+ comment_id INT NOT NULL,
+
+ PRIMARY KEY (comment_id),
+ INDEX (approval_id),
+ FOREIGN KEY (approval_id) REFERENCES FieldDef(id),
+ FOREIGN KEY (comment_id) REFERENCES Comment(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueSnapshot (
+ id INT NOT NULL AUTO_INCREMENT,
+ issue_id INT NOT NULL,
+ shard SMALLINT UNSIGNED DEFAULT 0 NOT NULL,
+ project_id SMALLINT UNSIGNED NOT NULL,
+ local_id INT NOT NULL,
+ reporter_id INT UNSIGNED NOT NULL,
+ owner_id INT UNSIGNED,
+ status_id INT,
+ period_start INT UNSIGNED NOT NULL,
+ period_end INT UNSIGNED NOT NULL,
+ is_open BOOLEAN DEFAULT TRUE,
+
+ PRIMARY KEY (id),
+ FOREIGN KEY (project_id) REFERENCES Project(project_id),
+ FOREIGN KEY (reporter_id) REFERENCES User(user_id),
+ FOREIGN KEY (owner_id) REFERENCES User(user_id),
+ FOREIGN KEY (status_id) REFERENCES StatusDef(id),
+ INDEX (shard, project_id, period_start, period_end),
+ INDEX by_period_start (shard, project_id, status_id, period_start),
+ INDEX by_period_end (shard, project_id, status_id, period_end),
+ KEY (issue_id, period_start, period_end)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueSnapshot2Component (
+ issuesnapshot_id INT NOT NULL,
+ component_id INT NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, component_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (component_id) REFERENCES ComponentDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueSnapshot2Label(
+ issuesnapshot_id INT NOT NULL,
+ label_id INT NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, label_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (label_id) REFERENCES LabelDef(id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueSnapshot2Cc(
+ issuesnapshot_id INT NOT NULL,
+ cc_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, cc_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (cc_id) REFERENCES User(user_id)
+) ENGINE=INNODB;
+
+
+CREATE TABLE IssueSnapshot2Hotlist(
+ issuesnapshot_id INT NOT NULL,
+ hotlist_id INT UNSIGNED NOT NULL,
+
+ PRIMARY KEY (issuesnapshot_id, hotlist_id),
+ FOREIGN KEY (issuesnapshot_id) REFERENCES IssueSnapshot(id),
+ FOREIGN KEY (hotlist_id) REFERENCES Hotlist(id)
+) ENGINE=INNODB;