github.com/JBoudou/Itero@v0.1.7/sql/install.mysql (about) 1 # Itero - Iterative vote online 2 # Copyright (C) 2020 Joseph Boudou 3 # 4 # This program is free software: you can redistribute it and/or modify 5 # it under the terms of the GNU Affero General Public License as 6 # published by the Free Software Foundation, either version 3 of the 7 # License, or (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU Affero General Public License for more details. 13 # 14 # You should have received a copy of the GNU Affero General Public License 15 # along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17 # Compatible with 10.3.28-MariaDB MariaDB Server 18 19 SET sql_mode='TRADITIONAL,NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY'; 20 21 ## Deletion must be in reverse order ## 22 23 DROP PROCEDURE IF EXISTS Ballots_checker_before; 24 DROP TABLE IF EXISTS Ballots; 25 26 DROP PROCEDURE IF EXISTS Participants_checker_before; 27 DROP TABLE IF EXISTS Participants; 28 29 DROP PROCEDURE IF EXISTS Alternatives_checker_before; 30 DROP TABLE IF EXISTS Alternatives; 31 32 DROP PROCEDURE IF EXISTS Polls_checker_before; 33 DROP TABLE IF EXISTS Polls; 34 DROP TABLE IF EXISTS PollType; 35 DROP TABLE IF EXISTS PollRule; 36 DROP TABLE IF EXISTS RoundType; 37 38 DROP PROCEDURE IF EXISTS Users_checker_before; 39 DROP TABLE IF EXISTS Users; 40 41 42 ######## Users ######## 43 44 45 # Deletion of a user is not possible once she participated to a poll. 46 CREATE TABLE Users ( 47 48 # Passwd stores only a hash signature. 49 Id int unsigned NOT NULL AUTO_INCREMENT, 50 Email varchar(128) , 51 Name varchar(64) , 52 Passwd binary(32) , 53 Hash binary(3) , 54 Created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 55 Verified bool NOT NULL DEFAULT FALSE, 56 57 CONSTRAINT Users_pk PRIMARY KEY (Id), 58 CONSTRAINT Users_Email_unique UNIQUE (Email), 59 CONSTRAINT Users_Name_unique UNIQUE (Name), 60 CONSTRAINT Users_Hash_unique UNIQUE (Hash) 61 62 ) ENGINE = InnoDB; 63 64 DELIMITER // 65 66 CREATE OR REPLACE PROCEDURE Users_checker_before ( 67 Email varchar(128), 68 Name varchar(64), 69 Passwd binary(32), 70 Hash binary(3) 71 ) 72 BEGIN 73 IF Hash IS NULL AND (Email IS NULL OR Name IS NULL OR Passwd IS NULL) THEN 74 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'When Hash is NULL, Email, Name and Passwd must not be NULL'; 75 END IF; 76 IF Hash IS NULL AND Email NOT LIKE '_%@_%.__%' THEN 77 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Email field is not valid'; 78 END IF; 79 IF Hash IS NULL AND length(Name) < 2 THEN 80 SIGNAL SQLSTATE '44999' SET MESSAGE_TEXT = 'Name field is too short'; 81 END IF; 82 END; 83 // 84 85 CREATE OR REPLACE TRIGGER Users_check_before_insert 86 BEFORE INSERT ON Users FOR EACH ROW 87 BEGIN 88 SET NEW.Created = CURRENT_TIMESTAMP(); 89 CALL Users_checker_before(NEW.Email, NEW.Name, NEW.Passwd, NEW.Hash); 90 END; 91 // 92 93 CREATE OR REPLACE TRIGGER Users_check_before_update 94 BEFORE UPDATE ON Users FOR EACH ROW 95 BEGIN 96 IF NEW.Created != OLD.Created THEN 97 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Created cannot be changed'; 98 END IF; 99 CALL Users_checker_before(NEW.Email, NEW.Name, NEW.Passwd, NEW.Hash); 100 END; 101 // 102 103 DELIMITER ; 104 105 106 ######## Confirmations ######## 107 108 CREATE TABLE Confirmations ( 109 110 Id int unsigned NOT NULL AUTO_INCREMENT, 111 Salt int unsigned NOT NULL, 112 Type ENUM('verify','passwd') NOT NULL, 113 User int unsigned NOT NULL, 114 Expires datetime NOT NULL, 115 116 CONSTRAINT Confirmations_pk PRIMARY KEY (Id), 117 CONSTRAINT Confirmations_User_fk FOREIGN KEY (User) REFERENCES Users (Id) ON DELETE CASCADE, 118 CONSTRAINT Confirmations_UserType_unique UNIQUE (User, Type) 119 120 ) ENGINE = InnoDB; 121 122 DELIMITER // 123 124 CREATE OR REPLACE PROCEDURE Confirmations_checker_before ( 125 Salt int unsigned 126 ) 127 BEGIN 128 IF Salt >= 4194304 THEN 129 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salt must be 22 bits long'; 130 END IF; 131 END; 132 // 133 134 CREATE OR REPLACE TRIGGER Confirmations_check_before_insert 135 BEFORE INSERT ON Confirmations FOR EACH ROW 136 BEGIN 137 CALL Confirmations_checker_before(NEW.Salt); 138 END; 139 // 140 141 CREATE OR REPLACE TRIGGER Confirmations_check_before_update 142 BEFORE UPDATE ON Confirmations FOR EACH ROW 143 BEGIN 144 CALL Confirmations_checker_before(NEW.Salt); 145 END; 146 // 147 148 DELIMITER ; 149 150 151 ######## Polls ######## 152 153 # Internal type of polls. 154 # Describes alternatives, outcomes and ballots. 155 CREATE TABLE PollType ( 156 Id tinyint unsigned NOT NULL, 157 Label varchar(32) NOT NULL, 158 CONSTRAINT PollType_pk PRIMARY KEY (Id), 159 CONSTRAINT PollType_Label_unique UNIQUE (Label) 160 ) ENGINE = InnoDB; 161 162 INSERT INTO PollType VALUES 163 # The outcome is a subset of the alternatives such that the sum of the cost of the alternatives in 164 # the outcome is at most MaxOutcomeCost. 165 # A ballot is a subset of the alternatives (all with rank 1) such that: 166 # - if BallotCostIsCount is true, the cadinality of the ballot is at most MaxBallotCost, 167 # - if BallotCostIsCount is false, the sum of the cost of the alternatives in the ballot is at 168 # most MaxBallotCost. 169 (0, 'Acceptance Set') 170 ; 171 172 # How outcome is computed. 173 # See Go implementation for detail of each method. 174 CREATE TABLE PollRule ( 175 Id tinyint unsigned NOT NULL, 176 Label varchar(32) NOT NULL, 177 CONSTRAINT PollRule_pk PRIMARY KEY (Id), 178 CONSTRAINT PollRule_Label_unique UNIQUE (Label) 179 ) ENGINE = InnoDB; 180 181 INSERT INTO PollRule VALUES 182 (0, 'Plurality') 183 ; 184 185 # How moves are made during each round. 186 CREATE TABLE RoundType ( 187 Id tinyint unsigned NOT NULL, 188 Label varchar(32) NOT NULL, 189 CONSTRAINT RoundType_pk PRIMARY KEY (Id), 190 CONSTRAINT RoundType_Label_unique UNIQUE (Label) 191 ) ENGINE = InnoDB; 192 193 INSERT INTO RoundType VALUES 194 (0, 'Freely Asynchronous') # Participants can move at any time, any number of time. 195 ; 196 197 # Deletion of a poll is possible (cascade). 198 CREATE TABLE Polls ( 199 200 Id int unsigned NOT NULL AUTO_INCREMENT, 201 Title tinytext NOT NULL, 202 Description text, 203 Admin int unsigned NOT NULL, # FK on Users 204 Created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 205 State ENUM('Waiting','Active','Terminated') NOT NULL DEFAULT 'Active', 206 Start datetime, 207 ShortURL varchar(32), 208 209 # Salt is used to make discovery of poll difficult 210 Salt int unsigned NOT NULL, 211 212 Type tinyint unsigned NOT NULL DEFAULT 0, # FK on PollType 213 Rule tinyint unsigned NOT NULL DEFAULT 0, # FK on PollRule 214 RoundType tinyint unsigned NOT NULL DEFAULT 0, # FK on RoundType 215 216 Electorate ENUM('All','Logged','Verified') NOT NULL DEFAULT 'Logged', 217 Hidden bool NOT NULL DEFAULT FALSE, 218 219 NbChoices tinyint unsigned NOT NULL, 220 MaxOutcomeCost decimal(65,6) NOT NULL DEFAULT 1, 221 MaxBallotCost decimal(65,6) NOT NULL DEFAULT 1, 222 BallotCostIsCount bool NOT NULL DEFAULT TRUE, 223 224 # Whether the last vote is used when a participant did not vote for the last round. 225 ReportVote bool NOT NULL DEFAULT FALSE, 226 227 # The poll ends as soon as one of the following condition holds: 228 # - CurrentRound >= MaxNbRounds 229 # - Deadline <= CURRENT_TIMESTAMP() AND CurrentRound >= MinNbRounds 230 MinNbRounds tinyint unsigned NOT NULL DEFAULT 2, 231 MaxNbRounds tinyint unsigned DEFAULT 10, 232 Deadline datetime, 233 234 # The round ends as soon as one of the following conditions holds: 235 # - addtime(CurrentRoundStart, MaxRoundDuration) >= CURRENT_TIMESTAMP() 236 # - CurrentRound > 0 AND RoundThreshold = 0 AND one participant moved for this round 237 # - CurrentRound > 0 AND RoundThreshold > 0 AND the proportion of participants who moved for this round >= RoundThreshold 238 MaxRoundDuration time DEFAULT '24:00:00', 239 RoundThreshold double unsigned NOT NULL DEFAULT 1, 240 241 CurrentRound tinyint unsigned NOT NULL DEFAULT 0, 242 CurrentRoundStart timestamp NOT NULL DEFAULT '2020-01-01', 243 244 CONSTRAINT Polls_pk PRIMARY KEY (Id), 245 246 CONSTRAINT Polls_Admin_fk FOREIGN KEY (Admin) REFERENCES Users (Id), 247 CONSTRAINT Polls_Type_fk FOREIGN KEY (Type) REFERENCES PollType (Id), 248 CONSTRAINT Polls_Rule_fk FOREIGN KEY (Rule) REFERENCES PollRule (Id), 249 CONSTRAINT Polls_RoundType_fk FOREIGN KEY (RoundType) REFERENCES RoundType (Id), 250 251 CONSTRAINT Polls_ShortURL_unique UNIQUE (ShortURL) 252 253 ) ENGINE = InnoDB; 254 255 DELIMITER // 256 257 CREATE FUNCTION RoundDeadline( 258 CurrentRoundStart datetime, 259 MaxRoundDuration time, 260 Deadline datetime, 261 CurrentRound tinyint, 262 MinNbRounds tinyint 263 ) 264 RETURNS datetime DETERMINISTIC 265 BEGIN 266 DECLARE one datetime; 267 SET one = ADDTIME(CurrentRoundStart, MaxRoundDuration); 268 269 IF CurrentRound + 1 < MinNbRounds OR Deadline < one THEN 270 RETURN one; 271 END IF; 272 273 IF ADDTIME(one, MaxRoundDuration) > Deadline THEN 274 RETURN Deadline; 275 END IF; 276 277 RETURN one; 278 END; 279 // 280 281 CREATE OR REPLACE PROCEDURE Polls_checker_before ( 282 Title tinytext, 283 State ENUM('Waiting','Active','Terminated'), 284 Start datetime, 285 Salt int unsigned, 286 ShortURL varchar(32), 287 NbChoices tinyint unsigned, 288 MinNbRounds smallint unsigned, 289 MaxNbRounds smallint unsigned, 290 Deadline datetime, 291 MaxRoundDuration time, 292 RoundThreshold double unsigned , 293 CurrentRound tinyint unsigned, 294 CurrentRoundStart timestamp 295 ) 296 BEGIN 297 IF length(Title) < 3 THEN 298 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Title field is too short'; 299 END IF; 300 IF State = '' THEN 301 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Unauthorized State value'; 302 END IF; 303 IF Start IS NULL AND State = 'Waiting' THEN 304 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'A Start date must be given for Waiting polls'; 305 END IF; 306 IF Salt >= 4194304 THEN 307 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salt must be 22 bits long'; 308 END IF; 309 IF ShortURL IS NOT NULL AND ShortURL NOT RLIKE '^[-_.~a-zA-Z0-9]+$' THEN 310 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'ShortURL must be a valid URI segment'; 311 END IF; 312 IF NbChoices < 2 THEN 313 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'NbChoices must be at least 2'; 314 END IF; 315 IF MaxNbRounds < MinNbRounds THEN 316 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'MaxNbRounds must be greater than MinNbRounds'; 317 END IF; 318 IF MaxNbRounds IS NULL AND Deadline IS NULL THEN 319 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'One amongst MaxNbRounds and Deadline must not be NULL'; 320 END IF; 321 IF MaxRoundDuration < '00:01:00' THEN 322 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'MaxRoundDuration must be at least one minute'; 323 END IF; 324 IF RoundThreshold < 0 OR RoundThreshold > 1 THEN 325 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'RoundThreshold must be in [0;1]'; 326 END IF; 327 IF CurrentRound > MaxNbRounds THEN 328 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'CurrentRound must be at most MaxNbRounds'; 329 END IF; 330 END; 331 // 332 333 CREATE OR REPLACE TRIGGER Polls_check_before_insert 334 BEFORE INSERT ON Polls FOR EACH ROW 335 BEGIN 336 IF NEW.State != 'Waiting' AND NEW.State != 'Active' THEN 337 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'State must be Waiting or Active on INSERT'; 338 END IF; 339 IF NEW.CurrentRound != 0 THEN 340 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'CurrentRound must be zero on INSERT'; 341 END IF; 342 343 SELECT Name 344 INTO @Name 345 FROM Users 346 WHERE Id = NEW.Admin; 347 IF @Name IS NULL THEN 348 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Admin must correspond to a named user'; 349 END IF; 350 351 SET NEW.Created = CURRENT_TIMESTAMP(); 352 SET NEW.CurrentRoundStart = CURRENT_TIMESTAMP(); 353 354 CALL Polls_checker_before(NEW.Title, NEW.State, NEW.Start, NEW.Salt, NEW.ShortURL, NEW.NbChoices, 355 NEW.MinNbRounds, NEW.MaxNbRounds, NEW.Deadline, NEW.MaxRoundDuration, 356 NEW.RoundThreshold, NEW.CurrentRound, NEW.CurrentRoundStart); 357 END; 358 // 359 360 CREATE OR REPLACE TRIGGER Polls_check_before_update 361 BEFORE UPDATE ON Polls FOR EACH ROW 362 BEGIN 363 IF NEW.Created != OLD.Created THEN 364 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Created cannot be changed'; 365 END IF; 366 IF NEW.Admin != OLD.Admin THEN 367 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Admin cannot be changed'; 368 END IF; 369 370 IF NEW.CurrentRound > OLD.CurrentRound THEN 371 SET NEW.CurrentRoundStart = CURRENT_TIMESTAMP(); 372 END IF; 373 IF OLD.State = 'Waiting' AND NEW.State = 'Active' THEN 374 SET NEW.CurrentRound = 0; 375 SET NEW.CurrentRoundStart = CURRENT_TIMESTAMP(); 376 END IF; 377 IF NEW.State = 'Terminated' THEN 378 SET NEW.ShortURL = NULL; 379 END IF; 380 381 CALL Polls_checker_before(NEW.Title, NEW.State, NEW.Start, NEW.Salt, NEW.ShortURL, NEW.NbChoices, 382 NEW.MinNbRounds, NEW.MaxNbRounds, NEW.Deadline, NEW.MaxRoundDuration, 383 NEW.RoundThreshold, NEW.CurrentRound, NEW.CurrentRoundStart); 384 END; 385 // 386 387 # Manual ON DELETE CASCADE because we want Ballots to be automatically deleted when the poll is 388 # deleted, but we don't want to allow deletion of an alternatives when someone voted for it. 389 CREATE TRIGGER Polls_cascade_delete_before 390 BEFORE DELETE ON Polls FOR EACH ROW 391 BEGIN 392 DELETE FROM Participants wHERE Poll = OLD.id; 393 END; 394 // 395 396 DELIMITER ; 397 398 399 ######## Alternatives ######## 400 401 CREATE TABLE Alternatives ( 402 403 # Beware that the primary key is (Poll, Id), not Id alone. 404 Poll int unsigned NOT NULL, # FK on Polls 405 Id tinyint unsigned NOT NULL, 406 Name varchar(128) NOT NULL, 407 Cost decimal(65,6) NOT NULL DEFAULT 1, 408 409 CONSTRAINT Alternatives_pk PRIMARY KEY (Poll, Id), 410 CONSTRAINT Alternatives_PollName_unique UNIQUE (Poll, Name), 411 412 CONSTRAINT Alternatives_Poll_fk FOREIGN KEY (Poll) REFERENCES Polls (Id) ON DELETE CASCADE 413 414 ) ENGINE = InnoDB; 415 416 DELIMITER // 417 418 CREATE PROCEDURE Alternatives_checker_before ( 419 Poll int unsigned , 420 Id tinyint unsigned, 421 Name varchar(128) , 422 Cost decimal(65,6) 423 ) 424 BEGIN 425 426 IF length(Name) < 1 THEN 427 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Name cannot be empty'; 428 END IF; 429 430 SELECT p.NbChoices, p.MaxOutcomeCost, p.MaxBallotCost, p.BallotCostIsCount 431 INTO @NbChoices, @MaxBallotCost, @MaxBallotCost, @BallotCostIsCount 432 FROM Polls AS p 433 WHERE p.Id = Poll; 434 435 IF Id >= @NbChoices THEN 436 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Id must be less than NbChoices'; 437 END IF; 438 IF Cost > @MaxOutcomeCost THEN 439 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cost must be at most MaxOutcomeCost'; 440 END IF; 441 IF NOT @BallotCostIsCount AND Cost > @MaxBallotCost THEN 442 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cost must be at most MaxBallotCost'; 443 END IF; 444 445 END; 446 // 447 448 CREATE TRIGGER Alternatives_check_before_insert 449 BEFORE INSERT ON Alternatives FOR EACH ROW 450 BEGIN 451 CALL Alternatives_checker_before(NEW.Poll, NEW.Id, NEW.Name, NEW.Cost); 452 END; 453 // 454 455 CREATE TRIGGER Alternatives_check_before_update 456 BEFORE UPDATE ON Alternatives FOR EACH ROW 457 BEGIN 458 CALL Alternatives_checker_before(NEW.Poll, NEW.Id, NEW.Name, NEW.Cost); 459 END; 460 // 461 462 DELIMITER ; 463 464 465 ######## Participants ######## 466 467 CREATE TABLE Participants ( 468 469 User int unsigned NOT NULL, 470 Poll int unsigned NOT NULL, 471 Round tinyint unsigned NOT NULL, 472 473 CONSTRAINT Participants_pk PRIMARY KEY (User, Poll, Round), 474 475 CONSTRAINT Participants_User_fk FOREIGN KEY (User) REFERENCES Users (Id), 476 CONSTRAINT Participants_Poll_fk FOREIGN KEY (Poll) REFERENCES Polls (Id) ON DELETE CASCADE 477 478 ) ENGINE = InnoDB; 479 480 DELIMITER // 481 482 CREATE PROCEDURE Participants_checker_before ( 483 Poll int unsigned, 484 Round tinyint unsigned 485 ) 486 BEGIN 487 SELECT p.CurrentRound 488 INTO @CurrentRound 489 FROM Polls AS p 490 WHERE p.id = Poll; 491 492 IF Round > @CurrentRound THEN 493 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Round must be at most CurrentRound'; 494 END IF; 495 END; 496 // 497 498 CREATE TRIGGER Participants_check_before_insert 499 BEFORE INSERT ON Participants FOR EACH ROW 500 BEGIN 501 CALL Participants_checker_before(NEW.Poll, NEW.Round); 502 END; 503 // 504 505 CREATE TRIGGER Participants_check_before_update 506 BEFORE UPDATE ON Participants FOR EACH ROW 507 BEGIN 508 CALL Participants_checker_before(NEW.Poll, NEW.Round); 509 END; 510 // 511 512 DELIMITER ; 513 514 515 # MariaDB 5.5 does not allow subquery in view's queries (sic!). 516 # Therefore we need an intermediate view... 517 CREATE SQL SECURITY INVOKER VIEW Participants_workaround AS 518 SELECT Poll 519 FROM Participants 520 GROUP By Poll, User; 521 522 523 CREATE SQL SECURITY INVOKER VIEW Participants_Poll_Count AS 524 SELECT Poll, COUNT(*) AS Count 525 FROM Participants_workaround 526 GROUP BY Poll; 527 528 CREATE SQL SECURITY INVOKER VIEW Participants_Round_Count AS 529 SELECT Poll, Round, COUNT(*) AS Count 530 FROM Participants 531 GROUP BY Poll, Round; 532 533 534 535 ######## Ballots ######## 536 537 # Simple ranked ballot. 538 # Other tables for ballots may be added in the future. 539 CREATE TABLE Ballots ( 540 541 User int unsigned NOT NULL, 542 Poll int unsigned NOT NULL, 543 Alternative tinyint unsigned NULL, 544 Round tinyint unsigned NOT NULL, 545 Rank tinyint NOT NULL DEFAULT 1, 546 Modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 547 548 CONSTRAINT Ballots_pk PRIMARY KEY (User, Poll, Alternative, Round), 549 550 CONSTRAINT Ballots_Participant_fk FOREIGN KEY (User, Poll, Round) REFERENCES Participants (User, Poll, Round) ON DELETE CASCADE, 551 CONSTRAINT Ballots_Alternative_fk FOREIGN KEY (Poll, Alternative) REFERENCES Alternatives (Poll, Id) 552 553 ) ENGINE = InnoDB; 554 555 DELIMITER // 556 557 CREATE TRIGGER Ballots_check_before_insert 558 BEFORE INSERT ON Ballots FOR EACH ROW 559 BEGIN 560 SET NEW.Modified = CURRENT_TIMESTAMP(); 561 END; 562 // 563 564 CREATE TRIGGER Ballots_check_before_update 565 BEFORE UPDATE ON Ballots FOR EACH ROW 566 BEGIN 567 SET NEW.Modified = CURRENT_TIMESTAMP(); 568 END; 569 // 570 571 DELIMITER ;