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
- Provide a non-empty table/column name at the call site.
- Validate configuration before constructing the source; fail fast with a clear message.
- 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
- Validate env-var/config-derived names at startup with clear failure messages.
- Use required config parsing (which rejects empty values) instead of os.environ.get with "" default.
- Load config with a schema validator (pydantic/marshmallow) that enforces min_length=1.
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
- Invalid {identifier_type}: '{name}'. Must start with a lette
- Invalid identifier: {name}
- Invalid BigQuery {kind}: {name!r}
- Invalid FalkorDB {kind}: {name!r}. Must match [a-zA-Z_][a-zA
- Invalid Neo4j database name: {database!r}. Must match [A-Za-
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/116b32ae8350aae3.
Report an issue: GitHub.