diff --git a/README.md b/README.md
index 7b8c3b68b18748b4d783f0ffbb3f3557a9288800..2f6858551ce4b1de3bbee254e49b50ae20e4378d 100644
--- a/README.md
+++ b/README.md
@@ -149,7 +149,6 @@ Take a look at how a `LIKE` condition could be implemented. It maps to a `LIKE`
 ```php
 use Grifart\Tables\Expression;
 use Grifart\Tables\Types\TextType;
-use function Grifart\Tables\Types\mapToDatabase;
 
 final class IsLike implements Condition
 {
@@ -166,7 +165,7 @@ final class IsLike implements Condition
 		return new \Dibi\Expression(
 			'? LIKE ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->pattern, TextType::varchar()),
+			TextType::varchar()->toDatabase($this->pattern),
 		);
 	}
 }
@@ -434,6 +433,12 @@ You can map values to an array via the `ArrayType`. This formats the items using
 $dateArrayType = ArrayType::of(new DateType());
 ```
 
+Note that while arrays in PostgreSQL can contain `NULL`, `ArrayType` rejects null values unless they are explicitly allowed:
+
+```php
+$nullableDateArrayType = ArrayType::of(NullableType::of(new DateType()));
+```
+
 ##### Enum types
 
 You can map native PHP enumerations to PostgreSQL's enums using the `EnumType`. This requires that the provided enum is a `\BackedEnum`, and serializes it to its backing value:
