VARCHAR2 and NVARCHAR2¶
The naming scheme¶
| Domain | Constraint | Use for |
|---|---|---|
oracle.varchar_<N>b |
octet_length(VALUE) <= N |
VARCHAR2(N) under Oracle's default NLS_LENGTH_SEMANTICS=BYTE |
oracle.varchar_<N>c |
char_length(VALUE) <= N |
VARCHAR2(N) under NLS_LENGTH_SEMANTICS=CHAR |
oracle.nvarchar_<N>c |
char_length(VALUE) <= N |
NVARCHAR2(N) — always character-length in real Oracle, regardless of NLS_LENGTH_SEMANTICS |
b counts bytes, c counts characters — they only diverge on multi-byte text. A 5-character string that's 6 bytes long (say, one accented character) fits _5c but not _5b.
Length coverage: every length 1–300 individually, then 310–4000 in steps of 10 — 670 distinct lengths, matching Oracle's own 4000-character VARCHAR2 column cap. A declared length above 300 that isn't an exact multiple of 10 gets rounded up to the next available domain by the rewrite tool below — e.g. VARCHAR2(847) becomes oracle.varchar_850b. This never rejects previously-valid data; it's only ever marginally looser than the original constraint. See Customizing the Type Family if you want a different range or step.
Finding existing usage¶
Three places VARCHAR2/NVARCHAR2 can turn up, each needing its own check:
1. Inside PL/pgSQL functions/procedures — use oracle.orafce_audit():
SELECT * FROM oracle.orafce_audit(p_summary => false)
WHERE target_object IN ('varchar2', 'nvarchar2') OR matched_text ~* 'n?varchar2';
2. Table columns — the audit function doesn't cover DDL, so query the catalog directly:
SELECT c.relname AS table_name,
a.attname AS column_name,
format_type(a.atttypid, a.atttypmod) AS declared_type
FROM pg_attribute a
JOIN pg_class c ON c.oid = a.attrelid
JOIN pg_type t ON t.oid = a.atttypid
JOIN pg_namespace n ON n.oid = t.typnamespace
WHERE n.nspname = 'oracle'
AND t.typname IN ('varchar2', 'nvarchar2')
AND a.attnum > 0
AND NOT a.attisdropped;
declared_type comes back as e.g. varchar2(50) — information_schema.columns hides the length for custom types (character_maximum_length comes back NULL), but format_type() recovers it.
3. Source you haven't run anywhere yet — a plain search works, and is exactly the pattern the rewrite tool below looks for:
Rewriting your code¶
audit/rewrite_varchar2_declarations.pl mechanically rewrites VARCHAR2(n)/NVARCHAR2(n) text in any SQL/PL-SQL source file to point at the matching domain. It's a plain text rewrite — it works identically on CREATE TABLE column definitions, PL/pgSQL variable declarations, and function return types, since all three are just VARCHAR2(n) appearing as a type name in source text.
# Dry-run (default): prints a diff, writes nothing.
perl audit/rewrite_varchar2_declarations.pl path/to/your_file.sql
# Byte-length mode is the default (matches Oracle's own default
# NLS_LENGTH_SEMANTICS=BYTE). Use --mode=c if your source database ran
# with NLS_LENGTH_SEMANTICS=CHAR. NVARCHAR2 always maps to the _c domain
# regardless of this flag.
perl audit/rewrite_varchar2_declarations.pl --mode=c path/to/your_file.sql
# Once you've reviewed the diff, write the changes back:
perl audit/rewrite_varchar2_declarations.pl --mode=c --apply path/to/your_file.sql
Example rewrite (--mode=b, the default):
-- before
v_name VARCHAR2(50);
v_note NVARCHAR2(847);
-- after
v_name oracle.varchar_50b;
v_note oracle.nvarchar_850c; -- 847 rounded up to the next available length
Lengths over 4000 are reported as a warning and left completely untouched — never silently mapped to something wrong. The script exits non-zero if any file had an out-of-range length, so it's safe to use as a gate in a migration pipeline.
Columns already live in a database, not in a source file¶
The rewrite tool operates on text files, not a running database's catalog. If you found usage via the catalog query above rather than in source you control, alter the column directly instead:
Every domain in the family wraps plain text, so USING my_column::text is always sufficient. The ALTER fails immediately, before touching any data, if an existing value exceeds the target domain's constraint — the correct, safe failure mode.
Continue to Why These Types Error Instead of Truncating — read this before you migrate anything that relies on VARCHAR2's cast-truncation behavior.