{"record":{"id":"4b53db44b9181062","repo":"cocoindex-io/cocoindex","slug":"invalid-identifier-type-name-must-start-wi","errorCode":null,"errorMessage":"Invalid {identifier_type}: '{name}'. Must start with a letter or underscore and contain only letters, digits, underscores, or $","messagePattern":"Invalid (.+?): '(.+?)'\\. Must start with a letter or underscore and contain only letters, digits, underscores, or \\$","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/connectors/postgres/_source.py","lineNumber":50,"sourceCode":"\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\n    return factory\n","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/connectors/postgres/_source.py#L32-L68","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rename the table/column to a valid identifier, or pass only the bare name without schema qualification.","In PostgreSQL, create an alias/view with a valid identifier name and read from that.","Sanitize/normalize the name (replace invalid characters with _) before calling the connector, and create the underlying object with that name."],"exampleFix":"// before\npostgres.source(pool, table_name=\"schema.my-table\")\n// after\npostgres.source(pool, table_name=\"my_table\")  # or a view named my_table over schema.\"my-table\"","handlingStrategy":"validation","validationCode":"import re\n_IDENT_RE = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_$]*$\")\nif not _IDENT_RE.match(table_name):\n    raise ValueError(f\"table name {table_name!r} is not a valid SQL identifier\")","typeGuard":"def is_sql_identifier(name: str) -> bool:\n    return bool(re.fullmatch(r\"[a-zA-Z_][a-zA-Z0-9_$]*\", name))","tryCatchPattern":"try:\n    src = postgres.source(pool, table_name=raw_name)\nexcept ValueError as e:\n    logger.error(\"invalid identifier %r: %s\", raw_name, e)\n    raise","preventionTips":["Never pass qualified names like \"schema.table\" — use the bare table name.","When deriving names from files/slugs, normalize to [a-zA-Z_][a-zA-Z0-9_]* before creating or referencing objects.","Quote reserved/odd names only in DDL; expose a compliant view to the connector."],"tags":["postgres","sql","validation","identifier"],"backgroundTag":"invalid-identifier-format","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"}