mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 08:18:52 +00:00
109 lines
5.6 KiB
Python
109 lines
5.6 KiB
Python
"""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)
|