1111
1212from .attribute_adapter import get_adapter
1313from .condition import translate_attribute
14- from .errors import FILEPATH_FEATURE_SWITCH , DataJointError , _support_filepath_types
14+ from .errors import DataJointError
1515from .settings import config
1616
17- UUID_DATA_TYPE = "binary(16)"
18-
19- # Type aliases for numeric types
20- SQL_TYPE_ALIASES = {
17+ # Core DataJoint type aliases - scientist-friendly names mapped to native SQL types
18+ # These types can be used without angle brackets in table definitions
19+ CORE_TYPE_ALIASES = {
20+ # Numeric types
2121 "FLOAT32" : "float" ,
2222 "FLOAT64" : "double" ,
2323 "INT64" : "bigint" ,
2929 "INT8" : "tinyint" ,
3030 "UINT8" : "tinyint unsigned" ,
3131 "BOOL" : "tinyint" ,
32+ # UUID type
33+ "UUID" : "binary(16)" ,
3234}
35+
3336MAX_TABLE_NAME_LENGTH = 64
3437CONSTANT_LITERALS = {
3538 "CURRENT_TIMESTAMP" ,
3639 "NULL" ,
3740} # SQL literals to be used without quotes (case insensitive)
38- EXTERNAL_TABLE_ROOT = "~external"
3941
42+ # Type patterns for declaration parsing
43+ # Two categories: core type aliases and native passthrough types
4044TYPE_PATTERN = {
4145 k : re .compile (v , re .I )
4246 for k , v in dict (
43- # Type aliases must come before INTEGER and FLOAT patterns to avoid prefix matching
47+ # Core DataJoint type aliases (scientist-friendly names)
4448 FLOAT32 = r"float32$" ,
4549 FLOAT64 = r"float64$" ,
4650 INT64 = r"int64$" ,
5155 UINT16 = r"uint16$" ,
5256 INT8 = r"int8$" ,
5357 UINT8 = r"uint8$" ,
54- BOOL = r"bool$" , # aliased to tinyint
55- # Native MySQL types
58+ BOOL = r"bool$" ,
59+ UUID = r"uuid$" ,
60+ # Native SQL types (passthrough)
5661 INTEGER = r"((tiny|small|medium|big|)int|integer)(\s*\(.+\))?(\s+unsigned)?(\s+auto_increment)?|serial$" ,
5762 DECIMAL = r"(decimal|numeric)(\s*\(.+\))?(\s+unsigned)?$" ,
5863 FLOAT = r"(double|float|real)(\s*\(.+\))?(\s+unsigned)?$" ,
5964 STRING = r"(var)?char\s*\(.+\)$" ,
6065 JSON = r"json$" ,
6166 ENUM = r"enum\s*\(.+\)$" ,
6267 TEMPORAL = r"(date|datetime|time|timestamp|year)(\s*\(.+\))?$" ,
63- INTERNAL_BLOB = r"(tiny|small|medium|long|)blob$" ,
64- EXTERNAL_BLOB = r"blob@(?P<store>[a-z][\-\w]*)$" ,
65- INTERNAL_ATTACH = r"attach$" ,
66- EXTERNAL_ATTACH = r"attach@(?P<store>[a-z][\-\w]*)$" ,
67- FILEPATH = r"filepath@(?P<store>[a-z][\-\w]*)$" ,
68- OBJECT = r"object(@(?P<store>[a-z][\-\w]*))?$" , # managed object storage (files/folders)
69- UUID = r"uuid$" ,
68+ BLOB = r"(tiny|small|medium|long|)blob$" ,
69+ # AttributeTypes use angle brackets
7070 ADAPTED = r"<.+>$" ,
7171 ).items ()
7272}
7373
74- # custom types are stored in attribute comment
75- SPECIAL_TYPES = {
76- "UUID" ,
77- "INTERNAL_ATTACH" ,
78- "EXTERNAL_ATTACH" ,
79- "EXTERNAL_BLOB" ,
80- "FILEPATH" ,
81- "OBJECT" ,
82- "ADAPTED" ,
83- } | set (SQL_TYPE_ALIASES )
74+ # Types that require special handling (stored in attribute comment for reconstruction)
75+ SPECIAL_TYPES = {"ADAPTED" } | set (CORE_TYPE_ALIASES )
76+
77+ # Native SQL types that pass through without modification
8478NATIVE_TYPES = set (TYPE_PATTERN ) - SPECIAL_TYPES
85- EXTERNAL_TYPES = {
86- "EXTERNAL_ATTACH" ,
87- "EXTERNAL_BLOB" ,
88- "FILEPATH" ,
89- } # data referenced by a UUID in external tables
90- # Blob and attachment types cannot have SQL default values (other than NULL)
91- BINARY_TYPES = {
92- "EXTERNAL_ATTACH" ,
93- "INTERNAL_ATTACH" ,
94- "EXTERNAL_BLOB" ,
95- "INTERNAL_BLOB" ,
96- }
9779
98- assert set (). union ( SPECIAL_TYPES , EXTERNAL_TYPES , BINARY_TYPES ) <= set (TYPE_PATTERN )
80+ assert SPECIAL_TYPES <= set (TYPE_PATTERN )
9981
10082
10183def match_type (attribute_type ):
@@ -459,50 +441,32 @@ def format_attribute(attr):
459441
460442def substitute_special_type (match , category , foreign_key_sql , context ):
461443 """
444+ Substitute special types with their native SQL equivalents.
445+
446+ Special types are:
447+ - Core type aliases (float32 → float, uuid → binary(16), etc.)
448+ - ADAPTED types (AttributeTypes in angle brackets)
449+
462450 :param match: dict containing with keys "type" and "comment" -- will be modified in place
463451 :param category: attribute type category from TYPE_PATTERN
464452 :param foreign_key_sql: list of foreign key declarations to add to
465453 :param context: context for looking up user-defined attribute_type adapters
466454 """
467- if category == "UUID" :
468- match ["type" ] = UUID_DATA_TYPE
469- elif category == "INTERNAL_ATTACH" :
470- match ["type" ] = "LONGBLOB"
471- elif category == "OBJECT" :
472- # Object type stores metadata as JSON - no foreign key to external table
473- # Extract store name if present (object@store_name syntax)
474- if "@" in match ["type" ]:
475- match ["store" ] = match ["type" ].split ("@" , 1 )[1 ]
476- match ["type" ] = "JSON"
477- elif category in EXTERNAL_TYPES :
478- if category == "FILEPATH" and not _support_filepath_types ():
479- raise DataJointError (
480- """
481- The filepath data type is disabled until complete validation.
482- To turn it on as experimental feature, set the environment variable
483- {env} = TRUE or upgrade datajoint.
484- """ .format (env = FILEPATH_FEATURE_SWITCH )
485- )
486- match ["store" ] = match ["type" ].split ("@" , 1 )[1 ]
487- match ["type" ] = UUID_DATA_TYPE
488- foreign_key_sql .append (
489- "FOREIGN KEY (`{name}`) REFERENCES `{{database}}`.`{external_table_root}_{store}` (`hash`) "
490- "ON UPDATE RESTRICT ON DELETE RESTRICT" .format (external_table_root = EXTERNAL_TABLE_ROOT , ** match )
491- )
492- elif category == "ADAPTED" :
455+ if category == "ADAPTED" :
456+ # AttributeType - resolve to underlying dtype
493457 attr_type , store_name = get_adapter (context , match ["type" ])
494- # Store the store parameter if present
495458 if store_name is not None :
496459 match ["store" ] = store_name
497460 match ["type" ] = attr_type .dtype
461+ # Recursively resolve if dtype is also a special type
498462 category = match_type (match ["type" ])
499463 if category in SPECIAL_TYPES :
500- # recursive redefinition from user-defined datatypes.
501464 substitute_special_type (match , category , foreign_key_sql , context )
502- elif category in SQL_TYPE_ALIASES :
503- match ["type" ] = SQL_TYPE_ALIASES [category ]
465+ elif category in CORE_TYPE_ALIASES :
466+ # Core type alias - substitute with native SQL type
467+ match ["type" ] = CORE_TYPE_ALIASES [category ]
504468 else :
505- assert False , "Unknown special type"
469+ assert False , f "Unknown special type: { category } "
506470
507471
508472def compile_attribute (line , in_key , foreign_key_sql , context ):
@@ -513,7 +477,7 @@ def compile_attribute(line, in_key, foreign_key_sql, context):
513477 :param in_key: set to True if attribute is in primary key set
514478 :param foreign_key_sql: the list of foreign key declarations to add to
515479 :param context: context in which to look up user-defined attribute type adapterss
516- :returns: (name, sql, is_external ) -- attribute name and sql code for its declaration
480+ :returns: (name, sql, store ) -- attribute name, sql code for its declaration, and optional store name
517481 """
518482 try :
519483 match = attribute_parser .parseString (line + "#" , parseAll = True )
@@ -550,13 +514,10 @@ def compile_attribute(line, in_key, foreign_key_sql, context):
550514 match ["comment" ] = ":{type}:{comment}" .format (** match ) # insert custom type into comment
551515 substitute_special_type (match , category , foreign_key_sql , context )
552516
553- if category in BINARY_TYPES and match ["default" ] not in {
554- "DEFAULT NULL" ,
555- "NOT NULL" ,
556- }:
557- raise DataJointError (
558- "The default value for blob or attachment attributes can only be NULL in:\n {line}" .format (line = line )
559- )
517+ # Check for invalid default values on blob types (after type substitution)
518+ final_category = match_type (match ["type" ])
519+ if final_category == "BLOB" and match ["default" ] not in {"DEFAULT NULL" , "NOT NULL" }:
520+ raise DataJointError ("The default value for blob attributes can only be NULL in:\n {line}" .format (line = line ))
560521
561522 sql = ("`{name}` {type} {default}" + (' COMMENT "{comment}"' if match ["comment" ] else "" )).format (** match )
562523 return match ["name" ], sql , match .get ("store" )
0 commit comments