fix(teradata): probe the CREATE TABLE right with the real destination DDL - #818
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Shadow auto-approve: would not auto-approve because issues were found.
Re-trigger cubic
|
One design-level point first: the CREATE probe is the one check in the SQL precheck family that leaves state behind. The probe creates and drops a real table. Run it in a transaction and roll back. DDL is transactional in Teradata, so wrapping the CREATE in an explicit transaction ( |
|
Approving. Nothing here blocks. This closes most of the #815 list: the probe runs the real DDL and 3523 refuses too (2), both refusal messages carry the blank-Database hint (3), 1. Version: main took 1.11.19 while this was open. The body's merge note expected a clash with #816, but it was #814 (confluence) that merged at 1.11.19, so this needs 1.11.20. Only 2. The CREATE probe refuses on 3523 and 3524 but still passes 5315 and 5612. The new docstring ( 3. Q: the 3523 message goes one step further than I asked for. In #815 I asked for a 3523 message that doesn't claim the missing right is CREATE TABLE. This one says it isn't: "The refused right is not CREATE TABLE on the database, which this code does not report" ( The same question runs the other way. In #815 I called 3524 the code that's certainly about CREATE TABLE, and on the real DDL I'm less sure. If I have Teradata's message template right, 3524 names its own database, so if a right on 4. Neither the customer message nor our log says which right a 3523 refused. The message tells the customer "Teradata names the right it refused in the same error in the database's own log" ( 5. The hints on the 3523 path aren't tested. The only test that drives the CREATE probe into 3523 ( 6. The Table Name hint doesn't say what to set it to. 7. Q: can the DROP fail on every check? Each call gets a fresh 8. "One line per outcome" has a couple of gaps, and most of the new lines aren't tested.
9. The leftover assertion I asked for in #815 can't run in CI, and passes whenever the probe creates nothing. 10. Nits.
Q. What level does the platform run the Pre-existing, not this PR's to fix:
cubic's three notes and @awalker4's transaction/rollback point are already in the thread, so I didn't repeat them. |
tabossert
left a comment
There was a problem hiding this comment.
Approving. Notes are in my comment above; none of them block.
Review follow-up on #815. The CREATE TABLE precheck probe asked the server for less than create_destination() will: a one-INTEGER-column table against a real DDL carrying CLOB, VECTOR32, JSON and a PRIMARY INDEX. A right one of those types needs and CREATE TABLE does not carry was therefore invisible to the check and hit the customer at upload instead. The probe now runs create_destination()'s own statement under the throwaway name, through the shared _elements_schema_sql(), so the rights it asks for are the rights the upload needs: no wider, no narrower, which is the rule #811 set. It runs UNQUALIFIED, the way create_destination() runs it, so it lands in the same session database the check just resolved and no identifier is interpolated into the SQL. That also answers cubic's quoting finding: a database name with a double quote in it no longer reaches a statement. 3523 now refuses alongside 3524. The probe is the real statement, so any answer that means "a right was refused and nothing else" is an answer about the real CREATE, and a right the column types need comes back as 3523 rather than 3524. Its message says which database and which code, and deliberately does not tell the customer CREATE TABLE is what they are missing, because on that code it is not. With the Database field blank both messages also say the named database is only the session default and that the field exists: a DBA who follows the message otherwise grants rights on a database nobody chose, which is the complaint this check came from. A refusal the server has already given now survives the way out of the block: denied is set before the try, and a cursor close or a commit that raises after the CREATE was refused no longer turns the refusal into "inconclusive". Logging: the probe table is named BEFORE it is created, so a process killed between the CREATE and the DROP leaves a traceable name; create_destination() names the database it is creating in, which precheck alone used to report; and each precheck outcome (skipped, refused, created, inconclusive) now has its own line, with inconclusive downgraded to info to match _run_write_probe. Tests cover the real-DDL statement, the 3523 refusal, the blank-Database sentence, the probe name in the log, a non-driver CREATE error, SELECT DATABASE raising and returning nothing, the DBC.TablesV lookup raising, get_cursor() raising, and teardown raising after a confirmed refusal. The live integration test now asserts the precheck left no unstructured_precheck_% table behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-up on #815, which merged before this landed. With no table configured the CREATE TABLE probe always runs, because the caller names the table only when it calls create_destination() and precheck has nothing to look up. That is the one case the check can refuse a credential the job would not have needed: a user whose per-workflow table already exists and who has since lost CREATE TABLE is refused here, even though create_destination() would have found the table and returned early without creating one. The refusal is kept rather than downgraded to a warning. Warning when the table name is unset would switch the check off on the path the platform uses, which is the path #815 targets. Instead the message now says why it probed and points at the Table Name field, which makes check_create_table_permission look the table up and skip the probe entirely. A dead end becomes something the customer can act on. CHANGELOG moves to its own 1.11.19 section: #815 shipped as 1.11.18, so the released entry is restored verbatim and this version documents the delta, plus the two behaviour changes 1.11.18's entry did not record (a no-table destination can now be refused where it used to pass, and 3524 was reclassified on the source side as well as the destination). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ok the CREATE The probe returned None for two different outcomes -- a CREATE the server accepted, and a CREATE that failed for a reason the check cannot read -- and the caller treated both as "probed". A probe that failed on 2644, 3803, 5315 or any unrecognized code therefore logged "CREATE TABLE permission check inconclusive" and then, two lines later, "destination credentials can create the destination table in database X". The second line is a result nobody got. The probe now returns the refusal alongside whether the CREATE was accepted, and the certifying log fires only on the accepted branch. Passing the check is unchanged: an inconclusive probe still passes, it just no longer claims a right it did not observe. A DROP that fails still counts as created, because the CREATE the server took is what proves the right. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ly ran leftover_probe_tables() filtered DBC.TablesV on TERADATA_DATABASE. With that blank -- a supported configuration, and the one the blank-database refusal hint exists for -- the connector's probe lands in the session default instead, the filter matches no rows, and the caller's `assert leftover_probe_tables() == []` passes without having looked anywhere. The helper now resolves the database with SELECT DATABASE, the way the connector resolves it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The note claimed no identifier reaches the SQL text. The probe table name is interpolated into both the CREATE and the DROP, deliberately. What stays out is the resolved database name, which is the point being made: the statement is unqualified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10a0955 to
28e9c24
Compare
|
All three cubic findings were real. Fixed, one commit each.
None of this touches the property the change is for: the probe still runs Rebased onto
|
What & why
Problem: A Teradata destination user who cannot create the table the job auto-creates still finds that out at upload time rather than at the connector check, because the check asks a narrower question than the upload does. The probe that shipped in 1.11.18 creates a one-INTEGER-column table, while
create_destination()runs the connector's real DDL withCLOB,VECTOR32,JSONand aPRIMARY INDEX. A right one of those column types needs and CREATE TABLE alone does not carry is invisible to the check, so the credential passes and the job fails partway through. Separately, a user whose per-workflow table already exists and who has since lost CREATE TABLE is now refused by that check with no way to act on the message, even though the upload would have found the table and never created one.Change: The probe runs
create_destination()'s own statement under the throwaway name, unqualified, so the rights it asks for are the rights the upload needs. Teradata 3523 refuses alongside 3524, with a message that does not claim CREATE TABLE is the missing right. Blank Database and unset Table Name each add a sentence naming the field that settles the refusal, so the customer has something to do. A refusal the server already gave survives a failure tearing the session down, and each precheck outcome gets its own log line.Blast radius: 3/5 -- the connector check runs the real destination DDL on the customer's Teradata; one connector, revert-safe.
Linked ticket
none
Follow-up to #815, which merged before this work landed. This branch closes Trevor's review items 2, 3, 4, 5, 6 (traceability), 7, 10, 11a and 11b from #815 (comment), and the code half of item 1. Items 9 and the body half of 1 were fixed by editing the merged body of #815. Item 8 (version collision) and item 11c (
TableKind = 'T') are answered in that thread and not changed here; the reasoning is in #815 (comment).Provenance, so the diff is not mis-read. The first commit here,
7e6f164a"probe Teradata CREATE with the real destination DDL", was written in a separate earlier session against the branch of #815 and was still unpushed when that PR merged. It is not new work and it is not mine; this branch carries it forward unchanged, cherry-picked ontomain(the trees matched exactly, so it applied clean). The second commit is the new work: the Table Name hint, its tests, and the CHANGELOG restructure.Client-facing follow-up: a customer configured a Teradata destination with a non-admin user and a blank Database field; it passed the connector check and the run auto-created its table in a database the user had not chosen.
Impact
VECTOR32column refused UDTUSAGE onSYSUDTLIBcomes back as 3523 and is caught here instead of failing the job. Refusal messages become actionable rather than dead ends. With the Database field blank the message says the database it names is only the session default, so a DBA does not grant rights on a database nobody chose. With no Table Name configured the message says the check had to create a table to test the right, and that setting Table Name makes the check look the table up instead. That is the escape hatch for the one case this check can refuse a credential the job would not have needed. Destinations with a configured, existing table are unchanged: they still get the 1.11.16 INSERT/DELETE probe.create_destination()now names the database on the line that actually creates the table. The probe table is named in a log line before the CREATE runs, so a pod killed between CREATE and DROP leaves a traceable name. No other connector is touched;_write_denied_messageis called with the same arguments 1.11.18 introduced.UserError(422) messages gain trailing sentences (the blank-Database hint from 1.11.18, plus the new Table Name hint). Teradata 3523 is a new refusal code for the destination precheck: a credential that previously passed the CREATE probe and failed at upload now gets a 422 at check time.test_teradata.py:1329pins the upload-path wording and is unchanged. Nothing outside theteradataconnector changes; the shared_USER_FAULT_TERADATA_CODESmap is not edited by this branch.Risk / rollback
except, and 3523 means a refused right and nothing else, so it cannot turn a missing table into a permissions error.How it was verified
make test-unit: 1843 passed. The teradata module alone is 168, up from 166, the two new ones being the Table Name hint firing when the field is unset and staying off when it is set.make check(ruff check .): all checks passed.ruff formatwas deliberately not run: the file was already format-dirty atHEADbefore any edit here (verified withgit show HEAD:<file> | ruff format --check), 17 files repo-wide failruff format --check, and CI's lint job runsruff checkonly. Reformatting would have been unrelated churn on a review diff.unstructured_precheck_%table behind, with_escaped in the LIKE so the prefix cannot match by accident. It is gated onTERADATA_*credentials and skipped without them, so it did not run here._USER_FAULT_TERADATA_CODES, not from a server.Proof
Unit-level repro-first record for the new behaviour in this branch (the Table Name hint):
Red -- test written before the fix, against this branch's first commit:
Green -- after adding
_unset_table_hint()and appending it to both refusal messages:The second test in that pair (
..._omits_the_table_name_hint_when_one_is_configured) passes both before and after by construction; it is there to pin that the hint stays off for a customer who already set the field.Dependencies / merge order
none
Note for whoever merges: this takes 1.11.20, renumbered from 1.11.19 when #814 shipped that version on main. #816 is still at 1.11.17 while
mainis at 1.11.19, and is currently conflicting.scripts/version-sync.sh:125only rejects a version equal to main's, so a resolution there that keeps 1.11.17 goes backwards and CI will not catch it. Whichever of these two lands second needs its version re-checked by hand, not just its conflict resolved.Generated from Orca worktree
pk-teradata-precheck-followup(branchpk/teradata-precheck-review-followup).🤖 Generated with Claude Code