Calling populate(reserve_jobs=True) (or any other access to Table.jobs, e.g.
Table.jobs.refresh()) on a Computed/Imported table whose primary key derives — via a
primary foreign key — from a uuid-typed attribute crashes with:
DataJointError: Unsupported attribute type binary(16)
LLM-analysis of the issue
The error message points at binary(16), which is never a type a user writes in a table
definition — it is the resolved MySQL column type DataJoint substitutes internally for the
uuid core type alias. The auto-generated jobs table definition leaks this internal, already-
resolved SQL type back into a fresh DataJoint definition string, which then fails to parse.
Environment
datajoint==2.2.4
- MySQL backend
- Reproduced with a table whose primary key is entirely FK-derived from a parent table with a
uuid primary key attribute — the officially supported/recommended shape for auto-populated
tables (_declare_check in autopopulate.py explicitly requires FK-only primary keys).
Root cause
datajoint/jobs.py, Job._generate_definition() builds the DataJoint definition string for
the ~~<table_name> job-queue table by walking the target table's FK-derived primary key
attributes and emitting "{name} : {dtype}" for each one:
# jobs.py:180-219 (_get_fk_derived_pk_attrs)
for name in target_pk:
if name in fk_derived_attrs:
attr = heading[name]
fk_attrs.append((name, attr.type)) # <-- line 211
...
# jobs.py:141-178 (_generate_definition)
pk_lines = "\n ".join(f"{name} : {dtype}" for name, dtype in pk_attrs)
return f"""
# Job queue for {self._target.full_table_name}
{pk_lines}
---
...
"""
attr.type is the already-resolved SQL type for the attribute (e.g. binary(16) for a
uuid column), not the DataJoint-level type alias the user originally wrote (uuid). DataJoint
heading attributes carry both:
# heading.py:60-63
default_attribute_properties = dict(
...
type="expression",
original_type=None, # For core types, stores the alias (e.g., "uuid") while type has db type ("binary(16)")
...
)
and DataJoint's own code elsewhere correctly falls back to original_type when it needs the
alias back (heading.py:496: original_type = attr["original_type"] or attr["type"]). jobs.py
does not do this — it always uses attr.type — so for a uuid PK attribute, the generated jobs
table definition contains a line like:
recording_id : binary(16)
When Job.declare() parses this string, compile_attribute() calls match_type("binary(16)")
(declare.py:910). TYPE_PATTERN has no entry that matches a bare binary(...) SQL type — it
only has uuid (r"uuid$"), native blob variants, and other named categories — so match_type
falls through every pattern and raises:
# declare.py:110-113
try:
return next(category for category, pattern in TYPE_PATTERN.items() if pattern.match(attribute_type))
except StopIteration:
raise DataJointError("Unsupported attribute type {type}".format(type=attribute_type))
producing DataJointError: Unsupported attribute type binary(16).
Note: Job.__init__ calls _generate_definition() unconditionally, but Job.declare() is only
invoked by autopopulate.py's _JobsDescriptor when not obj._jobs.is_declared (i.e. the
~~table does not yet exist in the database). So the crash is specifically triggered the first
time .jobs is accessed for a given table (including the implicit access inside
populate(reserve_jobs=True)) — any Computed/Imported table whose FK-derived primary key
chain passes through a uuid attribute will hit this on first populate/jobs access, and never
recover, since is_declared will keep being False.
Minimal reproduction
import datajoint as dj
schema = dj.Schema("repro_uuid_jobs")
@schema
class Parent(dj.Manual):
definition = """
parent_id : uuid
"""
@schema
class Child(dj.Computed):
definition = """
-> Parent
---
value : int32
"""
def make(self, key):
self.insert1(dict(key, value=1))
Child.populate(reserve_jobs=True)
Actual result:
DataJointError: Unsupported attribute type binary(16)
Expected result: the jobs table declares successfully with parent_id : uuid (which
DataJoint itself would then correctly resolve to binary(16) internally), and populate
proceeds normally.
Impact
Any pipeline that uses uuid as a stable, filesystem-independent identifier for a root table
(a common and DataJoint-recommended pattern for deterministic, content-addressed keys) and
computes downstream tables from it via FK cannot use reserve_jobs=True at all — the very
feature intended for safe distributed/parallel populate() calls. There is no user-facing
workaround via the table definition; the bug is entirely in the internal jobs-table
autogeneration path and fires before any user make() code runs.
Suggested fix
In jobs.py, _get_fk_derived_pk_attrs should use the same fallback pattern DataJoint uses
elsewhere for recovering the original core-type alias:
attr = heading[name]
fk_attrs.append((name, attr.original_type or attr.type))
This mirrors heading.py:496 (original_type = attr["original_type"] or attr["type"]) and
ensures the regenerated definition string contains DataJoint-level type syntax (uuid,
float32, etc.) rather than backend-resolved SQL types, so compile_attribute/match_type can
parse it correctly regardless of which core type the FK-derived primary key attribute uses.
Calling
populate(reserve_jobs=True)(or any other access toTable.jobs, e.g.Table.jobs.refresh()) on aComputed/Importedtable whose primary key derives — via aprimary foreign key — from a
uuid-typed attribute crashes with:LLM-analysis of the issue
The error message points at
binary(16), which is never a type a user writes in a tabledefinition — it is the resolved MySQL column type DataJoint substitutes internally for the
uuidcore type alias. The auto-generated jobs table definition leaks this internal, already-resolved SQL type back into a fresh DataJoint definition string, which then fails to parse.
Environment
datajoint==2.2.4uuidprimary key attribute — the officially supported/recommended shape for auto-populatedtables (
_declare_checkinautopopulate.pyexplicitly requires FK-only primary keys).Root cause
datajoint/jobs.py,Job._generate_definition()builds the DataJoint definition string forthe
~~<table_name>job-queue table by walking the target table's FK-derived primary keyattributes and emitting
"{name} : {dtype}"for each one:attr.typeis the already-resolved SQL type for the attribute (e.g.binary(16)for auuidcolumn), not the DataJoint-level type alias the user originally wrote (uuid). DataJointheading attributes carry both:
and DataJoint's own code elsewhere correctly falls back to
original_typewhen it needs thealias back (
heading.py:496:original_type = attr["original_type"] or attr["type"]).jobs.pydoes not do this — it always uses
attr.type— so for auuidPK attribute, the generated jobstable definition contains a line like:
When
Job.declare()parses this string,compile_attribute()callsmatch_type("binary(16)")(
declare.py:910).TYPE_PATTERNhas no entry that matches a barebinary(...)SQL type — itonly has
uuid(r"uuid$"), native blob variants, and other named categories — somatch_typefalls through every pattern and raises:
producing
DataJointError: Unsupported attribute type binary(16).Note:
Job.__init__calls_generate_definition()unconditionally, butJob.declare()is onlyinvoked by
autopopulate.py's_JobsDescriptorwhennot obj._jobs.is_declared(i.e. the~~tabledoes not yet exist in the database). So the crash is specifically triggered the firsttime
.jobsis accessed for a given table (including the implicit access insidepopulate(reserve_jobs=True)) — anyComputed/Importedtable whose FK-derived primary keychain passes through a
uuidattribute will hit this on first populate/jobs access, and neverrecover, since
is_declaredwill keep beingFalse.Minimal reproduction
Actual result:
Expected result: the jobs table declares successfully with
parent_id : uuid(whichDataJoint itself would then correctly resolve to
binary(16)internally), andpopulateproceeds normally.
Impact
Any pipeline that uses
uuidas a stable, filesystem-independent identifier for a root table(a common and DataJoint-recommended pattern for deterministic, content-addressed keys) and
computes downstream tables from it via FK cannot use
reserve_jobs=Trueat all — the veryfeature intended for safe distributed/parallel
populate()calls. There is no user-facingworkaround via the table definition; the bug is entirely in the internal jobs-table
autogeneration path and fires before any user
make()code runs.Suggested fix
In
jobs.py,_get_fk_derived_pk_attrsshould use the same fallback pattern DataJoint useselsewhere for recovering the original core-type alias:
This mirrors
heading.py:496(original_type = attr["original_type"] or attr["type"]) andensures the regenerated definition string contains DataJoint-level type syntax (
uuid,float32, etc.) rather than backend-resolved SQL types, socompile_attribute/match_typecanparse it correctly regardless of which core type the FK-derived primary key attribute uses.