Skip to content

Commit

Permalink
Generated/Virtual Columns: Insertable / Updateable
Browse files Browse the repository at this point in the history
Defines whether a column is included in an SQL INSERT and/or UPDATE statement.
Throws an exception for UPDATE statements attempting to update this field/column.

Closes doctrine#5728
  • Loading branch information
mehldau authored and greg0ire committed Jan 9, 2022
1 parent 0d911b9 commit 8b666a3
Show file tree
Hide file tree
Showing 29 changed files with 727 additions and 60 deletions.
18 changes: 18 additions & 0 deletions docs/en/reference/annotations-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ Optional attributes:

- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.

- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
If not specified, default value is true.

- **updatable**: Boolean value to determine if the column should be
included when updating the row of the underlying entities table.
If not specified, default value is true.

- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
used after an INSERT or UPDATE statement to determine if the database
generated this value and it needs to be fetched using a SELECT statement.

- **options**: Array of additional options:

- ``default``: The default value to set for the column if no value
Expand Down Expand Up @@ -193,6 +205,12 @@ Examples:
*/
protected $loginCount;
/**
* Generated column
* @Column(type="string", name="user_fullname", insertable=false, updatable=false)
*/
protected $fullname;
.. _annref_column_result:

@ColumnResult
Expand Down
12 changes: 12 additions & 0 deletions docs/en/reference/attributes-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ Optional parameters:
- **nullable**: Determines if NULL values allowed for this column.
If not specified, default value is ``false``.

- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
If not specified, default value is true.

- **updatable**: Boolean value to determine if the column should be
included when updating the row of the underlying entities table.
If not specified, default value is true.

- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
used after an INSERT or UPDATE statement to determine if the database
generated this value and it needs to be fetched using a SELECT statement.

- **options**: Array of additional options:

