Skip to content

tdfs4ds.utils.identifiers

tdfs4ds.utils.identifiers

Teradata identifier quoting — one authority for the whole package.

Why this exists

A Teradata object name is only legal unquoted when it matches [A-Za-z][A-Za-z0-9_$#]*. Anything else — a hyphen, a space, an @, a dot, an accented character — must be written as a double-quoted identifier ("SVC@CORP"."FS_FEATURE_CATALOG"). This matters here because tdfs4ds.SCHEMA is routinely the logon user's database (see tdfs4ds/__init__.py, which defaults it to _get_current_databasename()), and an LDAP/AD login can carry any of those characters.

The contract

Quoting is conditional. quote_ident('TDB') == 'TDB': a name that needs no quotes is returned untouched, so every store with an ordinary name keeps generating byte-identical SQL. Quoting engages only where the alternative is a syntax error. This is the same opt-in shape as database_view, read_federation and storage='OFS'.

Quoting applies to identifier slots only. These helpers are for names that occupy an identifier position in SQL. They must NOT be used for:

  • SQL string literals — WHERE DatabaseName = '{db}' against the DBC views compares a string, so it needs the raw (de-quoted) name and sql_literal escaping, not identifier quoting. Passing a quoted name there yields DatabaseName = "MYDB", which Teradata reads as a column reference.
  • Connection credentials — create_context(user=…, database=…) are driver parameters, never SQL text.
  • teradataml keyword arguments — in_schema(), copy_to_sql(schema_name=…) and db_list_tables(schema_name=…) do their own quoting (or need a literal), so a pre-quoted name is wrong at all three.
  • Values persisted into catalog columns — FEATURE_DATABASE stores the raw name; the quoting happens when that value is later read back and interpolated.

Reserved words are a deliberate non-goal: quoting them would change the SQL of existing stores that nobody reported a problem with.

__all__ module-attribute

__all__ = ['needs_quoting', 'quote_ident', 'unquote_ident', 'qualified', 'split_qualified', 'sql_literal']

_SAFE_IDENT module-attribute

_SAFE_IDENT = compile('^[A-Za-z][A-Za-z0-9_$#]*$')

_is_quoted

_is_quoted(s)

True when s is already a complete double-quoted identifier.

Requires a matched outer pair AND that no unescaped quote appears inside, so '"a"."b"' (two identifiers) is correctly rejected — treating it as one already-quoted name is how teradataml's own _teradata_quote_arg goes wrong.

_scan_quoted

_scan_quoted(s, i)

Index just past the quoted identifier starting at s[i], or -1.

"" inside the quotes is an escaped quote and does not end the identifier.

needs_quoting

needs_quoting(name)

True when name cannot be written as a bare Teradata identifier.

An already-quoted name returns False — it is legal as it stands.

quote_ident

quote_ident(name, force=False)

Return name as a Teradata identifier, quoting it only if required.

Idempotent: an already-quoted name is returned unchanged rather than being wrapped a second time. Embedded quotes are escaped as "".

Parameters

name : str The raw object/database name. force : bool, default False Quote even when the name does not need it. Use only at the few sites that have always emitted unconditional quotes and whose exact output is already baked into persisted DDL (see feature_store/feature_query_retrieval.py) — elsewhere forcing would change the SQL of existing stores for no reason.

unquote_ident

unquote_ident(name)

Inverse of :func:quote_ident: strip a matched quote pair and unescape.

A name that is not quoted is returned stripped but otherwise untouched. This is deliberately narrower than the .strip('"') / .replace('"','') idioms scattered through the package: those also mangle a name whose own content contains a quote.

qualified

qualified(database, object_name, force=False)

Build db.obj with each part quoted only as needed.

qualified('TDB', 'FS_X') == 'TDB.FS_X' (unchanged for ordinary names); qualified('SVC@CORP', 'FS_X') == '"SVC@CORP".FS_X'. An empty/None database yields the bare object name, so unqualified callers keep working.

force=True quotes both parts unconditionally — only for the sites that have always emitted quotes and whose exact text is already persisted (dataset view DDL, returned volatile-table names).

split_qualified

split_qualified(name)

Split db.obj into (database, object), both de-quoted.

Quote-aware, so a dot inside a quoted part does not split the name: split_qualified('"DEINF.CARLOSN"."FS_X"') == ('DEINF.CARLOSN', 'FS_X'). The naive rsplit('.', 1) this replaces returns ('"DEINF.CARLOSN"', '"FS_X"') only by luck and ('"DEINF', 'CARLOSN"."FS_X"') in the split('.') variant.

An unqualified name returns ('', name).

sql_literal

sql_literal(value)

Escape value for use inside a single-quoted SQL string literal.

For the DBC probes and catalog predicates that compare a name as data (WHERE DatabaseName = '…'). The caller supplies the surrounding quotes.