2525use Utopia \Database \Helpers \ID ;
2626use Utopia \Database \Helpers \Permission ;
2727use Utopia \Database \Helpers \Role ;
28+ use Utopia \Database \Validator \Attribute as AttributeValidator ;
2829use Utopia \Database \Validator \Authorization ;
2930use Utopia \Database \Validator \Authorization \Input ;
3031use Utopia \Database \Validator \Index as IndexValidator ;
@@ -45,6 +46,11 @@ class Database
4546 public const VAR_BOOLEAN = 'boolean ' ;
4647 public const VAR_DATETIME = 'datetime ' ;
4748
49+ public const VAR_VARCHAR = 'varchar ' ;
50+ public const VAR_TEXT = 'text ' ;
51+ public const VAR_MEDIUMTEXT = 'mediumtext ' ;
52+ public const VAR_LONGTEXT = 'longtext ' ;
53+
4854 // ID types
4955 public const VAR_ID = 'id ' ;
5056 public const VAR_UUID7 = 'uuid7 ' ;
@@ -2232,36 +2238,6 @@ private function validateAttribute(
22322238 array $ formatOptions ,
22332239 array $ filters
22342240 ): Document {
2235- // Attribute IDs are case-insensitive
2236- $ attributes = $ collection ->getAttribute ('attributes ' , []);
2237-
2238- /** @var array<Document> $attributes */
2239- foreach ($ attributes as $ attribute ) {
2240- if (\strtolower ($ attribute ->getId ()) === \strtolower ($ id )) {
2241- throw new DuplicateException ('Attribute already exists in metadata ' );
2242- }
2243- }
2244-
2245- if ($ this ->adapter ->getSupportForSchemaAttributes () && !($ this ->getSharedTables () && $ this ->isMigrating ())) {
2246- $ schema = $ this ->getSchemaAttributes ($ collection ->getId ());
2247- foreach ($ schema as $ attribute ) {
2248- $ newId = $ this ->adapter ->filter ($ attribute ->getId ());
2249- if (\strtolower ($ newId ) === \strtolower ($ id )) {
2250- throw new DuplicateException ('Attribute already exists in schema ' );
2251- }
2252- }
2253- }
2254-
2255- // Ensure required filters for the attribute are passed
2256- $ requiredFilters = $ this ->getRequiredFilters ($ type );
2257- if (!empty (\array_diff ($ requiredFilters , $ filters ))) {
2258- throw new DatabaseException ("Attribute of type: $ type requires the following filters: " . implode (", " , $ requiredFilters ));
2259- }
2260-
2261- if ($ format && !Structure::hasFormat ($ format , $ type )) {
2262- throw new DatabaseException ('Format (" ' . $ format . '") not available for this attribute type (" ' . $ type . '") ' );
2263- }
2264-
22652241 $ attribute = new Document ([
22662242 '$id ' => ID ::custom ($ id ),
22672243 'key ' => $ id ,
@@ -2276,111 +2252,31 @@ private function validateAttribute(
22762252 'filters ' => $ filters ,
22772253 ]);
22782254
2279- $ this ->checkAttribute ($ collection , $ attribute );
2280-
2281- switch ($ type ) {
2282- case self ::VAR_ID :
2283-
2284- break ;
2285- case self ::VAR_STRING :
2286- if ($ size > $ this ->adapter ->getLimitForString ()) {
2287- throw new DatabaseException ('Max size allowed for string is: ' . number_format ($ this ->adapter ->getLimitForString ()));
2288- }
2289- break ;
2290- case self ::VAR_INTEGER :
2291- $ limit = ($ signed ) ? $ this ->adapter ->getLimitForInt () / 2 : $ this ->adapter ->getLimitForInt ();
2292- if ($ size > $ limit ) {
2293- throw new DatabaseException ('Max size allowed for int is: ' . number_format ($ limit ));
2294- }
2295- break ;
2296- case self ::VAR_FLOAT :
2297- case self ::VAR_BOOLEAN :
2298- case self ::VAR_DATETIME :
2299- case self ::VAR_RELATIONSHIP :
2300- break ;
2301- case self ::VAR_OBJECT :
2302- if (!$ this ->adapter ->getSupportForObject ()) {
2303- throw new DatabaseException ('Object attributes are not supported ' );
2304- }
2305- if (!empty ($ size )) {
2306- throw new DatabaseException ('Size must be empty for object attributes ' );
2307- }
2308- if (!empty ($ array )) {
2309- throw new DatabaseException ('Object attributes cannot be arrays ' );
2310- }
2311- break ;
2312- case self ::VAR_POINT :
2313- case self ::VAR_LINESTRING :
2314- case self ::VAR_POLYGON :
2315- // Check if adapter supports spatial attributes
2316- if (!$ this ->adapter ->getSupportForSpatialAttributes ()) {
2317- throw new DatabaseException ('Spatial attributes are not supported ' );
2318- }
2319- if (!empty ($ size )) {
2320- throw new DatabaseException ('Size must be empty for spatial attributes ' );
2321- }
2322- if (!empty ($ array )) {
2323- throw new DatabaseException ('Spatial attributes cannot be arrays ' );
2324- }
2325- break ;
2326- case self ::VAR_VECTOR :
2327- if (!$ this ->adapter ->getSupportForVectors ()) {
2328- throw new DatabaseException ('Vector types are not supported by the current database ' );
2329- }
2330- if ($ array ) {
2331- throw new DatabaseException ('Vector type cannot be an array ' );
2332- }
2333- if ($ size <= 0 ) {
2334- throw new DatabaseException ('Vector dimensions must be a positive integer ' );
2335- }
2336- if ($ size > self ::MAX_VECTOR_DIMENSIONS ) {
2337- throw new DatabaseException ('Vector dimensions cannot exceed ' . self ::MAX_VECTOR_DIMENSIONS );
2338- }
2339-
2340- // Validate default value if provided
2341- if ($ default !== null ) {
2342- if (!is_array ($ default )) {
2343- throw new DatabaseException ('Vector default value must be an array ' );
2344- }
2345- if (count ($ default ) !== $ size ) {
2346- throw new DatabaseException ('Vector default value must have exactly ' . $ size . ' elements ' );
2347- }
2348- foreach ($ default as $ component ) {
2349- if (!is_numeric ($ component )) {
2350- throw new DatabaseException ('Vector default value must contain only numeric elements ' );
2351- }
2352- }
2353- }
2354- break ;
2355- default :
2356- $ supportedTypes = [
2357- self ::VAR_STRING ,
2358- self ::VAR_INTEGER ,
2359- self ::VAR_FLOAT ,
2360- self ::VAR_BOOLEAN ,
2361- self ::VAR_DATETIME ,
2362- self ::VAR_RELATIONSHIP
2363- ];
2364- if ($ this ->adapter ->getSupportForVectors ()) {
2365- $ supportedTypes [] = self ::VAR_VECTOR ;
2366- }
2367- if ($ this ->adapter ->getSupportForSpatialAttributes ()) {
2368- \array_push ($ supportedTypes , ...self ::SPATIAL_TYPES );
2369- }
2370- if ($ this ->adapter ->getSupportForObject ()) {
2371- $ supportedTypes [] = self ::VAR_OBJECT ;
2372- }
2373- throw new DatabaseException ('Unknown attribute type: ' . $ type . '. Must be one of ' . implode (', ' , $ supportedTypes ));
2374- }
2375-
2376- // Only execute when $default is given
2377- if (!\is_null ($ default )) {
2378- if ($ required === true ) {
2379- throw new DatabaseException ('Cannot set a default value for a required attribute ' );
2380- }
2255+ $ collectionClone = clone $ collection ;
2256+ $ collectionClone ->setAttribute ('attributes ' , $ attribute , Document::SET_TYPE_APPEND );
2257+
2258+ $ validator = new AttributeValidator (
2259+ attributes: $ collection ->getAttribute ('attributes ' , []),
2260+ schemaAttributes: $ this ->adapter ->getSupportForSchemaAttributes ()
2261+ ? $ this ->getSchemaAttributes ($ collection ->getId ())
2262+ : [],
2263+ maxAttributes: $ this ->adapter ->getLimitForAttributes (),
2264+ maxWidth: $ this ->adapter ->getDocumentSizeLimit (),
2265+ maxStringLength: $ this ->adapter ->getLimitForString (),
2266+ maxVarcharLength: $ this ->adapter ->getMaxVarcharLength (),
2267+ maxIntLength: $ this ->adapter ->getLimitForInt (),
2268+ supportForSchemaAttributes: $ this ->adapter ->getSupportForSchemaAttributes (),
2269+ supportForVectors: $ this ->adapter ->getSupportForVectors (),
2270+ supportForSpatialAttributes: $ this ->adapter ->getSupportForSpatialAttributes (),
2271+ supportForObject: $ this ->adapter ->getSupportForObject (),
2272+ attributeCountCallback: fn () => $ this ->adapter ->getCountOfAttributes ($ collectionClone ),
2273+ attributeWidthCallback: fn () => $ this ->adapter ->getAttributeWidth ($ collectionClone ),
2274+ filterCallback: fn ($ id ) => $ this ->adapter ->filter ($ id ),
2275+ isMigrating: $ this ->isMigrating (),
2276+ sharedTables: $ this ->getSharedTables (),
2277+ );
23812278
2382- $ this ->validateDefaultTypes ($ type , $ default );
2383- }
2279+ $ validator ->isValid ($ attribute );
23842280
23852281 return $ attribute ;
23862282 }
@@ -2430,6 +2326,14 @@ protected function validateDefaultTypes(string $type, mixed $default): void
24302326
24312327 switch ($ type ) {
24322328 case self ::VAR_STRING :
2329+ case self ::VAR_VARCHAR :
2330+ case self ::VAR_TEXT :
2331+ case self ::VAR_MEDIUMTEXT :
2332+ case self ::VAR_LONGTEXT :
2333+ if ($ defaultType !== 'string ' ) {
2334+ throw new DatabaseException ('Default value ' . $ default . ' does not match given type ' . $ type );
2335+ }
2336+ break ;
24332337 case self ::VAR_INTEGER :
24342338 case self ::VAR_FLOAT :
24352339 case self ::VAR_BOOLEAN :
@@ -2451,6 +2355,10 @@ protected function validateDefaultTypes(string $type, mixed $default): void
24512355 default :
24522356 $ supportedTypes = [
24532357 self ::VAR_STRING ,
2358+ self ::VAR_VARCHAR ,
2359+ self ::VAR_TEXT ,
2360+ self ::VAR_MEDIUMTEXT ,
2361+ self ::VAR_LONGTEXT ,
24542362 self ::VAR_INTEGER ,
24552363 self ::VAR_FLOAT ,
24562364 self ::VAR_BOOLEAN ,
0 commit comments