diff --git a/README.md b/README.md
index 53721ba0417dbf2c3ed616f3b14717c2496f8034..8619251b59d0278d39f06e82fa0fd5ee9260f67e 100644
--- a/README.md
+++ b/README.md
@@ -292,16 +292,16 @@ $changeSet = $table->new(
 );
 ```
 
-The method returns a change set which you can further modify, and eventually save:
+The method returns a change set which you can further modify, and eventually insert:
 
 ```php
 $changeSet->modifyText('Post text');
-$table->save($changeSet);
+$table->insert($changeSet);
 ```
 
 ### Update
 
-To update a record in the table, first you need to get an instance of change set for the specific record. You can get one for any given primary key or row:
+To update a record in the table, you need to get an instance of change set for the specific record. You can get one for any given primary key or row:
 
 ```php
 $changeSet = $table->edit(ArticlePrimaryKey::from($articleId));
@@ -309,11 +309,20 @@ $changeSet = $table->edit(ArticlePrimaryKey::from($articleId));
 $changeSet = $table->edit($articleRow);
 ```
 
-Then you can add modifications to the change set and finally save it:
+You can use named parameters to provide the values to update right within the method call:
+
+```php
+$changeSet = $table->edit(
+    $articleRow,
+    deletedAt: \Brick\DateTime\Instant::now(),
+);
+```
+
+As before, you can also add modifications to the change set afterward, and finally save it:
 
 ```php
 $changeSet->modifyDeletedAt(\Brick\DateTime\Instant::now());
