Skip to content

Bug: Auto-generated jobs table crashes when a Computed/Imported table's primary key derives from a uuid attribute #1515

Description

@arturoptophys

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.

Metadata

Metadata

Labels

bugIndicates an unexpected problem or unintended behavior

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions