Skip to content

Commit

Permalink
Greatly extended model indexing functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
martywallace committed Nov 30, 2017
1 parent e55fe9e commit 2e81650
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/Tempest/Database/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public function setDefault($value) {
}

/**
* Adds a primary key index to this field.
* Adds a primary key index to this field and {@link setNotNullable marks it non-nullable}.
*
* @return $this
*/
Expand Down
46 changes: 42 additions & 4 deletions src/Tempest/Database/Index.php
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
<?php namespace Tempest\Database;

use JsonSerializable;

/**
* An index on a field.
*
* @author Marty Wallace
*/
class Index {
class Index implements JsonSerializable {

const PRIMARY = 'primary';
const UNIQUE = 'unique';
const INDEX = 'index';

/** @var SealedField[] */
private $_fields;

/** @var string */
private $_type;

/** @var string */
private $_name;

/**
* Index constructor.
*
* @param string $type The index type.
* @param SealedField[] $fields The field this index was declared by.
* @param string $name The index name, useful for constructing composite indexes.
*/
public function __construct($type = self::INDEX, $name = null) {
public function __construct($type = self::INDEX, array $fields = [], $name = null) {
$this->_type = $type;
$this->_fields = $fields;
$this->_name = $name;
}

/**
* @return SealedField[]
*/
public function getFields() {
return $this->_fields;
}

/**
* @return string
*/
Expand All @@ -42,4 +54,30 @@ public function getName() {
return $this->_name;
}

/**
* Create a copy of this index.
*
* @return Index
*/
public function copy() {
return new static($this->getType(), $this->getFields(), $this->getName());
}

/**
* Merge this index with another index of the same name.
*
* @param Index $index The index to merge with.
*/
public function merge(Index $index) {
$this->_fields = array_merge($this->_fields, $index->getFields());
}

public function jsonSerialize() {
return [
'type' => $this->_type,
'name' => $this->_name,
'fields' => $this->_fields
];
}

}
130 changes: 127 additions & 3 deletions src/Tempest/Database/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ abstract class Model extends EventDispatcher implements JsonSerializable {
/** @var SealedField[] */
protected static $_fields = null;

/** @var Index[] */
protected static $_indexes = null;

/**
* Get the table name associated with this model.
*
Expand Down Expand Up @@ -96,15 +99,125 @@ public static function getField($field) {
return static::hasField($field) ? static::getFields()[$field] : null;
}

/**
* Get all indexes attached to this model, as declared by its {@link fields}. This does not return a flat tree of
* indexes as defined by each field, but will merge indexes with matching names and a list of all the associated
* fields.
*
* @return Index[]
*
* @throws Exception If the index tree could not be properly merged.
*/
public static function getIndexes() {
if (empty(static::$_indexes)) {
/** @var Index[] $indexes */
$indexes = [];

foreach (static::getFields() as $field) {
foreach ($field->getIndexes() as $index) {
if (!empty($index->getName()) || $index->getType() === Index::PRIMARY) {
foreach ($indexes as $known) {
// Merge keys that are both PRIMARY or whose names match.
$shouldMerge = $known->getName() === $index->getName()
|| ($known->getType() === Index::PRIMARY && $index->getType() === Index::PRIMARY);

if ($shouldMerge) {
if ($known->getType() !== $index->getType()) {
throw new Exception('Indexes for "' . static::class . '" sharing a name must also share their type.');
}

$known->merge($index);

continue 2;
}
}

$indexes[] = $index->copy();
} else {
$indexes[] = $index->copy();
}
}
}

static::$_indexes = $indexes;
}

return static::$_indexes;
}

/**
* Retrieve an index using its name.
*
* @param string $name The index name as declared by this model.
*
* @return Index
*/
public static function getIndexByName($name) {
foreach (static::getIndexes() as $index) {
if ($index->getName() === $name) {
return $index;
}
}

return null;
}

/**
* Gets the PRIMARY index if this model declares one.
*
* @return Index
*/
public static function getPrimaryIndex() {
foreach (static::getIndexes() as $index) {
if ($index->getType() === Index::PRIMARY) {
return $index;
}
}

return null;
}

/**
* Gets all PRIMARY or UNIQUE indexes declared by this model.
*
* @return Index[]
*/
public static function getUniqueIndexes() {
return array_filter(static::getIndexes(), function(Index $index) {
return $index->getType() === Index::PRIMARY
|| $index->getType() === INDEX::UNIQUE;
});
}

/**
* Get all INDEX (non PRIMARY or UNIQUE) indexes declared by this model.
*
* @return Index[]
*/
public static function getNonUniqueIndexes() {
return array_filter(static::getIndexes(), function(Index $index) {
return $index->getType() === Index::INDEX;
});
}

/**
* Get all non PRIMARY indexes declared by this model.
*
* @return Index[]
*/
public static function getNonPrimaryIndexes() {
return array_filter(static::getIndexes(), function(Index $index) {
return $index->getType() !== Index::PRIMARY;
});
}

/**
* Retrieve all fields that are PRIMARY keyed.
*
* @return SealedField[]
*/
public static function getPrimaryFields() {
return array_filter(static::getFields(), function(SealedField $field) {
return $field->hasPrimaryIndex();
});
return static::getPrimaryIndex()->getFields();
}

/**
Expand All @@ -129,6 +242,17 @@ public static function getNonUniqueFields() {
});
}

/**
* Retrieve all fields that are not PRIMARY keyed.
*
* @return SealedField[]
*/
public static function getNonPrimaryFields() {
return array_filter(static::getFields(), function(SealedField $field) {
return !$field->hasPrimaryIndex();
});
}

/**
* Retrieve the auto-incrementing field, if this model declared one.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Tempest/Database/Models/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected static function fields() {
'id' => Field::string()->setAutoIncrements(),
'created' => Field::dateTime()->setDefault('now'),
'updated' => Field::dateTime()->setDefault('now'),
'ip' => Field::string(),
'ip' => Field::string()->addIndex(),
'data' => Field::text()->setNotNullable()
];
}
Expand Down
24 changes: 20 additions & 4 deletions src/Tempest/Database/SealedField.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?php namespace Tempest\Database;

use JsonSerializable;
use Carbon\Carbon;

/**
* A real-only field declaration.
*
* @author Marty Wallace
*/
class SealedField {
class SealedField implements JsonSerializable {

/** @var string */
private $_name;
Expand Down Expand Up @@ -35,8 +37,12 @@ protected function __construct($name, Field $field) {
$this->_autoIncrements = $field->getAutoIncrements();
$this->_default = $field->getDefault();
$this->_nullable = $field->getNullable();
$this->_indexes = $field->getIndexes();
$this->_enumerable = $field->getEnumerable();

foreach ($field->getIndexes() as $index) {
// Clone indexes with new sealed field reference.
$this->_indexes[] = new Index($index->getType(), [$this], $index->getName());
}
}

/**
Expand Down Expand Up @@ -294,11 +300,11 @@ public function getIndexes() {
*/
protected function addIndexInternally($type, $name = null) {
if ($type === Index::PRIMARY && $this->hasPrimaryIndex()) {
// Only one primary key.
// Only one primary key allowed - ignore repeat attempts.
return $this;
}

$this->_indexes[] = new Index($type, $name);
$this->_indexes[] = new Index($type, [$this], $name);

return $this;
}
Expand Down Expand Up @@ -400,4 +406,14 @@ public function getColumnType() {
}
}

public function jsonSerialize() {
return [
'name' => $this->_name,
'type' => $this->_type,
'autoIncrements' => $this->_autoIncrements,
'default' => $this->_default,
'nullable' => $this->_nullable
];
}

}

0 comments on commit 2e81650

Please sign in to comment.