1/* wc-checks.sql -- trigger-based checks for the wc-metadata database. 2 * This is intended for use with SQLite 3 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25-- STMT_VERIFICATION_TRIGGERS 26 27/* ------------------------------------------------------------------------- */ 28 29CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository 30BEGIN 31 SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); 32END; 33 34/* ------------------------------------------------------------------------- */ 35 36/* Verify: on every NODES row: parent_relpath is parent of local_relpath */ 37CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes 38WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) 39 OR (relpath_depth(new.local_relpath) 40 = relpath_depth(new.parent_relpath) + 1)) 41BEGIN 42 SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); 43END; 44 45/* Verify: on every NODES row: its op-depth <= its own depth */ 46CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes 47WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) 48BEGIN 49 SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); 50END; 51 52/* Verify: on every NODES row: it is an op-root or it has a parent with the 53 sames op-depth. (Except when the node is a file external) */ 54CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes 55WHEN NOT ( 56 (new.op_depth = relpath_depth(new.local_relpath)) 57 OR 58 (EXISTS (SELECT 1 FROM nodes 59 WHERE wc_id = new.wc_id AND op_depth = new.op_depth 60 AND local_relpath = new.parent_relpath)) 61 ) 62 AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) 63BEGIN 64 SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); 65END; 66 67/* Verify: on every ACTUAL row (except root): a NODES row exists at its 68 * parent path. */ 69CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node 70WHEN NOT (new.local_relpath = '' 71 OR EXISTS (SELECT 1 FROM nodes 72 WHERE wc_id = new.wc_id 73 AND local_relpath = new.parent_relpath)) 74BEGIN 75 SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); 76END; 77 78-- STMT_STATIC_VERIFY 79SELECT local_relpath, op_depth, 1, 'Invalid parent relpath set in NODES' 80FROM nodes n WHERE local_relpath != '' 81 AND (parent_relpath IS NULL 82 OR NOT IS_STRICT_DESCENDANT_OF(local_relpath, parent_relpath) 83 OR relpath_depth(local_relpath) != relpath_depth(parent_relpath)+1) 84 85UNION ALL 86 87SELECT local_relpath, -1, 2, 'Invalid parent relpath set in ACTUAL' 88FROM actual_node a WHERE local_relpath != '' 89 AND (parent_relpath IS NULL 90 OR NOT IS_STRICT_DESCENDANT_OF(local_relpath, parent_relpath) 91 OR relpath_depth(local_relpath) != relpath_depth(parent_relpath)+1) 92 93UNION ALL 94 95/* All ACTUAL nodes must have an equivalent NODE in NODES 96 or be only one level deep (delete-delete tc) */ 97SELECT local_relpath, -1, 10, 'No ancestor in ACTUAL' 98FROM actual_node a WHERE local_relpath != '' 99 AND NOT EXISTS(SELECT 1 from nodes i 100 WHERE i.wc_id=a.wc_id 101 AND i.local_relpath=a.parent_relpath) 102 AND NOT EXISTS(SELECT 1 from nodes i 103 WHERE i.wc_id=a.wc_id 104 AND i.local_relpath=a.local_relpath) 105 106UNION ALL 107/* Verify if the ACTUAL data makes sense for the related node. 108 Only conflict data is valid if there is none */ 109SELECT a.local_relpath, -1, 11, 'Bad or Unneeded actual data' 110FROM actual_node a 111LEFT JOIN nodes n on n.wc_id = a.wc_id AND n.local_relpath = a.local_relpath 112 AND n.op_depth = (SELECT MAX(op_depth) from nodes i 113 WHERE i.wc_id=a.wc_id AND i.local_relpath=a.local_relpath) 114WHERE (a.properties IS NOT NULL 115 AND (n.presence IS NULL 116 OR n.presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE))) 117 OR (a.changelist IS NOT NULL AND (n.kind IS NOT NULL AND n.kind != MAP_FILE)) 118 OR (a.conflict_data IS NULL AND a.properties IS NULL AND a.changelist IS NULL) 119 AND NOT EXISTS(SELECT 1 from nodes i 120 WHERE i.wc_id=a.wc_id 121 AND i.local_relpath=a.parent_relpath) 122 123UNION ALL 124 125/* A parent node must exist for every normal node except the root. 126 That node must exist at a lower or equal op-depth */ 127SELECT local_relpath, op_depth, 20, 'No ancestor in NODES' 128FROM nodes n WHERE local_relpath != '' 129 AND file_external IS NULL 130 AND NOT EXISTS(SELECT 1 from nodes i 131 WHERE i.wc_id=n.wc_id 132 AND i.local_relpath=n.parent_relpath 133 AND i.op_depth <= n.op_depth) 134 135UNION ALL 136/* If a node is not present in the working copy (normal, add, copy) it doesn't 137 have revision details stored on this record */ 138SELECT local_relpath, op_depth, 21, 'Unneeded node data' 139FROM nodes 140WHERE presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE) 141AND (properties IS NOT NULL 142 OR checksum IS NOT NULL 143 OR depth IS NOT NULL 144 OR symlink_target IS NOT NULL 145 OR changed_revision IS NOT NULL 146 OR (changed_date IS NOT NULL AND changed_date != 0) 147 OR changed_author IS NOT NULL 148 OR translated_size IS NOT NULL 149 OR last_mod_time IS NOT NULL 150 OR dav_cache IS NOT NULL 151 OR file_external IS NOT NULL 152 OR inherited_props IS NOT NULL) 153 154UNION ALL 155/* base-deleted nodes don't have a repository location. They are just 156 shadowing without a replacement */ 157SELECT local_relpath, op_depth, 22, 'Unneeded base-deleted node data' 158FROM nodes 159WHERE presence IN (MAP_BASE_DELETED) 160AND (repos_id IS NOT NULL 161 OR repos_path IS NOT NULL 162 OR revision IS NOT NULL) 163 164UNION ALL 165/* Verify if type specific data is set (or not set for wrong type) */ 166SELECT local_relpath, op_depth, 23, 'Kind specific data invalid on normal' 167FROM nodes 168WHERE presence IN (MAP_NORMAL, MAP_INCOMPLETE) 169AND (kind IS NULL 170 OR (repos_path IS NULL 171 AND (properties IS NOT NULL 172 OR changed_revision IS NOT NULL 173 OR changed_author IS NOT NULL 174 OR (changed_date IS NOT NULL AND changed_date != 0))) 175 OR (CASE WHEN kind = MAP_FILE AND repos_path IS NOT NULL 176 THEN checksum IS NULL 177 ELSE checksum IS NOT NULL END) 178 OR (CASE WHEN kind = MAP_DIR THEN depth IS NULL 179 ELSE depth IS NOT NULL END) 180 OR (CASE WHEN kind = MAP_SYMLINK THEN symlink_target IS NULL 181 ELSE symlink_target IS NOT NULL END)) 182 183UNION ALL 184/* Local-adds are always their own operation (read: they don't have 185 op-depth descendants, nor are op-depth descendants */ 186SELECT local_relpath, op_depth, 24, 'Invalid op-depth for local add' 187FROM nodes 188WHERE presence IN (MAP_NORMAL, MAP_INCOMPLETE) 189 AND repos_path IS NULL 190 AND op_depth != relpath_depth(local_relpath) 191 192UNION ALL 193/* op-depth descendants are only valid if they have a direct parent 194 node at the same op-depth. Only certain types allow further 195 descendants */ 196SELECT local_relpath, op_depth, 25, 'Node missing op-depth ancestor' 197FROM nodes n 198WHERE op_depth < relpath_depth(local_relpath) 199 AND file_external IS NULL 200 AND NOT EXISTS(SELECT 1 FROM nodes p 201 WHERE p.wc_id=n.wc_id AND p.local_relpath=n.parent_relpath 202 AND p.op_depth=n.op_depth 203 AND (p.presence IN (MAP_NORMAL, MAP_INCOMPLETE) 204 OR (p.presence IN (MAP_BASE_DELETED, MAP_NOT_PRESENT) 205 AND n.presence = MAP_BASE_DELETED))) 206 207UNION ALL 208/* Present op-depth descendants have the repository location implied by their 209 ancestor */ 210SELECT n.local_relpath, n.op_depth, 26, 'Copied descendant mismatch' 211FROM nodes n 212JOIN nodes p 213 ON p.wc_id=n.wc_id AND p.local_relpath=n.parent_relpath 214 AND n.op_depth=p.op_depth 215WHERE n.op_depth > 0 AND n.presence IN (MAP_NORMAL, MAP_INCOMPLETE) 216 AND (n.repos_id != p.repos_id 217 OR n.repos_path != 218 RELPATH_SKIP_JOIN(n.parent_relpath, p.repos_path, n.local_relpath) 219 OR n.revision != p.revision 220 OR p.kind != MAP_DIR 221 OR n.moved_here IS NOT p.moved_here) 222 223UNION ALL 224/* Only certain presence values are valid as op-root. 225 Note that the wc-root always has presence normal or incomplete */ 226SELECT n.local_relpath, n.op_depth, 27, 'Invalid op-root presence' 227FROM nodes n 228WHERE n.op_depth = relpath_depth(local_relpath) 229 AND presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE, MAP_BASE_DELETED) 230 231UNION ALL 232/* If a node is shadowed, all its present op-depth descendants 233 must be shadowed at the same op-depth as well */ 234SELECT n.local_relpath, s.op_depth, 28, 'Incomplete shadowing' 235FROM nodes n 236JOIN nodes s ON s.wc_id=n.wc_id AND s.local_relpath=n.local_relpath 237 AND s.op_depth = relpath_depth(s.local_relpath) 238 AND s.op_depth = (SELECT MIN(op_depth) FROM nodes d 239 WHERE d.wc_id=s.wc_id AND d.local_relpath=s.local_relpath 240 AND d.op_depth > n.op_depth) 241WHERE n.presence IN (MAP_NORMAL, MAP_INCOMPLETE) 242 AND EXISTS(SELECT 1 243 FROM nodes dn 244 WHERE dn.wc_id=n.wc_id AND dn.op_depth=n.op_depth 245 AND dn.presence IN (MAP_NORMAL, MAP_INCOMPLETE) 246 AND IS_STRICT_DESCENDANT_OF(dn.local_relpath, n.local_relpath) 247 AND dn.file_external IS NULL 248 AND NOT EXISTS(SELECT 1 249 FROM nodes ds 250 WHERE ds.wc_id=n.wc_id AND ds.op_depth=s.op_depth 251 AND ds.local_relpath=dn.local_relpath)) 252 253UNION ALL 254/* A base-delete is only valid if it directly deletes a present node */ 255SELECT s.local_relpath, s.op_depth, 29, 'Invalid base-delete' 256FROM nodes s 257LEFT JOIN nodes n ON n.wc_id=s.wc_id AND n.local_relpath=s.local_relpath 258 AND n.op_depth = (SELECT MAX(op_depth) FROM nodes d 259 WHERE d.wc_id=s.wc_id AND d.local_relpath=s.local_relpath 260 AND d.op_depth < s.op_depth) 261WHERE s.presence = MAP_BASE_DELETED 262 AND (n.presence IS NULL 263 OR n.presence NOT IN (MAP_NORMAL, MAP_INCOMPLETE) 264 /*OR n.kind != s.kind*/) 265 266UNION ALL 267/* Moves are stored in the working layers, not in BASE */ 268SELECT n.local_relpath, n.op_depth, 30, 'Invalid data for BASE' 269FROM nodes n 270WHERE n.op_depth = 0 271 AND (n.moved_to IS NOT NULL 272 OR n.moved_here IS NOT NULL) 273 274UNION ALL 275/* If moved_here is set on an op-root, there must be a proper moved_to */ 276SELECT d.local_relpath, d.op_depth, 60, 'Moved here without origin' 277FROM nodes d 278WHERE d.op_depth = relpath_depth(d.local_relpath) 279 AND d.moved_here IS NOT NULL 280 AND NOT EXISTS(SELECT 1 FROM nodes s 281 WHERE s.wc_id = d.wc_id AND s.moved_to = d.local_relpath) 282 283UNION ALL 284/* If moved_to is set there should be an moved op root at the target */ 285SELECT s.local_relpath, s.op_depth, 61, 'Moved to without target' 286FROM nodes s 287WHERE s.moved_to IS NOT NULL 288 AND NOT EXISTS(SELECT 1 FROM nodes d 289 WHERE d.wc_id = s.wc_id AND d.local_relpath = s.moved_to 290 AND d.op_depth = relpath_depth(d.local_relpath) 291 AND d.moved_here =1 AND d.repos_path IS NOT NULL) 292