cocoindex-io/cocoindex · error · ValueError

Invalid {identifier_type}: '{name}'. Must start with a lette

Error message

Invalid {identifier_type}: '{name}'. Must start with a letter or underscore and contain only letters, digits, underscores, or $

What it means

_validate_identifier enforces the SQL identifier pattern ^[a-zA-Z_][a-zA-Z0-9_$]*$ — the name must start with a letter or underscore and contain only letters, digits, underscores, or $. This prevents SQL injection and invalid SQL from user-supplied names that cannot be quoted.

Source

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

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)

    return factory

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Rename the table/column to a valid identifier, or pass only the bare name without schema qualification.
  2. In PostgreSQL, create an alias/view with a valid identifier name and read from that.
  3. Sanitize/normalize the name (replace invalid characters with _) before calling the connector, and create the underlying object with that name.

Example fix

// before
postgres.source(pool, table_name="schema.my-table")
// after
postgres.source(pool, table_name="my_table")  # or a view named my_table over schema."my-table"
Defensive patterns

Strategy: validation

Validate before calling

import re
_IDENT_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_$]*$")
if not _IDENT_RE.match(table_name):
    raise ValueError(f"table name {table_name!r} is not a valid SQL identifier")

Type guard

def is_sql_identifier(name: str) -> bool:
    return bool(re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_$]*", name))

Try / catch

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

Prevention

When it happens

Trigger: Passing a table or column name containing hyphens, spaces, dots, leading digits, or other special characters (e.g. "my-table", "123t", "user name") to the postgres source connector.

Common situations: Deriving table names from file names or URL slugs (which contain hyphens/dots); qualified names like "schema.table" passed as one string; quoted/case-sensitive identifiers pasted from DDL.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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