功能概述

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));

对于列类型为 CHARVARCHARVARCHAR2 字符型的列,当没有显式指定列的长度类型时,IvorySQL使用 nls_length_semantics 参数的值来决定长度类型,有 bytechar 两种值,默认为 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 中数据类型 oracharcharoracharbyte 的修饰符输入输出函数包括:

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";

1.4. GUC参数 nls_currency/nls_iso_currency/nls_territory

目前,nls_territorynls_iso_currency 支持CHINA与AMERICA两个值。

默认值如下:

char	   *nls_territory = "AMERICA";
char	   *nls_currency = "$";
char	   *nls_iso_currency = "AMERICA";

这三个参数将在oracle兼容函数 to_number() 中被使用。

to_number() 函数尚未实现。