- ``default``: The default value to set for the column if no value
Expand Down
4 changes: 4 additions & 0 deletions docs/en/reference/basic-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ list:
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``insertable``: (optional, default TRUE) Whether the database
column should be inserted.
- ``updatable``: (optional, default TRUE) Whether the database
column should be updated.
- ``enumType``: (optional, requires PHP 8.1 and ORM 2.11) The PHP enum type
name to convert the database value into.
- ``precision``: (optional, default 0) The precision for a decimal
Expand Down
5 changes: 5 additions & 0 deletions docs/en/reference/xml-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ Optional attributes:
table? Defaults to false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- insertable - Should this field be inserted? Defaults to true.
- updatable - Should this field be updated? Defaults to true.
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
generated value must be fetched from database after INSERT or UPDATE.
Defaults to "NEVER".
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
Expand Down
13 changes: 13 additions & 0 deletions doctrine-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="generated-type">
<xs:restriction base="xs:token">
<xs:enumeration value="NEVER"/>
<xs:enumeration value="INSERT"/>
<xs:enumeration value="ALWAYS"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="field">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="options" type="orm:options" minOccurs="0" />
Expand All @@ -299,6 +307,9 @@
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="insertable" type="xs:boolean" default="true" />
<xs:attribute name="updatable" type="xs:boolean" default="true" />
<xs:attribute name="generated" type="orm:generated-type" default="NEVER" />
<xs:attribute name="enum-type" type="xs:string" />
<xs:attribute name="version" type="xs:boolean" />
<xs:attribute name="column-definition" type="xs:string" />
Expand Down Expand Up @@ -623,6 +634,8 @@
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="insertable" type="xs:boolean" default="true" />
<xs:attribute name="updateable" type="xs:boolean" default="true" />
<xs:attribute name="version" type="xs:boolean" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="precision" type="xs:integer" use="optional" />
Expand Down
12 changes: 10 additions & 2 deletions lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,16 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e
$data = $this->uow->getOriginalEntityData($entity);
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
if ($metadata->isVersioned) {
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
if ($metadata->requiresFetchAfterChange) {
if ($metadata->isVersioned) {
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
}

foreach ($metadata->fieldMappings as $name => $fieldMapping) {
if (isset($fieldMapping['generated'])) {
$data[$name] = $metadata->getFieldValue($entity, $name);
}
}
}

foreach ($metadata->associationMappings as $name => $assoc) {
Expand Down
28 changes: 28 additions & 0 deletions lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,34 @@ public function precision($p)
return $this;
}

/**
* Sets insertable.
*
* @return $this
*/
public function insertable(bool $flag = true)
{
if (! $flag) {
$this->mapping['notInsertable'] = true;
}

return $this;
}

/**
* Sets updatable.
*
* @return $this
*/
public function updatable(bool $flag = true)
{
if (! $flag) {
$this->mapping['notUpdatable'] = true;
}

return $this;
}

/**
* Sets scale.
*
Expand Down
61 changes: 58 additions & 3 deletions lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
* length?: int,
* id?: bool,
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* generated?: string,
* enumType?: class-string<BackedEnum>,
* columnDefinition?: string,
* precision?: int,
Expand Down Expand Up @@ -258,6 +261,21 @@ class ClassMetadataInfo implements ClassMetadata
*/
public const CACHE_USAGE_READ_WRITE = 3;

/**
* The value of this column is never generated by the database.
*/
public const GENERATED_NEVER = 0;

/**
* The value of this column is generated by the database on INSERT, but not on UPDATE.
*/
public const GENERATED_INSERT = 1;

/**
* The value of this column is generated by the database on both INSERT and UDPATE statements.
*/
public const GENERATED_ALWAYS = 2;

/**
* READ-ONLY: The name of the entity class.
*
Expand Down Expand Up @@ -439,6 +457,12 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>nullable</b> (boolean, optional)
* Whether the column is nullable. Defaults to FALSE.
*
* - <b>'notInsertable'</b> (boolean, optional)
* Whether the column is not insertable. Optional. Is only set if value is TRUE.
*
* - <b>'notUpdatable'</b> (boolean, optional)
* Whether the column is updatable. Optional. Is only set if value is TRUE.
*
* - <b>columnDefinition</b> (string, optional, schema-only)
* The SQL fragment that is used when generating the DDL for the column.
*
Expand Down Expand Up @@ -659,13 +683,21 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;

/**
* READ-ONLY: A Flag indicating whether one or more columns of this class
* have to be reloaded after insert / update operations.
*
* @var bool
*/
public $requiresFetchAfterChange = false;

/**
* READ-ONLY: A flag for whether or not instances of this class are to be versioned
* with optimistic locking.
*
* @var bool
*/
public $isVersioned;
public $isVersioned = false;

/**
* READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
Expand Down Expand Up @@ -963,6 +995,10 @@ public function __sleep()
$serialized[] = 'cache';
}

if ($this->requiresFetchAfterChange) {
$serialized[] = 'requiresFetchAfterChange';
}

return $serialized;
}

Expand Down Expand Up @@ -1609,6 +1645,16 @@ protected function validateAndCompleteFieldMapping(array $mapping): array
$mapping['requireSQLConversion'] = true;
}

if (isset($mapping['generated'])) {
if (! in_array($mapping['generated'], [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) {
throw MappingException::invalidGeneratedMode($mapping['generated']);
}

if ($mapping['generated'] === self::GENERATED_NEVER) {
unset($mapping['generated']);
}
}

if (isset($mapping['enumType'])) {
if (PHP_VERSION_ID < 80100) {
throw MappingException::enumsRequirePhp81($this->name, $mapping['fieldName']);
Expand Down Expand Up @@ -2673,6 +2719,10 @@ public function mapField(array $mapping)
$mapping = $this->validateAndCompleteFieldMapping($mapping);
$this->assertFieldNotMapped($mapping['fieldName']);

if (isset($mapping['generated'])) {
$this->requiresFetchAfterChange = true;
}

$this->fieldMappings[$mapping['fieldName']] = $mapping;
}

Expand Down Expand Up @@ -3403,8 +3453,9 @@ public function setSequenceGeneratorDefinition(array $definition)
*/
public function setVersionMapping(array &$mapping)
{
$this->isVersioned = true;
$this->versionField = $mapping['fieldName'];
$this->isVersioned = true;
$this->versionField = $mapping['fieldName'];
$this->requiresFetchAfterChange = true;

if (! isset($mapping['default'])) {
if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) {
Expand All @@ -3427,6 +3478,10 @@ public function setVersionMapping(array &$mapping)
public function setVersioned($bool)
{
$this->isVersioned = $bool;

if ($bool) {
$this->requiresFetchAfterChange = true;
}
}

/**
Expand Down
22 changes: 21 additions & 1 deletion lib/Doctrine/ORM/Mapping/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ final class Column implements Annotation
/** @var bool */
public $nullable = false;

/** @var bool */
public $insertable = true;

/** @var bool */
public $updatable = true;

/** @var class-string<\BackedEnum>|null */
public $enumType = null;

Expand All @@ -53,9 +59,17 @@ final class Column implements Annotation
/** @var string|null */
public $columnDefinition;

/**
* @var string|null
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
* @Enum({"NEVER", "INSERT", "ALWAYS"})
*/
public $generated;

/**
* @param class-string<\BackedEnum>|null $enumType
* @param array<string,mixed> $options
* @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated
*/
public function __construct(
?string $name = null,
Expand All @@ -65,9 +79,12 @@ public function __construct(
?int $scale = null,
bool $unique = false,
bool $nullable = false,
bool $insertable = true,
bool $updatable = true,
?string $enumType = null,
array $options = [],
?string $columnDefinition = null
?string $columnDefinition = null,
?string $generated = null
) {
$this->name = $name;
$this->type = $type;
Expand All @@ -76,8 +93,11 @@ public function __construct(
$this->scale = $scale;
$this->unique = $unique;
$this->nullable = $nullable;
$this->insertable = $insertable;
$this->updatable = $updatable;
$this->enumType = $enumType;
$this->options = $options;
$this->columnDefinition = $columnDefinition;
$this->generated = $generated;
}
}

0 comments on commit 8b666a3

Please sign in to comment.