@@ -482,3 +487,24 @@ $moneyType = new class extends CompositeType {
     }
 };
 ```
+
+Similarly to arrays, in PostgreSQL, composite type fields are always nullable. However, `CompositeType` rejects null values except in positions where they are explicitly allowed:
+
+```php
+$nullableDateArrayType = ArrayType::of(NullableType::of(new DateType()));
+```
+
+```php
+$moneyType = new class extends CompositeType {
+    public function __construct()
+    {
+        parent::__construct(
+            new Database\NamedType(new Database\Identifier('public', 'money')),
+            NullableType::of(DecimalType::decimal()),
+            new CurrencyType(),
+        );
+    }
+
+    // ...
+}
+```
diff --git a/src/Column.php b/src/Column.php
index 59dd95cf9a348b67480d1b5b1d3d2d998b62de52..2ebd5a55f9b82a1351d8e976d6facbef10e377db 100644
--- a/src/Column.php
+++ b/src/Column.php
@@ -6,6 +6,7 @@ namespace Grifart\Tables;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Database\Identifier;
+use Grifart\Tables\Types\NullableType;
 
 /**
  * @template TableType of Table
@@ -59,6 +60,10 @@ final class Column extends ExpressionWithShorthands
 		$location = new Identifier($table::getSchema(), $table::getTableName(), $column->getName());
 		$resolvedType = $typeResolver->resolveType($column->getType(), $location);
 
+		if ($column->isNullable()) {
+			$resolvedType = NullableType::of($resolvedType);
+		}
+
 		/** @var Column<FromTableType, mixed> $column */
 		$column = new self($column, $resolvedType);
 		return $column;
diff --git a/src/Conditions/IsEqualTo.php b/src/Conditions/IsEqualTo.php
index 2612c43e96e62ec26f5d29bd7a206aed8fdea3ae..7a35c39d4c82ac47121a53af40bace640f7956cf 100644
--- a/src/Conditions/IsEqualTo.php
+++ b/src/Conditions/IsEqualTo.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 
 /**
  * @template ValueType
@@ -27,7 +26,7 @@ final class IsEqualTo implements Condition
 		return new DibiExpression(
 			'? = ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->value, $this->expression->getType()),
+			$this->expression->getType()->toDatabase($this->value),
 		);
 	}
 }
diff --git a/src/Conditions/IsGreaterThan.php b/src/Conditions/IsGreaterThan.php
index 38b80e18cda0a596f7d0517ce18e78b1da4ab9b9..90f6a3400b169eb57ea34330a8495efe1b2324b9 100644
--- a/src/Conditions/IsGreaterThan.php
+++ b/src/Conditions/IsGreaterThan.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 
 /**
  * @template ValueType
@@ -27,7 +26,7 @@ final class IsGreaterThan implements Condition
 		return new DibiExpression(
 			'? > ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->value, $this->expression->getType()),
+			$this->expression->getType()->toDatabase($this->value),
 		);
 	}
 }
diff --git a/src/Conditions/IsGreaterThanOrEqualTo.php b/src/Conditions/IsGreaterThanOrEqualTo.php
index 447dd6a20c30ee71cbcbc3fd81741bd3b90b49e8..8aedc9ff3e6533645ed70ad8df42e4249bf74317 100644
--- a/src/Conditions/IsGreaterThanOrEqualTo.php
+++ b/src/Conditions/IsGreaterThanOrEqualTo.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 
 /**
  * @template ValueType
@@ -27,7 +26,7 @@ final class IsGreaterThanOrEqualTo implements Condition
 		return new DibiExpression(
 			'? >= ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->value, $this->expression->getType()),
+			$this->expression->getType()->toDatabase($this->value),
 		);
 	}
 }
diff --git a/src/Conditions/IsIn.php b/src/Conditions/IsIn.php
index b9bb5caf6e9f0332221a254fe84dd3ce58bbce8b..760401a09a58cdea873e420a28950aec8be2cfcd 100644
--- a/src/Conditions/IsIn.php
+++ b/src/Conditions/IsIn.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 use function Phun\map;
 
 /**
@@ -30,7 +29,7 @@ final class IsIn implements Condition
 			$this->expression->toSql(),
 			map(
 				$this->values,
-				fn(mixed $value) => mapToDatabase($value, $this->expression->getType()),
+				fn(mixed $value) => $this->expression->getType()->toDatabase($value),
 			),
 		);
 	}
diff --git a/src/Conditions/IsLesserThan.php b/src/Conditions/IsLesserThan.php
index 515caec4e2f36d39f18cc55e441c5e5591fd6cff..4f220daadd76546397619bdb309211ca46d519e4 100644
--- a/src/Conditions/IsLesserThan.php
+++ b/src/Conditions/IsLesserThan.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 
 /**
  * @template ValueType
@@ -27,7 +26,7 @@ final class IsLesserThan implements Condition
 		return new DibiExpression(
 			'? < ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->value, $this->expression->getType()),
+			$this->expression->getType()->toDatabase($this->value),
 		);
 	}
 }
diff --git a/src/Conditions/IsLesserThanOrEqualTo.php b/src/Conditions/IsLesserThanOrEqualTo.php
index 13e9a9327358233bd8454915508d89855521951e..59abbcae70b68c520bfd3edf2f97593ab2011825 100644
--- a/src/Conditions/IsLesserThanOrEqualTo.php
+++ b/src/Conditions/IsLesserThanOrEqualTo.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 
 /**
  * @template ValueType
@@ -27,7 +26,7 @@ final class IsLesserThanOrEqualTo implements Condition
 		return new DibiExpression(
 			'? <= ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->value, $this->expression->getType()),
+			$this->expression->getType()->toDatabase($this->value),
 		);
 	}
 }
diff --git a/src/Conditions/IsNotEqualTo.php b/src/Conditions/IsNotEqualTo.php
index c071604a93c396e1af2adf59a4ff0b5d5cdcfc08..6f2645e85ba7057257ccfd39ccc403cda840cea2 100644
--- a/src/Conditions/IsNotEqualTo.php
+++ b/src/Conditions/IsNotEqualTo.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 
 /**
  * @template ValueType
@@ -27,7 +26,7 @@ final class IsNotEqualTo implements Condition
 		return new DibiExpression(
 			'? != ?',
 			$this->expression->toSql(),
-			mapToDatabase($this->value, $this->expression->getType()),
+			$this->expression->getType()->toDatabase($this->value),
 		);
 	}
 }
diff --git a/src/Conditions/IsNotIn.php b/src/Conditions/IsNotIn.php
index f2bdd34886894baade269acb4e7f5b31cdd60309..11f0986f098247d8fc338854bb758921fecdf459 100644
--- a/src/Conditions/IsNotIn.php
+++ b/src/Conditions/IsNotIn.php
@@ -6,7 +6,6 @@ namespace Grifart\Tables\Conditions;
 
 use Dibi\Expression as DibiExpression;
 use Grifart\Tables\Expression;
-use function Grifart\Tables\Types\mapToDatabase;
 use function Phun\map;
 
 /**
@@ -30,7 +29,7 @@ final class IsNotIn implements Condition
 			$this->expression->toSql(),
 			map(
 				$this->values,
-				fn(mixed $value) => mapToDatabase($value, $this->expression->getType()),
+				fn(mixed $value) => $this->expression->getType()->toDatabase($value),
 			),
 		);
 	}
diff --git a/src/TableManager.php b/src/TableManager.php
index 3938260d30546c4f5e61664f995c9ea8a6775ab7..e17e9ad2af5f6a2f74bd418dc2806989f430e9ec 100644
--- a/src/TableManager.php
+++ b/src/TableManager.php
@@ -40,7 +40,7 @@ final class TableManager
 				'INTO %n.%n', $table::getSchema(), $table::getTableName(),
 				mapWithKeys(
 					$changes->getModifications(),
-					static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->toDatabase($value) : null,
+					static fn(string $columnName, mixed $value) => $table->getTypeOf($columnName)->toDatabase($value),
 				),
 			);
 		} catch (UniqueConstraintViolationException $e) {
@@ -113,7 +113,7 @@ final class TableManager
 			$modelRows[] = $rowClass::reconstitute(
 				mapWithKeys(
 					$dibiRow->toArray(),
-					static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->fromDatabase($value) : null,
+					static fn(string $columnName, mixed $value) => $table->getTypeOf($columnName)->fromDatabase($value),
 				),
 			);
 		}
@@ -176,7 +176,7 @@ final class TableManager
 		return $rowClass::reconstitute(
 			mapWithKeys(
 				$dibiRow->toArray(),
-				static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->fromDatabase($value) : null,
+				static fn(string $columnName, mixed $value) => $table->getTypeOf($columnName)->fromDatabase($value),
 			),
 		);
 	}
@@ -196,7 +196,7 @@ final class TableManager
 			'SET %a',
 			mapWithKeys(
 				$changes->getModifications(),
-				static fn(string $columnName, mixed $value) => $value !== null ? $table->getTypeOf($columnName)->toDatabase($value) : null,
+				static fn(string $columnName, mixed $value) => $table->getTypeOf($columnName)->toDatabase($value),
 			),
 			'WHERE %ex', $primaryKey->getCondition($table)->toSql()->getValues(),
 		);
diff --git a/src/Types/ArrayType.php b/src/Types/ArrayType.php
index 76aed5c3a7775f2d9b228967b1e12ec9b2e2544d..e0f1675638e768096539b6b6e2a871a8c63eba8a 100644
--- a/src/Types/ArrayType.php
+++ b/src/Types/ArrayType.php
@@ -9,6 +9,7 @@ use Dibi\Literal;
 use Grifart\ClassScaffolder\Definition\Types\Type as PhpType;
 use Grifart\Tables\Database\ArrayType as DatabaseArrayType;
 use Grifart\Tables\Type;
+use Grifart\Tables\UnexpectedNullValue;
 use function Grifart\ClassScaffolder\Definition\Types\listOf;
 use function Phun\map;
 
@@ -16,7 +17,7 @@ use function Phun\map;
  * @template ItemType
  * @implements Type<ItemType[]>
  */
-final class ArrayType implements Type // @todo: There is implicit support for nullable types, shouldn't it be explicit instead?
+final class ArrayType implements Type
 {
 	/**
 	 * @param Type<ItemType> $itemType
@@ -59,7 +60,13 @@ final class ArrayType implements Type // @todo: There is implicit support for nu
 			new Literal('ARRAY['),
 			...map(
 				$value,
-				fn(mixed $item) => $item !== null ? $this->itemType->toDatabase($item) : new Literal('NULL'),
+				function (mixed $item) {
+					if ($item === null && ! $this->itemType instanceof NullableType) {
+						throw new UnexpectedNullValue();
+					}
+
+					return $this->itemType->toDatabase($item);
+				},
 			),
 			new Literal(']::'),
 			$this->getDatabaseType()->toSql(),
@@ -74,9 +81,15 @@ final class ArrayType implements Type // @todo: There is implicit support for nu
 	public function fromDatabase(mixed $value): array
 	{
 		$result = $this->parseArray($value);
-		return map( // @phpstan-ignore return.type (will be fixed in later commits)
+		return map(
 			$result,
-			fn($item) => $item !== null ? $this->itemType->fromDatabase($item) : null,
+			function ($item) {
+				if ($item === null && ! $this->itemType instanceof NullableType) {
+					throw new UnexpectedNullValue();
+				}
+
+				return $this->itemType->fromDatabase($item);
+			},
 		);
 	}
 
@@ -97,7 +110,7 @@ final class ArrayType implements Type // @todo: There is implicit support for nu
 
 			if ( ! $string && $char === '}') {
 				if ($item !== '') {
-					$result[] = $item;
+					$result[] = $item !== 'NULL' ? $item : null;
 				}
 				$end = $i;
 				break;
@@ -108,7 +121,7 @@ final class ArrayType implements Type // @todo: There is implicit support for nu
 				$subArrayStart = $i;
 				$this->parseArray($value, $i, $i);
 				$item = \substr($value, $subArrayStart, $i - $subArrayStart + 1);
-			} elseif ( ! $string && $char ===',') {
+			} elseif ( ! $string && $char === ',') {
 				$result[] = $item !== 'NULL' ? $item : null;
 				$item = '';
 			} elseif ( ! $string && $char === '"') {
diff --git a/src/Types/CompositeType.php b/src/Types/CompositeType.php
index 64c42f6c34bf87e7762dad4eb3eb40d576d1fe37..6a58484bf8c569dd08e4dd21c17d3fb470adc5bc 100644
--- a/src/Types/CompositeType.php
+++ b/src/Types/CompositeType.php
@@ -8,6 +8,7 @@ use Dibi\Expression;
 use Dibi\Literal;
 use Grifart\Tables\Database\DatabaseType;
 use Grifart\Tables\Type;
+use Grifart\Tables\UnexpectedNullValue;
 use function Phun\map;
 use function Phun\mapWithKeys;
 
@@ -47,7 +48,15 @@ abstract class CompositeType implements Type
 			new Literal('ROW('),
 			...mapWithKeys(
 				$value,
-				fn(int $index, mixed $item) => $item !== null ? $this->types[$index]->toDatabase($item) : new Literal('NULL'),
+				function (int $index, mixed $item) {
+					$itemType = $this->types[$index];
+
+					if ($item === null && ! $itemType instanceof NullableType) {
+						throw new UnexpectedNullValue();
+					}
+
+					return $itemType->toDatabase($item);
+				},
 			),
 			new Literal(')::'),
 			$this->getDatabaseType()->toSql(),
@@ -69,7 +78,15 @@ abstract class CompositeType implements Type
 		\assert(\count($result) === \count($this->types));
 		return mapWithKeys(
 			$result,
-			fn($index, $item) => $item !== null ? $this->types[$index]->fromDatabase($item) : null,
+			function (int $index, mixed $item) {
+				$itemType = $this->types[$index];
+
+				if ($item === null && ! $itemType instanceof NullableType) {
+					throw new UnexpectedNullValue();
+				}
+
+				return $itemType->fromDatabase($item);
+			},
 		);
 	}
 
diff --git a/src/Types/NullableType.php b/src/Types/NullableType.php
new file mode 100644
index 0000000000000000000000000000000000000000..d351c0b64742cf438fbbd86260b40cae49b2ebd9
--- /dev/null
+++ b/src/Types/NullableType.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Grifart\Tables\Types;
+
+use Dibi\Expression;
+use Dibi\Expression as DibiExpression;
+use Grifart\ClassScaffolder\Definition\Types\Type as PhpType;
+use Grifart\Tables\Database\DatabaseType;
+use Grifart\Tables\Type;
+use function Grifart\ClassScaffolder\Definition\Types\nullable;
+
+/**
+ * @template ValueType
+ * @implements Type<ValueType|null>
+ */
+final readonly class NullableType implements Type
+{
+	/**
+	 * @param Type<ValueType> $type
+	 */
+	private function __construct(private Type $type) {}
+
+	/**
+	 * @template OfValueType
+	 * @param Type<OfValueType> $type
+	 * @return self<OfValueType>
+	 */
+	public static function of(Type $type): self
+	{
+		return new self($type);
+	}
+
+	public function getPhpType(): PhpType
+	{
+		return nullable($this->type->getPhpType());
+	}
+
+	public function getDatabaseType(): DatabaseType
+	{
+		return $this->type->getDatabaseType();
+	}
+
+	public function toDatabase(mixed $value): DibiExpression
+	{
+		if ($value === null) {
+			return new Expression('%sql', 'NULL');
+		}
+
+		return $this->type->toDatabase($value);
+	}
+
+	public function fromDatabase(mixed $value): mixed
+	{
+		if ($value === null) {
+			return null;
+		}
+
+		return $this->type->fromDatabase($value);
+	}
+}
diff --git a/src/Types/functions.php b/src/Types/functions.php
index b1fb257cac637b0623a21516a4cecf908bc04ee0..9b7376352c92899d8292460ea96a6b5f51a68bd7 100644
--- a/src/Types/functions.php
+++ b/src/Types/functions.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Grifart\Tables\Types;
 
+use Deprecated;
 use Grifart\Tables\Type;
 
 /**
@@ -11,11 +12,10 @@ use Grifart\Tables\Type;
  * @param ValueType $value
  * @param Type<ValueType> $type
  */
+#[Deprecated('Use $type->toDatabase($value) instead')]
 function mapToDatabase(mixed $value, Type $type): mixed
 {
-	return $value !== null
-		? $type->toDatabase($value)
-		: null;
+	return $type->toDatabase($value);
 }
 
 /**
@@ -23,9 +23,8 @@ function mapToDatabase(mixed $value, Type $type): mixed
  * @param Type<ValueType> $type
  * @return ValueType|null
  */
+#[Deprecated('Use $type->fromDatabase($value) instead')]
 function mapFromDatabase(mixed $value, Type $type): mixed
 {
-	return $value !== null
-		? $type->fromDatabase($value)
-		: null;
+	return $type->fromDatabase($value);
 }
diff --git a/src/exceptions.php b/src/exceptions.php
index 696803a932472af5b25cc2f92faaaf0e3ed6a31b..c6cb98dbe40e277b48d970e7265b109332b1bcf7 100644
--- a/src/exceptions.php
+++ b/src/exceptions.php
@@ -69,3 +69,5 @@ final class GivenSearchCriteriaHaveNotMatchedAnyRows extends RuntimeException {}
 final class RowWithGivenPrimaryKeyAlreadyExists extends RuntimeException {}
 final class RowNotFound extends RuntimeException {}
 final class TooManyRowsFound extends UsageException {}
+
+final class UnexpectedNullValue extends UsageException {}
diff --git a/tests/Types/ArrayTypeTest.phpt b/tests/Types/ArrayTypeTest.phpt
index 5800c9b9824aeb644060b06cd57cd69ecb74aaf3..cfb3c07144cc2d5ee003e58758b015affc863403 100644
--- a/tests/Types/ArrayTypeTest.phpt
+++ b/tests/Types/ArrayTypeTest.phpt
@@ -6,7 +6,9 @@ namespace Grifart\Tables\Tests\Types;
 
 use Grifart\Tables\Types\ArrayType;
 use Grifart\Tables\Types\IntType;
+use Grifart\Tables\Types\NullableType;
 use Grifart\Tables\Types\TextType;
+use Grifart\Tables\UnexpectedNullValue;
 use Tester\Assert;
 use function Grifart\Tables\Tests\connect;
 use function Grifart\Tables\Tests\executeExpressionInDatabase;
@@ -18,7 +20,7 @@ $connection = connect();
 
 (function() use ($connection) {
 	$theInput = [42, null, -5];
-	$intArrayType = ArrayType::of(IntType::integer());
+	$intArrayType = ArrayType::of(NullableType::of(IntType::integer()));
 	Assert::same('ARRAY[42,NULL,-5]::integer[]', $connection->translate($dbExpr = $intArrayType->toDatabase($theInput)));
 	$dbResult = executeExpressionInDatabase($connection, $dbExpr);
 	Assert::same('{42,NULL,-5}', $dbResult);
@@ -35,10 +37,16 @@ $connection = connect();
 })();
 
 (function() use ($connection) {
-	$theInput = ['simple', null, '', 'co,m\\ple"\'x'];
+	$theInput = ['simple', '', 'co,m\\ple"\'x'];
 	$textArrayType = ArrayType::of(TextType::text());
-	Assert::same("ARRAY['simple',NULL,'','co,m\\ple\"''x']::text[]", $connection->translate($dbExpr = $textArrayType->toDatabase($theInput)));
+	Assert::same("ARRAY['simple','','co,m\\ple\"''x']::text[]", $connection->translate($dbExpr = $textArrayType->toDatabase($theInput)));
 	$dbResult = executeExpressionInDatabase($connection, $dbExpr);
-	Assert::same('{simple,NULL,"","co,m\\\\ple\\"\'x"}', $dbResult);
+	Assert::same('{simple,"","co,m\\\\ple\\"\'x"}', $dbResult);
 	Assert::same($theInput, $textArrayType->fromDatabase($dbResult));
 })();
+
+(function() {
+	$textArrayType = ArrayType::of(TextType::text());
+	Assert::throws(fn() => $textArrayType->toDatabase([null]), UnexpectedNullValue::class);
+	Assert::throws(fn() => $textArrayType->fromDatabase('{NULL}'), UnexpectedNullValue::class);
+})();
diff --git a/tests/Types/CompositeTypeTest.phpt b/tests/Types/CompositeTypeTest.phpt
index e6ce65947e640b76d44ca9f8bb54d5d896117ce5..7e931bda0e06828ecbbaa2fa5f18874241bca26a 100644
--- a/tests/Types/CompositeTypeTest.phpt
+++ b/tests/Types/CompositeTypeTest.phpt
@@ -10,7 +10,9 @@ use Grifart\Tables\Database\Identifier;
 use Grifart\Tables\Database\NamedType;
 use Grifart\Tables\Types\CompositeType;
 use Grifart\Tables\Types\IntType;
+use Grifart\Tables\Types\NullableType;
 use Grifart\Tables\Types\TextType;
+use Grifart\Tables\UnexpectedNullValue;
 use Tester\Assert;
 use function Grifart\ClassScaffolder\Definition\Types\nullable;
 use function Grifart\ClassScaffolder\Definition\Types\tuple;
@@ -26,11 +28,11 @@ $composite = new class extends CompositeType {
 		parent::__construct(
 			new NamedType(new Identifier('databaseType')),
 			IntType::integer(),
-			IntType::integer(),
-			TextType::text(),
+			NullableType::of(IntType::integer()),
 			TextType::text(),
 			TextType::text(),
 			TextType::text(),
+			NullableType::of(TextType::text()),
 		);
 	}
 
@@ -56,3 +58,6 @@ Assert::same(
 );
 
 Assert::same([42, null, 'com\\ple"\'x', '(', '', null], $composite->fromDatabase('(42,,"com\\\\ple""\'x","(","",)'));
+
+Assert::throws(fn() => $composite->toDatabase([null, null, 'foo', '', '', null]), UnexpectedNullValue::class);
+Assert::throws(fn() => $composite->fromDatabase('(,,"foo","","",)'), UnexpectedNullValue::class);