Add unique indexes, by CREATE UNIQUE INDEX or by UNIQUE in a column of CREATE TABLE, that refuse an INSERT or UPDATE giving a row another row's value, compare values only, let NULLs repeat, and refuse to be built over values that already repeat.
The primary key already refuses a duplicate: the table's tree cannot hold two cells with one key. Many columns need the same promise without being the key: an email address, a username, an order number from another system. Checking for them with a full scan before every INSERT would turn each insert into a read of the whole table.
An index makes the check cheap. Equal values sit side by side in it, so "is this value already here?" is one seek. That is all a UNIQUE constraint is in this engine, and in most others: an index that refuses a second entry with the same value.
The entries of section 54 are built to be unique already: value, then table key. That is exactly why the unique check must not compare whole entries. ('a@x', 1) and ('a@x', 7) are different entries, and the tree would happily hold both. The constraint is about the value alone:
For an UPDATE, the one entry with the value that is allowed is the row's own: update u set email = 'a@x' where id = 1 on row 1 changes nothing and must not fail.
NULL means "unknown", and two unknowns are not known to be equal. SQL takes that literally here: a unique column may hold NULL in any number of rows. The NULL entries are still in the index (they sort first), the check just skips them.
The check is something only writing can find, like a duplicate primary key, so it runs after every other check: for an INSERT, the key (00701) first, then each unique index in the order they were created. When it fails, the statement fails as a whole, and the rules you already have take back anything it wrote. Inside a transaction, only that statement fails; the ones around it stay.
An UPDATE of many rows checks each row as it changes it. With unique values 1 and 2, update u set n = 2 changes row 1 first, finds row 2 already holding 2, and fails: 01605, and the statement changes nothing. Checking each row as it changes is also why, in engines whose SET takes expressions, set n = n + 1 over 1 and 2 can fail even though its final result would be fine. Some engines can defer the check to the end of the statement or the transaction; this one does not.
CREATE UNIQUE INDEX on a column whose values already repeat cannot succeed: the constraint would be false the moment it existed. Build the entries as for any index; equal values end up next to each other, so a pass over neighbours finds any repeat. Then the statement is 01606, and it leaves nothing behind: no index in indexes, no page allocated, the name still free.
Before writing the row, for each unique index of the table, in the order they were created.
Build it as any index, and look for neighbours that are equal.
Sugar for the statement above.
These are the exact lines the tests check. The full definition is on the output contract page, section 57.
| Input | Output |
|---|---|
create unique index by_email on u (email); | Create index statement executed successfully |
create table p (id int, email varchar(40) unique); | creates the table and the unique index p_email_key |
| an INSERT or UPDATE giving a row another row's value | [ERROR:01605] <message> |
| CREATE UNIQUE INDEX over values that repeat | [ERROR:01606] <message> |
indexes | [INDEX] by_email on u (email) root=<page> UNIQUE |
ast create unique index by_email on u (email); | Statement: CREATE UNIQUE INDEX, then Index:, Table:, Columns: |
ast of a CREATE TABLE with unique not null | email varchar(40) NOT NULL UNIQUE |
explain create unique index by_email on u (email); | [PLAN] └── CreateIndex (index=by_email, table=u, unique) |
unique is a keyword.
$droid --db people.dbWelcome to droid!>create table p (id int, email varchar(40) unique);Create table statement executed successfully>insert into p (id, email) values (1, 'a@x');Insert statement executed successfully (1 rows)>insert into p (id, email) values (2, 'a@x');[ERROR:01605] 'a@x' is already in p_email_key>insert into p (id, email) values (3, NULL);Insert statement executed successfully (1 rows)>insert into p (id, email) values (4, NULL);Insert statement executed successfully (1 rows)>indexes[INDEX] Indexes: 1[INDEX] p_email_key on p (email) root=3 UNIQUE>.exitexiting.. good bye!
The messages are yours to word; the tests check only the codes.
The page above is the same for every language. These are the only language-specific hints.
cmp and ignore the trailing key; a #[derive(Ord)] on the whole entry is the wrong order for this check.