1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-09 22:31:03 +01:00

Add unique constraint for Almanac network names

Summary:
The name of networks should be unique.

Also adds support for exact-name queries for AlamanacNetworks.

Test Plan: Applied migration with existing duplicates, saw networks renamed, attempted to add duplicates, got a nice error message.

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D19379
This commit is contained in:
Austin McKinley 2018-04-18 11:45:50 -07:00
parent a817aa6c71
commit 0a83f253ed
5 changed files with 115 additions and 17 deletions

View file

@ -0,0 +1,46 @@
<?php
$table = new AlmanacNetwork();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'LOCK TABLES %T WRITE',
$table->getTableName());
$seen = array();
foreach (new LiskMigrationIterator($table) as $network) {
$name = $network->getName();
// If this is the first copy of this row we've seen, mark it as seen and
// move on.
if (empty($seen[$name])) {
$seen[$name] = 1;
continue;
}
// Otherwise, rename this row.
while (true) {
$new_name = $name.'-'.$seen[$name];
if (empty($seen[$new_name])) {
$network->setName($new_name);
try {
$network->save();
break;
} catch (AphrontDuplicateKeyQueryException $ex) {
// New name is a dupe of a network we haven't seen yet.
}
}
$seen[$name]++;
}
$seen[$new_name] = 1;
}
queryfx(
$conn,
'ALTER TABLE %T ADD UNIQUE KEY `key_name` (name)',
$table->getTableName());
queryfx(
$conn,
'UNLOCK TABLES');

View file

@ -5,6 +5,7 @@ final class AlmanacNetworkQuery
private $ids; private $ids;
private $phids; private $phids;
private $names;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -20,6 +21,11 @@ final class AlmanacNetworkQuery
return new AlmanacNetwork(); return new AlmanacNetwork();
} }
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNameNgrams($ngrams) { public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint( return $this->withNgramsConstraint(
new AlmanacNetworkNameNgrams(), new AlmanacNetworkNameNgrams(),
@ -47,6 +53,13 @@ final class AlmanacNetworkQuery
$this->phids); $this->phids);
} }
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'network.name IN (%Ls)',
$this->names);
}
return $where; return $where;
} }

View file

@ -24,8 +24,15 @@ final class AlmanacNetwork
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text128', 'name' => 'sort128',
'mailKey' => 'bytes20', 'mailKey' => 'bytes20',
),
self::CONFIG_KEY_SCHEMA => array(
'key_name' => array(
'columns' => array('name'),
'unique' => true,
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -6,57 +6,58 @@ final class AlmanacNames extends Phobject {
if (strlen($name) < 3) { if (strlen($name) < 3) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names must be '. 'Almanac service, device, property, network and namespace names '.
'at least 3 characters long.')); 'must be at least 3 characters long.'));
} }
if (strlen($name) > 100) { if (strlen($name) > 100) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names may not '. 'Almanac service, device, property, network and namespace names '.
'be more than 100 characters long.')); 'may not be more than 100 characters long.'));
} }
if (!preg_match('/^[a-z0-9.-]+\z/', $name)) { if (!preg_match('/^[a-z0-9.-]+\z/', $name)) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names may only '. 'Almanac service, device, property, network and namespace names '.
'contain lowercase letters, numbers, hyphens, and periods.')); 'may only contain lowercase letters, numbers, hyphens, and '.
'periods.'));
} }
if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) { if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names may not '. 'Almanac service, device, network, property and namespace names '.
'have any segments containing only digits.')); 'may not have any segments containing only digits.'));
} }
if (preg_match('/\.\./', $name)) { if (preg_match('/\.\./', $name)) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names may not '. 'Almanac service, device, property, network and namespace names '.
'contain multiple consecutive periods.')); 'may not contain multiple consecutive periods.'));
} }
if (preg_match('/\\.-|-\\./', $name)) { if (preg_match('/\\.-|-\\./', $name)) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names may not '. 'Almanac service, device, property, network and namespace names '.
'contain hyphens adjacent to periods.')); 'may not contain hyphens adjacent to periods.'));
} }
if (preg_match('/--/', $name)) { if (preg_match('/--/', $name)) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names may not '. 'Almanac service, device, property, network and namespace names '.
'contain multiple consecutive hyphens.')); 'may not contain multiple consecutive hyphens.'));
} }
if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) { if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) {
throw new Exception( throw new Exception(
pht( pht(
'Almanac service, device, property and namespace names must begin '. 'Almanac service, device, property, network and namespace names '.
'and end with a letter or number.')); 'must begin and end with a letter or number.'));
} }
} }

View file

@ -38,6 +38,37 @@ final class AlmanacNetworkNameTransaction
pht('Network name is required.')); pht('Network name is required.'));
} }
foreach ($xactions as $xaction) {
$name = $xaction->getNewValue();
$message = null;
try {
AlmanacNames::validateName($name);
} catch (Exception $ex) {
$message = $ex->getMessage();
}
if ($message !== null) {
$errors[] = $this->newInvalidError($message, $xaction);
continue;
}
if ($name === $object->getName()) {
continue;
}
$other = id(new AlmanacNetworkQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array($name))
->executeOne();
if ($other && ($other->getID() != $object->getID())) {
$errors[] = $this->newInvalidError(
pht('Almanac networks must have unique names.'),
$xaction);
continue;
}
}
return $errors; return $errors;
} }