{"record":{"id":"116b32ae8350aae3","repo":"cocoindex-io/cocoindex","slug":"identifier-type-cannot-be-empty","errorCode":null,"errorMessage":"{identifier_type} cannot be empty","messagePattern":"(.+?) cannot be empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/connectors/postgres/_source.py","lineNumber":48,"sourceCode":"# Valid SQL identifier pattern: starts with letter or underscore, contains only letters, digits, underscores, or $ (for temp tables)\n_VALID_IDENTIFIER_RE = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_$]*$\")\n\ntry:\n    import asyncpg  # type: ignore\nexcept ImportError as e:\n    raise ImportError(\n        \"asyncpg is required to use the PostgreSQL source connector. \"\n        \"Please install cocoindex[postgres].\"\n    ) from e\n\n\nRowT = TypeVar(\"RowT\", default=dict[str, Any])\n\n\ndef _validate_identifier(name: str, identifier_type: str) -> None:\n    \"\"\"Validate that a string is a valid SQL identifier.\"\"\"\n    if not name:\n        raise ValueError(f\"{identifier_type} cannot be empty\")\n    if not _VALID_IDENTIFIER_RE.match(name):\n        raise ValueError(\n            f\"Invalid {identifier_type}: '{name}'. \"\n            f\"Must start with a letter or underscore and contain only letters, digits, underscores, or $\"\n        )\n\n\ndef _create_row_factory(\n    row_type: type[RowT],\n    field_names: frozenset[str],\n) -> Callable[[dict[str, Any]], RowT]:\n    \"\"\"Create a row factory function from a record type and its field names.\"\"\"\n\n    def factory(row: dict[str, Any]) -> RowT:\n        # Extract only fields that exist in the record type\n        kwargs = {k: v for k, v in row.items() if k in field_names}\n        return row_type(**kwargs)\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/connectors/postgres/_source.py#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"Config value interpolated from an unset environment variable or empty YAML field; a variable initialized to \"\" and never assigned.","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."],"exampleFix":"// before\nsource = postgres.source(pool, table_name=os.environ[\"PG_TABLE\"])  # PG_TABLE unset -> \"\"\n// after\ntable = os.environ.get(\"PG_TABLE\", \"\")\nassert table, \"PG_TABLE must be set\"\nsource = postgres.source(pool, table_name=table)","handlingStrategy":"validation","validationCode":"def ensure_non_empty(name: str, what: str) -> str:\n    if not name or not name.strip():\n        raise ValueError(f\"{what} must be a non-empty string\")\n    return name\n\nensure_non_empty(table_name, \"table name\")","typeGuard":"def is_non_empty_str(v: object) -> bool:\n    return isinstance(v, str) and bool(v)","tryCatchPattern":"try:\n    src = postgres.source(pool, table_name=table_name)\nexcept ValueError as e:\n    logger.error(\"bad identifier: %s\", e)\n    raise","preventionTips":["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."],"tags":["postgres","sql","validation","identifier"],"backgroundTag":"empty-required-field","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}