cocoindex-io/cocoindex · error · ValueError

{identifier_type} cannot be empty

Error message

{identifier_type} cannot be empty

What it means

Shared validation helper for SQL identifiers used by the PostgreSQL source connector. It rejects (a) empty strings and (b) strings failing _VALID_IDENTIFIER_RE (must start with a letter/underscore and contain only letters, digits, underscores, or $), because such values would need quoting and could enable injection or reference non-existent objects. Raised with identifier_type naming which kind of identifier (table, column, etc.) failed; occurs when source config or query results carry a blank or malformed name. Use a valid, non-empty SQL identifier.

Source

Thrown at python/cocoindex/connectors/postgres/_source.py:48

# Valid SQL identifier pattern: starts with letter or underscore, contains only letters, digits, underscores, or $ (for temp tables)
_VALID_IDENTIFIER_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_$]*$")

try:
    import asyncpg  # type: ignore
except ImportError as e:
    raise ImportError(
        "asyncpg is required to use the PostgreSQL source connector. "
        "Please install cocoindex[postgres]."
    ) from e


RowT = TypeVar("RowT", default=dict[str, Any])


def _validate_identifier(name: str, identifier_type: str) -> None:
    """Validate that a string is a valid SQL identifier."""
    if not name:
        raise ValueError(f"{identifier_type} cannot be empty")
    if not _VALID_IDENTIFIER_RE.match(name):
        raise ValueError(
            f"Invalid {identifier_type}: '{name}'. "
            f"Must start with a letter or underscore and contain only letters, digits, underscores, or $"
        )


def _create_row_factory(
    row_type: type[RowT],
    field_names: frozenset[str],
) -> Callable[[dict[str, Any]], RowT]:
    """Create a row factory function from a record type and its field names."""

    def factory(row: dict[str, Any]) -> RowT:
        # Extract only fields that exist in the record type
        kwargs = {k: v for k, v in row.items() if k in field_names}
        return row_type(**kwargs)

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Provide a non-empty table/column name at the call site.
  2. Validate configuration before constructing the source; fail fast with a clear message.
  3. Check for env vars or config keys resolving to empty strings and set defaults.

Example fix

// before
source = postgres.source(pool, table_name=os.environ["PG_TABLE"])  # PG_TABLE unset -> ""
// after
table = os.environ.get("PG_TABLE", "")
assert table, "PG_TABLE must be set"
source = postgres.source(pool, table_name=table)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_non_empty(name: str, what: str) -> str:
    if not name or not name.strip():
        raise ValueError(f"{what} must be a non-empty string")
    return name

ensure_non_empty(table_name, "table name")

Type guard

def is_non_empty_str(v: object) -> bool:
    return isinstance(v, str) and bool(v)

Try / catch

try:
    src = postgres.source(pool, table_name=table_name)
except ValueError as e:
    logger.error("bad identifier: %s", e)
    raise

Prevention

When it happens

Trigger: Calling a postgres source API (e.g. the async iterable via __aiter__) with an empty table_name or column name string, which reaches _validate_identifier.

Common situations: Config value interpolated from an unset environment variable or empty YAML field; a variable initialized to "" and never assigned.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/116b32ae8350aae3. Report an issue: GitHub.