-$table->save($changeSet);
+$table->update($changeSet);
 ```
 
 ### Delete
diff --git a/composer.json b/composer.json
index e09500511f7ce8b5a501feabbbacdb528b7bf9cf..6f141889c4538810117757663bfffbeed3b08d89 100644
--- a/composer.json
+++ b/composer.json
@@ -53,6 +53,7 @@
 			"src/exceptions.php"
 		],
 		"files": [
+			"src/constants.php",
 			"src/functions.php",
 			"src/Conditions/functions.php",
 			"src/Types/functions.php"
diff --git a/src/DefaultOrExistingValue.php b/src/DefaultOrExistingValue.php
new file mode 100644
index 0000000000000000000000000000000000000000..fe4bf0e113b0769c93018305299533a06c2862d8
--- /dev/null
+++ b/src/DefaultOrExistingValue.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Grifart\Tables;
+
+/**
+ * @internal
+ */
+final class DefaultOrExistingValue
+{
+}
diff --git a/src/Scaffolding/TableImplementation.php b/src/Scaffolding/TableImplementation.php
index b91e25326527f6818fd218f7dddd94581cfd1f70..cebcfa562b809e2edb0d5f535ded03434186d8cc 100644
--- a/src/Scaffolding/TableImplementation.php
+++ b/src/Scaffolding/TableImplementation.php
@@ -8,14 +8,18 @@ use Grifart\ClassScaffolder\Capabilities\Capability;
 use Grifart\ClassScaffolder\ClassInNamespace;
 use Grifart\ClassScaffolder\Definition\ClassDefinition;
 use Grifart\ClassScaffolder\Definition\Types\Type as PhpType;
+use Grifart\ClassScaffolder\Definition\Types\UnionType;
 use Grifart\Tables\CaseConversion;
 use Grifart\Tables\Column;
 use Grifart\Tables\ColumnMetadata;
 use Grifart\Tables\ColumnNotFound;
 use Grifart\Tables\Conditions\Condition;
+use Grifart\Tables\DefaultOrExistingValue;
 use Grifart\Tables\Expression;
+use Grifart\Tables\GivenSearchCriteriaHaveNotMatchedAnyRows;
 use Grifart\Tables\RowNotFound;
 use Grifart\Tables\OrderBy\OrderBy;
+use Grifart\Tables\RowWithGivenPrimaryKeyAlreadyExists;
 use Grifart\Tables\Table;
 use Grifart\Tables\TableManager;
 use Grifart\Tables\TooManyRowsFound;
@@ -23,6 +27,9 @@ use Grifart\Tables\Type;
 use Grifart\Tables\TypeResolver;
 use Nette\PhpGenerator as Code;
 use Nette\Utils\Paginator;
+use function Functional\map;
+use function Grifart\ClassScaffolder\Definition\Types\resolve;
+use function usort;
 
 final class TableImplementation implements Capability
 {
@@ -63,7 +70,7 @@ final class TableImplementation implements Capability
 
 		// column info:
 		$namespace->addUse(ColumnMetadata::class);
-		$columnsDefinitions = []; // name => PhpLiteral
+		$columnsDefinitions = []; // name => Literal
 		$columnsArrayTemplate = [];
 		foreach($this->columnMetadata as $column) {
 			$columnsArrayTemplate[] = "\t? => new ColumnMetadata(?, ?, ?, ?)";
@@ -96,7 +103,7 @@ final class TableImplementation implements Capability
 				'$row = $this->tableManager->find($this, $primaryKey);' . "\n" .
 				'\assert($row instanceof ? || $row === NULL);' . "\n" .
 				'return $row;',
-				[new Code\PhpLiteral($namespace->simplifyName($this->rowClass))]
+				[new Code\Literal($namespace->simplifyName($this->rowClass))]
 			);
 
 		$namespace->addUse(RowNotFound::class);
@@ -129,7 +136,7 @@ final class TableImplementation implements Capability
 				"/** @var ?[] \$result */\n" .
 				"\$result = \$this->tableManager->getAll(\$this, \$orderBy, \$paginator);\n" .
 				'return $result;',
-				[new Code\PhpLiteral($namespace->simplifyName($this->rowClass))],
+				[new Code\Literal($namespace->simplifyName($this->rowClass))],
 			);
 
 		$namespace->addUse(Condition::class);
@@ -145,7 +152,7 @@ final class TableImplementation implements Capability
 			->addComment('@param array<OrderBy|Expression<mixed>> $orderBy')
 			->addComment('@return ' . $namespace->simplifyName($this->rowClass) . '[]')
 			->setReturnType('array')
-			->addBody('/** @var ?[] $result */', [new Code\PhpLiteral($namespace->simplifyName($this->rowClass))])
+			->addBody('/** @var ?[] $result */', [new Code\Literal($namespace->simplifyName($this->rowClass))])
 			->addBody('$result = $this->tableManager->findBy($this, $conditions, $orderBy, $paginator);')
 			->addBody('return $result;');
 
@@ -165,65 +172,117 @@ final class TableImplementation implements Capability
 			->addBody('return $result[0];');
 
 
-		$classType->addMethod('newEmpty')
-			->setReturnType($this->modificationClass)
-			->setBody(
-				'return ?::new();',
-				[new Code\PhpLiteral($namespace->simplifyName($this->modificationClass))],
-			);
-
 		$newMethod = $classType->addMethod('new')
 			->setReturnType($this->modificationClass)
 			->addBody(
 				'$modifications = ?::new();',
-				[new Code\PhpLiteral($namespace->simplifyName($this->modificationClass))],
+				[new Code\Literal($namespace->simplifyName($this->modificationClass))],
 			);
 
-		foreach ($this->columnMetadata as $columnMetadata) {
-			if ( ! $columnMetadata->hasDefaultValue()) {
+		$editMethod = $classType->addMethod('edit')
+			->setReturnType($this->modificationClass)
+			->setParameters([
+				(new Code\Parameter('rowOrKey'))->setType($this->rowClass . '|' . $this->primaryKeyClass),
+			])
+			->addBody('$primaryKey = $rowOrKey instanceof ? \? $rowOrKey : ?::fromRow($rowOrKey);', [
+				new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
+				new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
+			])
+			->addBody('$modifications = ?::update($primaryKey);', [new Code\Literal($namespace->simplifyName($this->modificationClass))]);
+
+		$columns = $this->columnMetadata;
+		usort($columns, fn (ColumnMetadata $a, ColumnMetadata $b) => $a->hasDefaultValue() <=> $b->hasDefaultValue());
+
+		foreach ([$newMethod, $editMethod] as $method) {
+			foreach ($columns as $columnMetadata) {
+				$isEditMethod = $method === $editMethod;
+				$hasDefaultValue = $columnMetadata->hasDefaultValue() || $isEditMethod;
+
 				$fieldName = $columnMetadata->getName();
 				$fieldType = $this->columnPhpTypes[$fieldName];
+				$isNullable = $fieldType->isNullable();
+
+				if ($hasDefaultValue) {
+					$namespace->addUse(DefaultOrExistingValue::class);
+					$fieldType = new UnionType($fieldType, resolve(DefaultOrExistingValue::class));
+				}
 
-				$newMethod->addParameter($fieldName)
+
+				$parameter = $method->addParameter($fieldName)
 					->setType($fieldType->getTypeHint())
-					->setNullable($fieldType->isNullable());
+					->setNullable($isNullable);
+
+				if ($hasDefaultValue) {
+					$parameter->setDefaultValue(
+						new Code\Literal(
+							$namespace->simplifyName(
+								$isEditMethod ? 'Grifart\Tables\Unchanged' : 'Grifart\Tables\DefaultValue',
+								$namespace::NameConstant,
+							),
+						),
+					);
+				}
 
 				if ($fieldType->requiresDocComment()) {
-					$newMethod->addComment(\sprintf(
+					$method->addComment(\sprintf(
 						'@param %s $%s',
 						$fieldType->getDocCommentType($namespace),
 						$fieldName,
 					));
 				}
 
-				$newMethod->addBody(
-					'$modifications->modify' . \ucfirst($fieldName) . '(?);',
-					[new Code\PhpLiteral('$' . $fieldName)],
+				if ($hasDefaultValue) {
+					$method->addBody(
+						'if (!? instanceof ?) {',
+						[new Code\Literal('$' . $fieldName), new Code\Literal($namespace->simplifyName(DefaultOrExistingValue::class))],
+					);
+				}
+
+				$method->addBody(
+					($hasDefaultValue ? "\t" : '') . '$modifications->modify' . \ucfirst($fieldName) . '(?);',
+					[new Code\Literal('$' . $fieldName)],
 				);
+
+				if ($hasDefaultValue) {
+					$method->addBody('}');
+				}
 			}
-		}
 
-		$newMethod->addBody('return $modifications;');
+			$method->addBody('return $modifications;');
+		}
 
+		$namespace->addUse(RowWithGivenPrimaryKeyAlreadyExists::class);
+		$namespace->addUse(GivenSearchCriteriaHaveNotMatchedAnyRows::class);
 
-		$classType->addMethod('edit')
-			->setReturnType($this->modificationClass)
+		$classType->addMethod('save')
+			->addComment('@throws RowWithGivenPrimaryKeyAlreadyExists')
+			->addComment('@throws GivenSearchCriteriaHaveNotMatchedAnyRows')
+			->setReturnType('void')
 			->setParameters([
-				(new Code\Parameter('rowOrKey'))->setType($this->rowClass . '|' . $this->primaryKeyClass),
+				(new Code\Parameter('changes'))->setType($this->modificationClass)
 			])
-			->addBody('$primaryKey = $rowOrKey instanceof ? \? $rowOrKey : ?::fromRow($rowOrKey);', [
-				new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
-				new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
+			->setBody(
+				'$this->tableManager->save($this, $changes);'
+			);
+
+		$classType->addMethod('insert')
+			->addComment('@throws RowWithGivenPrimaryKeyAlreadyExists')
+			->setReturnType('void')
+			->setParameters([
+				(new Code\Parameter('changes'))->setType($this->modificationClass),
 			])
-			->addBody('return ?::update($primaryKey);', [new Code\PhpLiteral($namespace->simplifyName($this->modificationClass))]);
+			->setBody(
+				'$this->tableManager->insert($this, $changes);',
+			);
 
-		$classType->addMethod('save')
+		$classType->addMethod('update')
+			->addComment('@throws GivenSearchCriteriaHaveNotMatchedAnyRows')
 			->setReturnType('void')
 			->setParameters([
-				(new Code\Parameter('changes'))->setType($this->modificationClass)
+				(new Code\Parameter('changes'))->setType($this->modificationClass),
 			])
 			->setBody(
-				'$this->tableManager->save($this, $changes);'
+				'$this->tableManager->update($this, $changes);',
 			);
 
 		$classType->addMethod('delete')
@@ -232,8 +291,8 @@ final class TableImplementation implements Capability
 				(new Code\Parameter('rowOrKey'))->setType($this->rowClass . '|' . $this->primaryKeyClass)
 			])
 			->addBody('$primaryKey = $rowOrKey instanceof ? \? $rowOrKey : ?::fromRow($rowOrKey);', [
-				new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
-				new Code\PhpLiteral($namespace->simplifyName($this->primaryKeyClass)),
+				new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
+				new Code\Literal($namespace->simplifyName($this->primaryKeyClass)),
 			])
 			->addBody('$this->tableManager->delete($this, $primaryKey);');
 
@@ -273,7 +332,7 @@ final class TableImplementation implements Capability
 
 			$constructor->addBody(\sprintf('/** @var Column<self, %s> $%s */', $docCommentType, $columnName));
 			$constructor->addBody('$? = Column::from($this, self::getDatabaseColumns()[?], $this->typeResolver);', [$columnName, $columnName]);
-			$columnInitializers[$columnName] = new Code\PhpLiteral('$?', [$columnName]);
+			$columnInitializers[$columnName] = new Code\Literal('$?', [$columnName]);
 		}
 
 		$columnsProperty->addComment(\sprintf('@var array{%s}', \implode(', ', $columnsShape)));
@@ -297,7 +356,7 @@ final class TableImplementation implements Capability
 	private function implementConfigMethodReturningClass(Code\PhpNamespace $namespace, Code\ClassType $classType, string $name, string $class): void
 	{
 		$namespace->addUse($class);
-		$this->implementConfigMethod($classType, $name, new Code\PhpLiteral($namespace->simplifyName($class) . '::class'));
+		$this->implementConfigMethod($classType, $name, new Code\Literal($namespace->simplifyName($class) . '::class'));
 	}
 
 	private function implementConfigMethod(Code\ClassType $classType, string $name, mixed $value): void
diff --git a/src/Scaffolding/TablesDefinitions.php b/src/Scaffolding/TablesDefinitions.php
index 652051ace024fb2c8b1c229995ef3d6d8839bb93..06f31a212b1aa0a33d5911828a339693b056640d 100644
--- a/src/Scaffolding/TablesDefinitions.php
+++ b/src/Scaffolding/TablesDefinitions.php
@@ -11,6 +11,7 @@ use Grifart\Tables\Database\Identifier;
 use Grifart\Tables\Row;
 use Grifart\Tables\Type;
 use Grifart\Tables\TypeResolver;
+use function array_keys;
 use function Functional\map;
 use function Grifart\ClassScaffolder\Capabilities\constructorWithPromotedProperties;
 use function Grifart\ClassScaffolder\Capabilities\getters;
diff --git a/src/TableManager.php b/src/TableManager.php
index 75bd5ea5d3d0be8e3e25ff639ab0d9061214eadf..0915cf0e9f46c01fc1d36dfbe0da942c15bc16ad 100644
--- a/src/TableManager.php
+++ b/src/TableManager.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Grifart\Tables;
 
 use Dibi\Connection;
+use Dibi\UniqueConstraintViolationException;
 use Grifart\Tables\Conditions\Composite;
 use Grifart\Tables\Conditions\Condition;
 use Grifart\Tables\OrderBy\OrderBy;
@@ -26,18 +27,25 @@ final class TableManager
 	 * @template TableType of Table
 	 * @param TableType $table
 	 * @param Modifications<TableType> $changes
+	 * @throws RowWithGivenPrimaryKeyAlreadyExists
 	 */
 	public function insert(Table $table, Modifications $changes): void
 	{
 		\assert($changes->getPrimaryKey() === NULL);
-		$this->connection->query(
-			'INSERT',
-			'INTO %n.%n', $table::getSchema(), $table::getTableName(),
-			map(
-				$changes->getModifications(),
-				static fn(mixed $value, string $columnName) => $value !== null ? $table->getTypeOf($columnName)->toDatabase($value) : null,
-			),
-		);
+
+		try {
+			$this->connection->query(
+				'INSERT',
+				'INTO %n.%n', $table::getSchema(), $table::getTableName(),
+				map(
+					$changes->getModifications(),
+					static fn(mixed $value, string $columnName) => $value !== null ? $table->getTypeOf($columnName)->toDatabase($value) : null,
+				),
+			);
+		} catch (UniqueConstraintViolationException $e) {
+			throw new RowWithGivenPrimaryKeyAlreadyExists(previous: $e);
+		}
+
 		\assert($this->connection->getAffectedRows() === 1);
 	}
 
@@ -177,6 +185,7 @@ final class TableManager
 	 * @template TableType of Table
 	 * @param TableType $table
 	 * @param Modifications<TableType> $changes
+	 * @throws RowWithGivenPrimaryKeyAlreadyExists
 	 * @throws GivenSearchCriteriaHaveNotMatchedAnyRows
 	 */
 	public function save(Table $table, Modifications $changes): void {
diff --git a/src/constants.php b/src/constants.php
new file mode 100644
index 0000000000000000000000000000000000000000..93fb528fc90e99a29139dc64402d36e7f35be52b
--- /dev/null
+++ b/src/constants.php
@@ -0,0 +1,8 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Grifart\Tables;
+
+const DefaultValue = new DefaultOrExistingValue();
+const Unchanged = new DefaultOrExistingValue();
diff --git a/src/exceptions.php b/src/exceptions.php
index b209bea166059deed76cce8d4aa037e849cf68c0..696803a932472af5b25cc2f92faaaf0e3ed6a31b 100644
--- a/src/exceptions.php
+++ b/src/exceptions.php
@@ -66,6 +66,6 @@ final class ColumnNotFound extends UsageException {
 }
 
 final class GivenSearchCriteriaHaveNotMatchedAnyRows extends RuntimeException {}
-
+final class RowWithGivenPrimaryKeyAlreadyExists extends RuntimeException {}
 final class RowNotFound extends RuntimeException {}
 final class TooManyRowsFound extends UsageException {}
diff --git a/tests/CompositeTypeIntegrationTest.php b/tests/CompositeTypeIntegrationTest.php
index 2939933c92633c63549057ffc3783697e239feda..c8c5a59e519a11d84cb147e63ea2dffaeb149cf8 100644
--- a/tests/CompositeTypeIntegrationTest.php
+++ b/tests/CompositeTypeIntegrationTest.php
@@ -20,8 +20,9 @@ $table = new PackagesTable(
 	TestFixtures::createTypeResolver($connection),
 );
 
-$package = $table->new('grifart/tables', [0, 8, 0], [new Version(0, 7, 0)]);
-$table->save($package);
+$table->insert(
+	$table->new('grifart/tables', [0, 8, 0], [new Version(0, 7, 0)]),
+);
 
 $byVersion = $table->findBy([
 	$table->version()->is([0, 8, 0]),
diff --git a/tests/Fixtures/PackagesTable.php b/tests/Fixtures/PackagesTable.php
index a0db34901e80a7acdc9c7121a61dce92e31e4fbc..bc570ff8a946660e37adccb004db2d7c55ab9108 100644
--- a/tests/Fixtures/PackagesTable.php
+++ b/tests/Fixtures/PackagesTable.php
@@ -12,9 +12,12 @@ use Grifart\Tables\Column;
 use Grifart\Tables\ColumnMetadata;
 use Grifart\Tables\ColumnNotFound;
 use Grifart\Tables\Conditions\Condition;
+use Grifart\Tables\DefaultOrExistingValue;
 use Grifart\Tables\Expression;
+use Grifart\Tables\GivenSearchCriteriaHaveNotMatchedAnyRows;
 use Grifart\Tables\OrderBy\OrderBy;
 use Grifart\Tables\RowNotFound;
+use Grifart\Tables\RowWithGivenPrimaryKeyAlreadyExists;
 use Grifart\Tables\Table;
 use Grifart\Tables\TableManager;
 use Grifart\Tables\TooManyRowsFound;
@@ -135,12 +138,6 @@ final class PackagesTable implements Table
 	}
 
 
-	public function newEmpty(): PackageModifications
-	{
-		return PackageModifications::new();
-	}
-
-
 	/**
 	 * @param array{int, int, int} $version
 	 * @param Version[] $previousVersions
@@ -155,19 +152,60 @@ final class PackagesTable implements Table
 	}
 
 
-	public function edit(PackageRow|PackagePrimaryKey $rowOrKey): PackageModifications
+	/**
+	 * @param array{int, int, int}|DefaultOrExistingValue $version
+	 * @param Version[]|DefaultOrExistingValue $previousVersions
+	 */
+	public function edit(
+		PackageRow|PackagePrimaryKey $rowOrKey,
+		string|DefaultOrExistingValue $name = \Grifart\Tables\Unchanged,
+		array|DefaultOrExistingValue $version = \Grifart\Tables\Unchanged,
+		array|DefaultOrExistingValue $previousVersions = \Grifart\Tables\Unchanged,
+	): PackageModifications
 	{
 		$primaryKey = $rowOrKey instanceof PackagePrimaryKey ? $rowOrKey : PackagePrimaryKey::fromRow($rowOrKey);
-		return PackageModifications::update($primaryKey);
+		$modifications = PackageModifications::update($primaryKey);
+		if (!$name instanceof DefaultOrExistingValue) {
+			$modifications->modifyName($name);
+		}
+		if (!$version instanceof DefaultOrExistingValue) {
+			$modifications->modifyVersion($version);
+		}
+		if (!$previousVersions instanceof DefaultOrExistingValue) {
+			$modifications->modifyPreviousVersions($previousVersions);
+		}
+		return $modifications;
 	}
 
 
+	/**
+	 * @throws RowWithGivenPrimaryKeyAlreadyExists
+	 * @throws GivenSearchCriteriaHaveNotMatchedAnyRows
+	 */
 	public function save(PackageModifications $changes): void
 	{
 		$this->tableManager->save($this, $changes);
 	}
 
 
+	/**
+	 * @throws RowWithGivenPrimaryKeyAlreadyExists
+	 */
+	public function insert(PackageModifications $changes): void
+	{
+		$this->tableManager->insert($this, $changes);
+	}
+
+
+	/**
+	 * @throws GivenSearchCriteriaHaveNotMatchedAnyRows
+	 */
+	public function update(PackageModifications $changes): void
+	{
+		$this->tableManager->update($this, $changes);
+	}
+
+
 	public function delete(PackageRow|PackagePrimaryKey $rowOrKey): void
 	{
 		$primaryKey = $rowOrKey instanceof PackagePrimaryKey ? $rowOrKey : PackagePrimaryKey::fromRow($rowOrKey);
diff --git a/tests/Fixtures/TestsTable.php b/tests/Fixtures/TestsTable.php
index 57f773a4f3ab6205a988126f4fd435a086fac761..5e00e4aa4937cdb1f054e2295881940bd0da0778 100644
--- a/tests/Fixtures/TestsTable.php
+++ b/tests/Fixtures/TestsTable.php
@@ -12,9 +12,12 @@ use Grifart\Tables\Column;
 use Grifart\Tables\ColumnMetadata;
 use Grifart\Tables\ColumnNotFound;
 use Grifart\Tables\Conditions\Condition;
+use Grifart\Tables\DefaultOrExistingValue;
 use Grifart\Tables\Expression;
+use Grifart\Tables\GivenSearchCriteriaHaveNotMatchedAnyRows;
 use Grifart\Tables\OrderBy\OrderBy;
 use Grifart\Tables\RowNotFound;
+use Grifart\Tables\RowWithGivenPrimaryKeyAlreadyExists;
 use Grifart\Tables\Table;
 use Grifart\Tables\TableManager;
 use Grifart\Tables\TooManyRowsFound;
@@ -135,34 +138,72 @@ final class TestsTable implements Table
 	}
 
 
-	public function newEmpty(): TestModifications
-	{
-		return TestModifications::new();
-	}
-
-
-	public function new(Uuid $id, int $score): TestModifications
+	public function new(
+		Uuid $id,
+		int $score,
+		string|DefaultOrExistingValue|null $details = \Grifart\Tables\DefaultValue,
+	): TestModifications
 	{
 		$modifications = TestModifications::new();
 		$modifications->modifyId($id);
 		$modifications->modifyScore($score);
+		if (!$details instanceof DefaultOrExistingValue) {
+			$modifications->modifyDetails($details);
+		}
 		return $modifications;
 	}
 
 
-	public function edit(TestRow|TestPrimaryKey $rowOrKey): TestModifications
+	public function edit(
+		TestRow|TestPrimaryKey $rowOrKey,
+		Uuid|DefaultOrExistingValue $id = \Grifart\Tables\Unchanged,
+		int|DefaultOrExistingValue $score = \Grifart\Tables\Unchanged,
+		string|DefaultOrExistingValue|null $details = \Grifart\Tables\Unchanged,
+	): TestModifications
 	{
 		$primaryKey = $rowOrKey instanceof TestPrimaryKey ? $rowOrKey : TestPrimaryKey::fromRow($rowOrKey);
-		return TestModifications::update($primaryKey);
+		$modifications = TestModifications::update($primaryKey);
+		if (!$id instanceof DefaultOrExistingValue) {
+			$modifications->modifyId($id);
+		}
+		if (!$score instanceof DefaultOrExistingValue) {
+			$modifications->modifyScore($score);
+		}
+		if (!$details instanceof DefaultOrExistingValue) {
+			$modifications->modifyDetails($details);
+		}
+		return $modifications;
 	}
 
 
+	/**
+	 * @throws RowWithGivenPrimaryKeyAlreadyExists
+	 * @throws GivenSearchCriteriaHaveNotMatchedAnyRows
+	 */
 	public function save(TestModifications $changes): void
 	{
 		$this->tableManager->save($this, $changes);
 	}
 
 
+	/**
+	 * @throws RowWithGivenPrimaryKeyAlreadyExists
+	 */
+	public function insert(TestModifications $changes): void
+	{
+		$this->tableManager->insert($this, $changes);
+	}
+
+
+	/**
+	 * @throws GivenSearchCriteriaHaveNotMatchedAnyRows
+	 */
+	public function update(TestModifications $changes): void
+	{
+		$this->tableManager->update($this, $changes);
+	}
+
+
 	public function delete(TestRow|TestPrimaryKey $rowOrKey): void
 	{
 		$primaryKey = $rowOrKey instanceof TestPrimaryKey ? $rowOrKey : TestPrimaryKey::fromRow($rowOrKey);
diff --git a/tests/TableTest.php b/tests/TableTest.php
index edfccfa0d1f0f648d7b7887dc230a2cac4a047ec..e4db509f51999f83d788b406c04fd97483286343 100644
--- a/tests/TableTest.php
+++ b/tests/TableTest.php
@@ -37,9 +37,9 @@ Assert::count(2, $all);
 $all2 = $table->findBy([]);
 Assert::equal($all, $all2);
 
-$changeSet = $table->new(new Uuid('9493decd-4b9c-45d6-9960-0c94dc9be353'), -5);
-$changeSet->modifyDetails('👎');
-$table->save($changeSet);
+$table->insert(
+	$table->new(new Uuid('9493decd-4b9c-45d6-9960-0c94dc9be353'), -5, details: '👎')
+);
 
 $all = $table->getAll();
 Assert::count(3, $all);
@@ -115,12 +115,14 @@ Assert::same(0, $nullDetails[0]->getScore());
 $unique = $table->getBy($table->score()->is(42));
 Assert::same(42, $unique->getScore());
 
-$zero = $table->get(TestPrimaryKey::from(new Uuid('2bec3f23-a210-455c-b907-bb69a99d07b2')));
-$zeroChangeSet = $table->edit($zero);
-$zeroChangeSet->modifyDetails('nada');
-$table->save($zeroChangeSet);
+$table->update($table->edit(
+	TestPrimaryKey::from(new Uuid('2bec3f23-a210-455c-b907-bb69a99d07b2')),
+	details: 'nada',
+));
 
 $updatedZero = $table->get(TestPrimaryKey::from(new Uuid('2bec3f23-a210-455c-b907-bb69a99d07b2')));
+
+Assert::same(0, $updatedZero->getScore());
 Assert::same('nada', $updatedZero->getDetails());
 
 $table->delete(TestPrimaryKey::fromRow($updatedZero));