如何使用Python cx_Oracle从US7ASCII Oracle读取国家字符(> 127)?

时间:2014-01-24 15:20:44

标签: python sql django oracle cx-oracle

我在使用Python 3.3 cx_Oracle 5.1.2和“NLS_LANG”环境变量显示来自“ENGLISH_UNITED KINGDOM.US7ASCII”Oracle 11数据库的国家字符时遇到问题。 Db表列类型为“VARCHAR2(2000 BYTE)”

如何在Python中用Oracle US7ASCII显示字符串“£aÀÁÂÃÄÅÆÇÈ”?这将是某种黑客攻击。 hank适用于所有其他脚本语言Perl,PHP,PL / SQL和 Python 2.7 ,但它在Python 3.3中不起作用。

在Oracle 11数据库中,我创建了SECURITY_HINTS.ANSWER =“£aÀÁÂÃÄÅÆÇÈ”。 ANSWER列类型为“VARCHAR2(2000 BYTE)”。

现在使用cx_Oracle和默认NLS_LANG时,我得到“¿a¿¿¿¿¿¿¿¿¿”

当使用NLS_LANG =“ENGLISH_UNITED KINGDOM.US7ASCII”时我得到了

"UnicodeDecodeError: 'ascii' codec can't decode byte 0xa3 in position 0: ordinal not in range(128)"

UPDATE1 我取得了一些进展。当切换到Python 2.7和cx_Oracle 5.1.2 for Python 2.7时,问题就消失了(我从db获得了所有> 127个字符)。在Python 2中,字符串表示为字节,在Python 3 +字符串中表示为unicode。我仍然需要最好的Python 3.3解决方案。

UPDATE2 解决该问题的一种可能方法是使用rawtohex(utl_raw.cast_to_raw请参阅下面的代码。

cursor.execute("select rawtohex(utl_raw.cast_to_raw(ANSWER)) from security_hints where userid = '...'")
for rawValue in cursor:
    print (''.join(['%c' % iterating_var for iterating_var in binascii.unhexlify(rawValue[0])]))

我的脚本的源代码位于GitHubGitHub Sollution

之下
def test_nls(nls_lang=None):
    print (">>> run test_nls for %s" %(nls_lang))
    if nls_lang:
        os.environ["NLS_LANG"] = nls_lang
    os.environ["ORA_NCHAR_LITERAL_REPLACE"] = "TRUE"

    connection = get_connection()
    cursor = connection.cursor()
    print("version=%s\nencoding=%s\tnencoding=%s\tmaxBytesPerCharacter=%s" %(connection.version, connection.encoding,
            connection.nencoding, connection.maxBytesPerCharacter))

    cursor.execute("SELECT USERENV ('language') FROM DUAL")
    for result in cursor:
        print("%s" %(result))

    cursor.execute("select ANSWER from SECURITY_HINTS where USERID = '...'")
    for rawValue in cursor:
        print("query returned [%s]" % (rawValue))
        answer = rawValue[0]
    str = ""
    for iterating_var in answer:
        str = ("%s [%d]" % (str, ord(iterating_var)))

    print ("str %s" %(str))

    cursor.close()
    connection.close()

if __name__ == '__main__':
    test_nls()
    test_nls(".AL32UTF8")
    test_nls("ENGLISH_UNITED KINGDOM.US7ASCII")

请参阅下面的日志输出。

run test_nls for None
version=11.1.0.7.0
encoding=WINDOWS-1252   nencoding=WINDOWS-1252  maxBytesPerCharacter=1
ENGLISH_UNITED KINGDOM.US7ASCII
query returned [¿a¿¿¿¿¿¿¿¿¿]
str  [191] [97] [191] [191] [191] [191] [191] [191] [191] [191] [191


run test_nls for .AL32UTF8
version=11.1.0.7.0
encoding=UTF-8  nencoding=UTF-8 maxBytesPerCharacter=4
AMERICAN_AMERICA.US7ASCII
query returned [�a���������]
str  [65533] [97] [65533] [65533] [65533] [65533] [65533] [65533] [65533] [65533] [65533]

run test_nls for ENGLISH_UNITED KINGDOM.US7ASCII
version=11.1.0.7.0
encoding=US-ASCII   nencoding=US-ASCII  maxBytesPerCharacter=1
ENGLISH_UNITED KINGDOM.US7ASCII
Traceback (most recent call last):
  File "C:/dev/tmp/Python_US7ASCII_cx_Oracle/showUS7ASCII.py", line 71, in <module>
    test_nls("ENGLISH_UNITED KINGDOM.US7ASCII")
  File "C:/dev/tmp/Python_US7ASCII_cx_Oracle/showUS7ASCII.py", line 55, in test_nls
    for rawValue in cursor:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa3 in position 0: ordinal not in range(128)

我正在尝试在Django网页中显示它。但每个字符都是代码为191或65533的字符。

我看着 choosing NLS_LANG for OracleImporting from Oracle using the correct encoding with Python

Cannot Insert Unicode Using cx-Oracle

2 个答案:

答案 0 :(得分:2)

如果要在客户端应用程序中获取未更改的ASCII字符串,最好的方法是以二进制模式从DB传输它。因此,在UTL_RAW包和标准rawtohex函数的帮助下,首先必须在服务器端进行转换。

您在cursor.execute中的选择可能如下:

select rawtohex(utl_raw.cast_to_raw(ANSWER)) from SECURITY_HINTS where USERID = '...'

在客户端上,您获得了一串十六进制字符,可以借助binascii.unhexlify函数将其转换为字符串表示形式:

for rawValue in cursor:
       print("query returned [%s]" % (binascii.unhexlify(rawValue)))

P.S。我不知道Python语言,所以最后的陈述可能不正确。

答案 1 :(得分:1)

我认为你不应该回归这种邪恶的诡计。 NLS_LANG应该只是设置为客户端的默认编码。看看更可靠的选项:

  1. 扩展数据库的字符集以允许在VARCHAR列中包含这些字符。
  2. 将此特定列升级为NVARCHAR。您也许可以为此列使用新名称,并使用旧名称创建一个VARCHAR计算列,以便读取旧版应用程序。
  3. 保持数据库不变,但在输入数据时检查数据,并用可接受的ASCII等效字符替换所有非ASCII字符。
  4. 哪个选项最好取决于非ASCII字符的常见程度。如果有更多的表有相同的问题,我建议选项1.如果这是唯一的表,选项2.如果整个表中只有几个非ASCII字符,并且他们的损失不是那么大的交易:选项3。

    数据库的任务之一就是保持数据质量,如果你在强行插入非法字符时作弊,它就无法正常工作,每个新客户端或升级或导出都会到来有趣的新未定义行为。


    编辑:请参阅Oracle对NLS_LANG faq中类似设置示例的评论(我的重点):

      

    在具有US7ASCII字符的UNIX系统上创建数据库   组。 连接到数据库的Windows客户端使用   WE8MSWIN1252字符集(区域设置 - &gt;西欧/ ACP   1252)和DBA一样,使用UNIX shell(ROMAN8)来工作   数据库。 NLS_LANG设置为american_america.US7ASCII   客户和服务器。

         

    注意:

         

    这是一个解释字符集转换的INCORRECT设置,不是   在您的环境中使用它!