Thin mode: ORA-01483 for string binds of 2731-8191 bytes on a database with the UTF8 character set

オープン
#627 コメント 1 件 リアクション 1 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
45/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
backend, database

調査の方向性

Start with _write_bind_params_row() in src/oracledb/impl/thin/messages/base.pyx and trace how string bind sizes and ordering are handled for Thin mode. Use the supplied script against a database with NLS_CHARACTERSET=UTF8 to reproduce the failure, then compare behavior around the reported 2730, 2731, 8191, and 8192 byte boundaries. Done means the failing cases with a following bind work while the existing DB_TYPE_LONG workaround remains covered.

索引モデルが issue の本文から書いたものです。

説明

  1. What versions are you using?
connection.version: 19.11.0.0.0
platform.platform: Linux-6.10.14-linuxkit-x86_64-with-glibc2.36
sys.maxsize > 2**32: True
platform.python_version: 3.9.25
oracledb.__version__: 3.4.2

The database is Oracle 19c on Amazon RDS, with NLS_CHARACTERSET = UTF8 (not AL32UTF8), NLS_NCHAR_CHARACTERSET = AL16UTF16 and MAX_STRING_SIZE = STANDARD.

  1. Is it an error or a hang or a crash?

An error.

  1. What error(s) or behavior you are seeing?

In Thin mode, a string bind between 2731 and 8191 bytes fails with ORA-01483: invalid length for DATE or NUMBER bind variable whenever another bind follows it in the statement. Below 2731 bytes and from 8192 bytes up, the same statement works. Output of the script below:

{'NLS_NCHAR_CHARACTERSET': 'AL16UTF16', 'NLS_CHARACTERSET': 'UTF8'}
2730 ASCII chars             OK, lengthb = 2730
2731 ASCII chars             ORA-01483: invalid length for DATE or NUMBER bind variable
1000 x '€' (3000 bytes)      ORA-01483: invalid length for DATE or NUMBER bind variable
8191 ASCII chars             ORA-01483: invalid length for DATE or NUMBER bind variable
8192 ASCII chars             OK, lengthb = 8192
2731, DB_TYPE_LONG           OK, lengthb = 2731

It hits DML on ordinary VARCHAR2(4000 BYTE) columns too: INSERT ... VALUES (:text, :num, ...) fails with ORA-01483, and UPDATE ... SET col = :text, other = :num with ORA-01483 or ORA-01461. The same application worked in Thick mode, and I found this while moving it from Thick to Thin mode.

The same cases work in Thin mode against Oracle Database 23ai Free (23.26), which has NLS_CHARACTERSET = AL32UTF8 and also MAX_STRING_SIZE = STANDARD: 2731 and 4000 bytes in an UPDATE followed by a number bind, 1333 × '€', INSERT ... RETURNING, and a SELECT with 3000 and 6000 bytes followed by another bind.

I tracked this down with Claude (Anthropic's AI assistant). The analysis below is its own, from reading the 3.4.2 sources and bisecting the size:

  • For a str, the bind metadata gets a buffer_size of 4 × the encoded length in bytes.
  • _write_bind_params_row() in src/oracledb/impl/thin/messages/base.pyx sends a bind after all the others only when buffer_size > caps.max_string_size, which is 32767 here. That threshold is 8192 bytes of data, the point where the error disappears.
  • The server, on this UTF8 database, appears to treat the bind as LONG, which must come last, from 2731 bytes. That matches a server-side size of 3 × buffer_size exceeding 32767: 2731 × 4 × 3 = 32772, while 2730 × 4 × 3 = 32760. 3 is the maximum character width of UTF8. This factor is an inference from the numbers; I could not check it on the server side.
  • Between 2731 and 8191 bytes the client therefore writes the bind in its place, the server expects it last, and the following bind is read wrongly.

As a workaround I declare the long binds with cursor.setinputsizes(name=oracledb.DB_TYPE_LONG), which makes the driver send them last. DB_TYPE_CLOB also works with a synchronous connection. With connect_async(), setinputsizes(name=oracledb.DB_TYPE_CLOB) with a str value still fails with ORA-01483; DB_TYPE_LONG works there too.

  1. Does your application call init_oracle_client()?

No, Thin mode.

  1. Include a runnable Python script that shows the problem.

It needs no schema: it binds against dual. The difference only shows on a database whose character set is UTF8.

import platform
import sys

import oracledb

connection = oracledb.connect(user="...", password="...", dsn="...")

print("connection.version:", connection.version)
print("platform.platform:", platform.platform())
print("sys.maxsize > 2**32:", sys.maxsize > 2**32)
print("platform.python_version:", platform.python_version())
print("oracledb.__version__:", oracledb.__version__)

cursor = connection.cursor()
cursor.execute("""
    select parameter, value from nls_database_parameters
    where parameter in ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET')""")
print(dict(cursor.fetchall()))

sql = "select lengthb(:text) from dual where :num = 1"
for label, text in [
    ("2730 ASCII chars", "x" * 2730),
    ("2731 ASCII chars", "x" * 2731),
    ("1000 x '€' (3000 bytes)", "€" * 1000),
    ("8191 ASCII chars", "x" * 8191),
    ("8192 ASCII chars", "x" * 8192),
]:
    try:
        cursor.execute(sql, text=text, num=1)
        print(f"{label:28} OK, lengthb = {cursor.fetchone()[0]}")
    except oracledb.DatabaseError as e:
        print(f"{label:28} {e}".splitlines()[0])

cursor.setinputsizes(text=oracledb.DB_TYPE_LONG)
cursor.execute(sql, text="x" * 2731, num=1)
print(f"{'2731, DB_TYPE_LONG':28} OK, lengthb = {cursor.fetchone()[0]}")
主要言語
Python
スター
456
フォーク
119
PR マージ指標
30日以内にマージされた PR はありません

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

oracle/python-oracledb のほかの issue

oracle/python-oracledb の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。