功能概述
IvorySQL提供了兼容Oracle的NLS参数功能,包含如下参数。
参数名称 |
功能描述 |
ivorysql.datetime_ignore_nls_mask |
表示日期格式是否忽略NLS参数影响,默认为0。 |
nls_length_semantics |
兼容Oracle的同名参数,表示char/varchar/varchar2的类型修饰符的大小单位是字节还是字符。 |
nls_date_format |
表示默认的日期格式,可以通过show命令查看,默认为‘YYYY-MM-DD’。 |
nls_timestamp_format |
兼容Oracle的同名参数,控制带时间的日期格式。 |
nls_timestamp_tz_format |
兼容Oracle的同名参数,控制带时区的日期时间格式。 |
nls_territory |
兼容Oracle的同名参数,指定数据库的默认区域。 |
nls_iso_currency |
兼容Oracle的同名参数,指定国家和区域对应的唯一货币符。 |
nls_currency |
兼容Oracle的同名参数,指定显示本地货币的符号,对应数字字符串格式中占位符L。 |
1. 实现原理
1.1. nls_length_semantics 参数
IvorySQL中的数据类型存在一个属性修饰符 typmod ,是对类型的补充说明,比如在 VARCHAR(n) 类型中,n 就是类型修饰符。
在创建或修改表的列时可以指定长度类型,例如:
ivorysql=# create table t1(name varchar2(2 byte));
对于列类型为 CHAR 、 VARCHAR 和 VARCHAR2 字符型的列,当没有显式指定列的长度类型时,IvorySQL使用 nls_length_semantics 参数的值来决定长度类型,有 byte 和 char 两种值,默认为 byte 。
需要特别注意的是, nls_length_semantics 参数的值仅影响新创建的列,对已经存在的列不会产生任何影响。
在语法解析文件 ora_gram.y中,存在如下代码来根据 nls_length_semantics 把原本的 char/varchar/varchar2 类型改成 oracharchar 或者 oracharbyte :
CharacterWithLength: character '(' Iconst ')'
{
if (ORA_PARSER == compatible_db)
{
if (strcmp($1, "bpchar"))
{
if (nls_length_semantics == NLS_LENGTH_CHAR)
$1 = "oravarcharchar";
else
$1 = "oravarcharbyte";
}
else
{
if (nls_length_semantics == NLS_LENGTH_CHAR)
$1 = "oracharchar";
else
$1 = "oracharbyte";
}
$$ = OracleSystemTypeName($1);
$$->typmods = list_make1(makeIntConst($3, @3));
$$->location = @1;
}
else
{
...
}
}
;
IvorySQL 中数据类型 oracharchar 和 oracharbyte 的修饰符输入输出函数包括:
oravarcharchartypmodout()
oravarcharbytetypmodout()
oracharbytetypmodout()
oracharchartypmodout()
上面这些函数调用C语言实现的函数 anychar_typmodout() ,后者根据 nls_length_semantics 的值来调整输出的内容是否包含 byte/char 的说明。
nls_length_semantics 另一个作用是限制表中的列长度:
根据上述代码在语法解析文件 ora_gram.y中,如果原本的 varchar 类型被转换成了 oracharchar 类型,则函数 oravarcharchar() 会被调用,而 pg_mbcharcliplen() 函数计算字符长度,而不是字节长度。
Datum
oravarcharchar(PG_FUNCTION_ARGS)
{
VarChar *source = PG_GETARG_VARCHAR_PP(0);
int32 typmod = PG_GETARG_INT32(1);
bool isExplicit = PG_GETARG_BOOL(2);
int32 len,
maxlen;
size_t maxmblen;
char *s_data;
len = VARSIZE_ANY_EXHDR(source);
s_data = VARDATA_ANY(source);
maxlen = typmod - VARHDRSZ;
/* No work if typmod is invalid or supplied data fits it already */
if (maxlen < 0 || len <= maxlen)
PG_RETURN_VARCHAR_P(source);
maxmblen = pg_mbcharcliplen(s_data, len, maxlen);
...
}
1.2. GUC参数 datetime_ignore_nls_mask
这个参数被定义为一个int值,低四位分别表示是否在相应的日期时间格式上忽略NLS参数的影响,掩码定义如下:
#define ORADATE_MASK 0x01
#define ORATIMESTAMP_MASK 0x02
#define ORATIMESTAMPTZ_MASK 0x04
#define ORATIMESTAMPLTZ_MASK 0x08
在源代码中,这个GUC参数被用于下面这些函数:
oradate_in()
oratimestamp_in()
oratimestampltz_in()
oratimestamptz_in()
如果相应的掩码被设置,则调用原生PG的处理函数,否则调用兼容代码并忽略NLS格式。
1.3. GUC参数 nls_date_format/nls_timestamp_format/nls_timestamp_tz_format
这三个GUC参数,在函数 ora_do_to_timestamp() 中作为格式字符串,对输入的字符串进行格式检查与模式识别。
下面是其默认值,可以通过设置其值为"pg"使其失效。"pg"表示禁用NLS特定行为,恢复为PostgreSQL的默认行为。
char *nls_date_format = "YYYY-MM-DD";
char *nls_timestamp_format = "YYYY-MM-DD HH24:MI:SS.FF6";
char *nls_timestamp_tz_format = "YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM";