fix(db2): preserve identifier and schema case in grid edits

This commit is contained in:
Rendegou
2026-10-14 18:48:38 +08:00
committed by GitHub
parent 845a3e7faf
commit 4b6896be3c
3 changed files with 206 additions and 2 deletions
+108
View File
@@ -0,0 +1,108 @@
"""DB2 catalog/write identity proof; run remotely with db2.env loaded (no secrets printed)."""
import json
import os
import subprocess
import uuid
schema = os.environ["DB2INSTANCE"].upper()
base = "DBX_CASE_" + uuid.uuid4().hex[:12].upper()
names = [base, base.lower(), "Dbx_Case_" + base[9:].lower()]
process = subprocess.Popen(
["java", "-jar", os.environ["DBX_DB2_AGENT_JAR"]],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True,
)
assert json.loads(process.stdout.readline())["ready"]
request_id = 0
created = []
created_schemas = []
def rpc(method, **params):
global request_id
request_id += 1
params["agentSessionId"] = "identifier-proof"
process.stdin.write(json.dumps({"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}) + "\n")
process.stdin.flush()
return json.loads(process.stdout.readline())
def call(method, **params):
response = rpc(method, **params)
if "error" in response:
raise RuntimeError(method + ": " + json.dumps(response["error"]))
return response["result"]
def quote(name):
return '"' + name.replace('"', '""') + '"'
def target(name, target_schema=schema):
return quote(target_schema) + "." + quote(name)
def query(sql, execution_schema=schema):
return call("execute_query", sql=sql, schema=execution_schema, maxRows=10, timeoutSecs=15)
try:
call("open_session", host="127.0.0.1", port=50000, database=os.environ["DBNAME"],
username=os.environ["DB2INSTANCE"], password=os.environ["DB2INST1_PASSWORD"])
query("CREATE TABLE " + target(base) + " (TEST_TX1ID INTEGER NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR(100))")
created.append((schema, base))
query("INSERT INTO " + target(base) + " VALUES (7, 'upper')")
row = query("select * from " + base.lower())["rows"][0]
assert int(row[0]) == 7 and row[1] == "upper", row
assert call("get_columns", schema=schema, table=base.lower()) == []
failure = rpc("execute_query", sql="UPDATE " + target(base.lower()) + " SET DESCRIPTION='bad' WHERE TEST_TX1ID=7", schema=schema)
assert "-204" in json.dumps(failure.get("error", {})), failure
print("BASELINE: unquoted lowercase SELECT succeeds; lowercase catalog lookup misses; quoted lowercase UPDATE returns -204")
for name, value in zip(names[1:], ["lower", "mixed"]):
query("CREATE TABLE " + target(name) + " (TEST_TX1ID INTEGER NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR(100))")
created.append((schema, name))
query("INSERT INTO " + target(name) + " VALUES (7, '" + value + "')")
for name, original in zip(names, ["upper", "lower", "mixed"]):
columns = call("get_columns", schema=schema, table=name)
assert [column["name"] for column in columns if column["is_primary_key"]] == ["TEST_TX1ID"], columns
ddl = call("get_table_ddl", schema=schema, table=name)
assert "TEST_TX1ID" in ddl and "DESCRIPTION" in ddl, ddl
query("UPDATE " + target(name) + " SET \"DESCRIPTION\"='" + original + "-saved' WHERE \"TEST_TX1ID\"=7")
row = query("SELECT * FROM " + target(name))["rows"][0]
assert int(row[0]) == 7 and row[1] == original + "-saved", row
print("PASS: " + name + " exact catalog identity, primary key, DDL and quoted UPDATE")
# These are simultaneously existing, distinct physical objects. Unquoted SQL
# must select the uppercase one even when a lowercase exact spelling exists.
assert query("select * from " + schema.lower() + "." + base.lower())["rows"][0][1] == "upper-saved"
print("PASS: unquoted schema/table select uppercase object; quoted lowercase/mixed objects remain distinct")
namespace = "Dbx_Schema_" + uuid.uuid4().hex[:12]
namespaces = [namespace.upper(), namespace.lower(), namespace]
for selected_schema in namespaces:
query("CREATE SCHEMA " + quote(selected_schema))
created_schemas.append(selected_schema)
query("CREATE TABLE " + target(base, selected_schema) + " (TEST_TX1ID INTEGER NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR(100))")
created.append((selected_schema, base))
query("INSERT INTO " + target(base, selected_schema) + " VALUES (7, '" + selected_schema + "')")
for selected_schema in namespaces:
# DB2's selected execution schema is exact, even for unquoted table SQL.
row = query("select * from " + base.lower(), selected_schema)["rows"][0]
assert row[1] == selected_schema, row
columns = call("get_columns", schema=selected_schema, table=base)
assert [column["name"] for column in columns if column["is_primary_key"]] == ["TEST_TX1ID"]
ddl = call("get_table_ddl", schema=selected_schema, table=base)
assert "TEST_TX1ID" in ddl and "DESCRIPTION" in ddl
query("UPDATE " + target(base, selected_schema) + " SET DESCRIPTION='" + selected_schema + "-saved' WHERE TEST_TX1ID=7", selected_schema)
for selected_schema in namespaces:
assert query("SELECT * FROM " + target(base, selected_schema))["rows"][0][1] == selected_schema + "-saved"
print("PASS: selected uppercase/lowercase/mixed schemas preserve exact SELECT, catalog, DDL and write targets with same-named tables")
finally:
try:
for table_schema, name in reversed(created):
query("DROP TABLE " + target(name, table_schema))
for created_schema in reversed(created_schemas):
query("DROP SCHEMA " + quote(created_schema) + " RESTRICT")
print("CLEANUP: dropped " + str(len(created)) + " random proof tables")
print("CLEANUP: dropped " + str(len(created_schemas)) + " random proof schemas")
call("close_session")
finally:
process.stdin.close()
process.wait(timeout=20)