pythia-plsql 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ # Data dictionary reference
2
+
3
+ Which Oracle views back each pythia command, and what to know when going
4
+ beyond them with `pythia sql`.
5
+
6
+ ## Views behind the commands
7
+
8
+ | Question | View(s) | pythia command |
9
+ |---|---|---|
10
+ | What objects exist, status, last change | `ALL_OBJECTS` | `ls`, `invalid`, `similar` |
11
+ | PL/SQL source, line-exact | `ALL_SOURCE` | `src`, and the apply snapshot |
12
+ | Compile errors with line:column | `ALL_ERRORS` | `errors` |
13
+ | Procedure/function signatures | `ALL_ARGUMENTS` | `args` |
14
+ | Columns, types, nullability, defaults | `ALL_TAB_COLUMNS` | `cols` |
15
+ | Object-level dependency graph | `ALL_DEPENDENCIES` | `deps`, `impact` |
16
+ | Identifier declarations and usages | `ALL_IDENTIFIERS` (PL/Scope) | `plscope` |
17
+ | SQL statements inside PL/SQL, per table | `ALL_STATEMENTS` (PL/Scope) | `plscope` on a table |
18
+ | DDL reconstruction | `DBMS_METADATA.GET_DDL` | `ddl` |
19
+ | Session's dangerous privileges | `SESSION_PRIVS` | `check` warning |
20
+
21
+ `ALL_*` views show what the connected user has rights on. Empty results can
22
+ mean "does not exist" or "not visible to this user" — with a least-privilege
23
+ setup, prefer the proxy connection so visibility matches the schema owner's.
24
+
25
+ ## PL/Scope
26
+
27
+ PL/Scope data exists only for objects compiled while it was enabled:
28
+
29
+ ```sql
30
+ ALTER SESSION SET plscope_settings = 'IDENTIFIERS:ALL, STATEMENTS:ALL';
31
+ ALTER PROCEDURE <name> COMPILE;
32
+ ```
33
+
34
+ - Statement capture (`ALL_STATEMENTS`) needs Oracle 12.2+.
35
+ - Recompiling objects on a **shared** schema affects everyone using it —
36
+ agree it with the team first. pythia never runs these for you.
37
+ - Check what an object was compiled with: `ALL_PLSQL_OBJECT_SETTINGS`
38
+ (`PLSCOPE_SETTINGS` column).
39
+
40
+ ## DBMS_METADATA notes
41
+
42
+ `ddl` disables `STORAGE` and `SEGMENT_ATTRIBUTES` transforms deliberately:
43
+ segment clauses are noise for code review and burn context. If you need
44
+ tablespace/storage detail, ask with `pythia sql` against `DBA_/ALL_SEGMENTS`
45
+ (subject to privileges).
46
+
47
+ ## Licensing boundary — stay on the free side by default
48
+
49
+ Safe on every edition, no extra license:
50
+
51
+ - All `ALL_*` / `USER_*` dictionary views used above
52
+ - `V$SESSION`, `EXPLAIN PLAN`, Statspack
53
+
54
+ **Extra-cost** — do not query these unless the site confirms the license:
55
+
56
+ - `DBA_HIST_*` (AWR) — requires Diagnostics Pack
57
+ - SQL Performance Analyzer / SQL Tuning Advisor — Tuning/RAT packs
58
+
59
+ pythia's built-in queries are license-safe. When writing free-form `sql`,
60
+ keep to the free list unless told otherwise — a senior DBA reads restraint
61
+ here as a sign the tooling understands Oracle.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: plsql-impact
3
+ description: Use before proposing or writing any change to an Oracle object - a procedure, package, function, trigger, view, or table. Impact analysis comes first, because what depends on an object decides how careful the change must be, and Oracle invalidates dependents in cascade.
4
+ ---
5
+
6
+ # Impact Before Change
7
+
8
+ **Announce at start:** "Using plsql-impact to measure the blast radius first."
9
+
10
+ Changing an Oracle object recompiles or invalidates everything that depends
11
+ on it — immediately, schema-wide, for every user of a shared database. The
12
+ cost of knowing first is one command.
13
+
14
+ ## The Iron Law
15
+
16
+ ```
17
+ NO CHANGE PROPOSED WITHOUT ITS IMPACT MEASURED FIRST
18
+ ```
19
+
20
+ ## The Workflow
21
+
22
+ 1. `pythia impact <OBJECT> --depth 2` — everything that depends on it,
23
+ as a tree, ending with the line that matters:
24
+ `-- impact: N dependent objects, M currently VALID`.
25
+ 2. `pythia deps <OBJECT> --depth 2` — the other direction, what it uses;
26
+ read it when the change touches calls or table access.
27
+ 3. `pythia invalid` — the baseline. Anything already INVALID before the
28
+ change must not be blamed on the change later; apply compares against
29
+ this automatically, but you should know the starting state too.
30
+
31
+ ## Reading the numbers
32
+
33
+ | Result | What to do |
34
+ |---|---|
35
+ | 0 dependents | Say so and proceed; verify after apply anyway. |
36
+ | 1–9 dependents | List them to the developer alongside the proposal. |
37
+ | 10+ dependents, or any cross-schema dependent | Show the tree and get an explicit go-ahead **before writing any code**. |
38
+ | Dependents already INVALID | Point them out — the area is already unstable. |
39
+
40
+ Tables deserve the same treatment as code: `impact` on a table shows every
41
+ program that would be invalidated by an `ALTER`.
42
+
43
+ ## Red Flags — STOP if you catch yourself thinking
44
+
45
+ | Thought | Reality |
46
+ |---------|---------|
47
+ | "It's just a small helper" | Helpers are the most-depended-on objects there are. |
48
+ | "I'll check impact after writing the code" | Then the number cannot change the design. Too late. |
49
+ | "The preview in apply shows impact anyway" | That line is confirmation of a number you already knew — not discovery. |
50
+ | "It's a new object, nothing depends on it" | True — say that, with `impact` output as evidence, not as an assumption. |
51
+
52
+ ## Hand-off
53
+
54
+ Impact known and acceptable → `plsql-write` to write the change following
55
+ the codebase's conventions → `plsql-apply` to land it with preview and
56
+ verification.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: plsql-review
3
+ description: Use when reviewing PL/SQL - a changed procedure or package, a proposed file, or an object suspected of causing trouble. Combines the database's own correctness signals with the antipattern checklist, and reports findings anchored to line numbers.
4
+ ---
5
+
6
+ # Reviewing PL/SQL
7
+
8
+ **Announce at start:** "Using plsql-review — checking the database's signals first."
9
+
10
+ Review in two passes: what the database *knows* is wrong, then what the
11
+ checklist says is *likely* wrong. Machine signals first — they are free and
12
+ exact.
13
+
14
+ ## Pass 1 — the database's own verdict
15
+
16
+ 1. `pythia errors NAME` — compile errors and warnings with line:column.
17
+ An object that does not compile needs no style review yet.
18
+ 2. `pythia src NAME` — read the source with the compiler's line numbers, so
19
+ findings can be anchored (`line 47: ...`).
20
+ 3. `pythia impact NAME --depth 2` — how exposed is this object; a finding in
21
+ something with 40 dependents outranks the same finding in a leaf.
22
+ 4. For a proposed file (not yet applied): the compile verdict arrives at
23
+ apply time — say explicitly that compilation is still unverified.
24
+
25
+ ## Pass 2 — the antipattern checklist
26
+
27
+ Work through `reference/antipatterns.md` — and the project's own
28
+ `.pythia/conventions.md` when it exists, which outranks the generic list.
29
+ The seven entries, each with
30
+ wrong → right → why: row-by-row cursor loops, string-concatenated dynamic
31
+ SQL, `WHEN OTHERS THEN NULL`, `COMMIT` inside loops, hand-copied types
32
+ instead of `%TYPE`/`%ROWTYPE`, large OUT parameters without `NOCOPY`, and
33
+ convention drift against the codebase (`pythia similar` shows the house
34
+ style).
35
+
36
+ ## Reporting findings
37
+
38
+ - Anchor every finding: `line N: <what> — <why it bites> — <the fix>`.
39
+ - Severity order: breaks correctness → silently loses data or errors →
40
+ performance at scale → style drift.
41
+ - Verify the claim before writing it: read the actual lines with `src`;
42
+ never report from memory of the diff alone.
43
+ - A clean review says what was checked, not just "looks good": compiles
44
+ clean, no new INVALID, checklist passed.
45
+
46
+ ## When NOT to use this skill
47
+
48
+ - Reviewing whether a change is *safe to apply* — that is `plsql-impact`
49
+ plus `plsql-apply`'s preview; this skill judges the code itself.
50
+ - Reviewing non-PL/SQL application code — outside this skill's scope.
@@ -0,0 +1,106 @@
1
+ # PL/SQL antipatterns
2
+
3
+ Seven findings that recur in real codebases. Each: how it looks, what to
4
+ write instead, and why it bites. Anchor findings to line numbers from
5
+ `pythia src`.
6
+
7
+ ## 1. Row-by-row cursor loop doing DML
8
+
9
+ **Wrong**
10
+ ```sql
11
+ FOR r IN (SELECT id FROM t_order WHERE status = 'NEW') LOOP
12
+ UPDATE t_order SET status = 'DONE' WHERE id = r.id;
13
+ END LOOP;
14
+ ```
15
+ **Right** — `BULK COLLECT ... LIMIT` + `FORALL` (see patterns.md for the
16
+ full template), or better: one set-based `UPDATE` when no per-row logic
17
+ exists.
18
+ **Why** — a context switch per row; thousands of rows means thousands of
19
+ switches. The set-based form is also atomic.
20
+
21
+ ## 2. Dynamic SQL built by concatenation
22
+
23
+ **Wrong**
24
+ ```sql
25
+ EXECUTE IMMEDIATE 'DELETE FROM t_log WHERE id = ' || p_id;
26
+ ```
27
+ **Right**
28
+ ```sql
29
+ EXECUTE IMMEDIATE 'DELETE FROM t_log WHERE id = :1' USING p_id;
30
+ ```
31
+ **Why** — SQL injection when the value is user-reachable, and a hard parse
32
+ per distinct value even when it is not: shared-pool churn that hits the
33
+ whole instance, not just this session.
34
+
35
+ ## 3. `WHEN OTHERS THEN NULL`
36
+
37
+ **Wrong**
38
+ ```sql
39
+ EXCEPTION WHEN OTHERS THEN NULL;
40
+ ```
41
+ **Right** — handle the exceptions you can name; for the rest, log and
42
+ `RAISE`.
43
+ **Why** — every future bug in the block becomes silent wrong data. The
44
+ worst version is around a `SELECT INTO`, where it also hides
45
+ `TOO_MANY_ROWS` — a data-quality alarm.
46
+
47
+ ## 4. `COMMIT` inside a loop
48
+
49
+ **Wrong**
50
+ ```sql
51
+ FOR r IN c LOOP
52
+ process(r);
53
+ COMMIT; -- "to be safe"
54
+ END LOOP;
55
+ ```
56
+ **Right** — commit once, at the transaction's owner (usually the outermost
57
+ caller). For huge volumes, batch commits at a documented interval are a
58
+ deliberate, named decision — not a reflex.
59
+ **Why** — a failure mid-loop leaves a half-applied state that can be
60
+ neither completed nor rolled back; it also breaks any caller that thought
61
+ it owned the transaction, and `ORA-01555` risk goes up, not down.
62
+
63
+ ## 5. Hand-copied types instead of `%TYPE` / `%ROWTYPE`
64
+
65
+ **Wrong**
66
+ ```sql
67
+ v_name VARCHAR2(50); -- the column is VARCHAR2(100) since 2024
68
+ ```
69
+ **Right**
70
+ ```sql
71
+ v_name customers.name%TYPE;
72
+ ```
73
+ **Why** — the declaration is a snapshot that silently drifts from the
74
+ schema; the failure arrives later as `VALUE_ERROR` on production data.
75
+
76
+ ## 6. Large OUT parameters without `NOCOPY`
77
+
78
+ **Wrong**
79
+ ```sql
80
+ PROCEDURE render(p_out IN OUT CLOB);
81
+ ```
82
+ **Right**
83
+ ```sql
84
+ PROCEDURE render(p_out IN OUT NOCOPY CLOB);
85
+ ```
86
+ **Why** — IN OUT copies on entry and exit; for CLOBs and big collections
87
+ that is real memory and time per call.
88
+
89
+ ## 7. Style drift from the codebase
90
+
91
+ **Wrong** — new naming scheme, new cursor style, new comment format,
92
+ introduced silently in one procedure.
93
+ **Right** — `pythia similar` + `pythia src` on the top hits; match them.
94
+ Propose style improvements to the developer as a separate conversation.
95
+ **Why** — a mixed-style codebase costs every future reader; consistency is
96
+ a feature maintainers can feel.
97
+
98
+ ## Reporting format
99
+
100
+ ```
101
+ line 47: WHEN OTHERS THEN NULL — swallows every error including
102
+ TOO_MANY_ROWS — handle NO_DATA_FOUND explicitly, log and RAISE the rest
103
+ ```
104
+
105
+ Severity order: correctness → silent data/error loss → performance at
106
+ scale → style.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: plsql-setup
3
+ description: Use when setting pythia up for a project or a machine - writing connections.json, creating a least-privilege agent user, wiring the SQLcl MCP server, or diagnosing a connection that will not open. Also the place to start when check prints a privilege warning.
4
+ ---
5
+
6
+ # Setting Up
7
+
8
+ **Announce at start:** "Using plsql-setup to configure the database access."
9
+
10
+ Set up in this order: connection first (everything else needs it), the
11
+ least-privilege user second (the only protection that cannot be bypassed),
12
+ SQLcl MCP last (optional, reads only).
13
+
14
+ ## 1. Connection
15
+
16
+ Create `.pythia/connections.json` at the project root — `pythia install`
17
+ scaffolds it (pip installs: `pip install pythia-plsql`), or copy
18
+ `examples/connections.example.json` from a clone. Fill it in. Rules pythia
19
+ applies:
20
+
21
+ - One entry: used as-is. Several entries: the path segment directly under the
22
+ project root picks one (`root/DEV/...` → `DEV`), else the entry named by a
23
+ top-level `"default": "<name>"`. Ambiguity is an error, never a guess.
24
+ - `--conn NAME` and `PYTHIA_CONNECTION` override everything; the
25
+ `PYTHIA_USER/PYTHIA_PASSWORD/PYTHIA_DSN` variables bypass the file.
26
+ - The file holds credentials: it is gitignored — keep it that way, and keep
27
+ it out of chat and screenshots.
28
+
29
+ Verify with `pythia check`: it prints who you are connected as and object
30
+ counts. A failure names the connection and what to check.
31
+
32
+ ## 2. The agent's database user — the real protection
33
+
34
+ The policy file is an application-side fence; **Oracle grants are the only
35
+ layer an agent cannot walk around.** Oracle has no clean per-object form of
36
+ "may edit PL/SQL in that schema" — compiling into another schema needs
37
+ `CREATE ANY PROCEDURE`, which spans every schema on the instance.
38
+
39
+ The workable pattern is **proxy authentication**: a logon-only user that
40
+ connects *through* the schema owner. The agent never learns the owner's
41
+ password, revocation is one statement, the audit trail shows who really
42
+ connected, and the blast radius is the one development schema.
43
+
44
+ Run `examples/agent-user-setup.example.sql` (as a DBA, names adapted), then
45
+ set the connection's user to `"agent_user[schema_owner]"`.
46
+
47
+ Oracle's own guidance for LLM access, follow it: grant minimum privileges,
48
+ never point an LLM at a production database, audit its activity regularly.
49
+
50
+ `pythia check` warns on one line when the session holds `%ANY%` privileges or
51
+ runs as the schema owner directly. The goal state is: no warning.
52
+
53
+ ## 3. SQLcl MCP server (optional, reads only)
54
+
55
+ If SQLcl 25.2+ is installed, agents can read through Oracle's official MCP
56
+ server: command `sql -mcp`. Example client config:
57
+
58
+ ```json
59
+ {"mcpServers": {"sqlcl": {"command": "sql", "args": ["-mcp"]}}}
60
+ ```
61
+
62
+ - Keep the default restrict level (`-R 4`, most restrictive): it blocks host
63
+ commands and `@` scripts. Note it does NOT block DML/DDL inside `run-sql` —
64
+ which is why **writes never go through MCP**: only `pythia apply` has the
65
+ snapshot, preview, verify and journal.
66
+ - Built-in audit, worth telling the DBA about: every interaction lands in
67
+ `DBTOOLS$MCP_LOG`; `V$SESSION.MODULE` shows the MCP client and
68
+ `V$SESSION.ACTION` the LLM's name; generated SQL carries an
69
+ `/* LLM in use */` comment.
70
+
71
+ ## Done when
72
+
73
+ - `pythia check` connects, shows the right schema, and prints **no**
74
+ privilege warning.
75
+ - `pythia policy` prints the write policy and the rollback table.
76
+ - The credentials file is untracked (`git status` does not show it).
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: plsql-skill-author
3
+ description: Use when the developer wants their own way of working captured as a new skill - "make a skill for how we do X", a house convention or preference worth teaching every future session, a team ritual, or the same correction arriving for the second time. Interviews the developer, mines the real conventions from the database, and writes a skill in this pack's format.
4
+ ---
5
+
6
+ # Authoring a New Skill
7
+
8
+ **Announce at start:** "Using plsql-skill-author — let's capture how you actually work."
9
+
10
+ A skill is a decision captured so it never has to be re-argued. Capture what
11
+ the team *actually does* — with evidence from the database — not what anyone
12
+ remembers about it.
13
+
14
+ ## Step 1 — Interview, one question at a time
15
+
16
+ Ask, in order, waiting for each answer:
17
+
18
+ 1. **What task is this for?» One skill per concern — "our report procedure
19
+ ritual" is one skill; "everything about our team" is not.
20
+ 2. **When should it trigger?** Collect the developer's *own words* — the
21
+ phrases they would type ("làm báo cáo", "clone the export proc", an error
22
+ code, a file pattern). These go into the description verbatim; triggering
23
+ lives or dies on them.
24
+ 3. **Which parts are law, which are taste?** Hard rules get an Iron Law and
25
+ a red-flags table; preferences get a "default unless told otherwise".
26
+ 4. **What does *wrong* look like?** Past incidents make the best red-flag
27
+ rows.
28
+
29
+ ## Step 2 — Mine the evidence
30
+
31
+ Memory lies; the schema does not. Before writing a line:
32
+
33
+ - `pythia similar <TYPICAL_NAME>` → which family of programs embodies this
34
+ workflow; open the top hits with `pythia src` and extract the *actual*
35
+ naming, parameter and structure conventions.
36
+ - `pythia args` / `pythia cols` for the signatures and types the skill will
37
+ tell people to use.
38
+ - Save two or three short **verbatim** snippets as examples — real code
39
+ outranks invented code.
40
+
41
+ ## Step 3 — Draft in the house format
42
+
43
+ - Frontmatter: kebab-case `name` matching the folder; `description` that
44
+ opens with the trigger condition (`Use when ...`) and contains the
45
+ developer's phrases from step 1.
46
+ - Body budget ~150 lines. Long material (templates, checklists, snippet
47
+ libraries) goes to `reference/` and is linked, not pasted.
48
+ - Structure menu — use what the answers call for, skip the rest:
49
+ Iron Law (one line, caps) · numbered workflow · red-flags table
50
+ ("thought → reality") · "When NOT to use" · an **Announce at start** line.
51
+ - Write in the language the team works in; this pack is English, a private
52
+ team skill may be Vietnamese — the developer decides.
53
+
54
+ **Not everything needs a skill.** Pure naming and style rules travel better
55
+ as `.pythia/conventions.json` (machine-checked at every apply preview) plus
56
+ `.pythia/conventions.md` (prose the agent reads). Reserve skills for
57
+ workflows — things with steps, gates and judgment.
58
+
59
+ ## Step 4 — Place it
60
+
61
+ User skills live in the **developer's project**, e.g.
62
+ `.claude/skills/<name>/SKILL.md` for Claude Code (other agents: the layout
63
+ `skills/<name>/SKILL.md` works with `npx skills add`). Never write into the
64
+ installed pythia pack — updates would overwrite it, and the pack's lint
65
+ enforces its own fixed skill list.
66
+
67
+ ## Step 5 — Verify it triggers
68
+
69
+ In a **fresh session**, give a task phrased the way the developer would
70
+ really ask (step 2's phrases). The skill must activate unprompted. If it
71
+ does not, the description is the bug: sharpen it with the exact phrases that
72
+ failed, and test again.
73
+
74
+ ## Keep it alive
75
+
76
+ When the developer corrects the agent for the same thing twice, propose
77
+ folding that correction into the skill — that is the skill earning its keep.
78
+ Retire rules that stopped being true; a stale skill is worse than none.
79
+
80
+ ## Before sharing outside the team
81
+
82
+ Run the hygiene check this pack applies to itself: no hosts, schema names,
83
+ credentials, or internal identifiers in the skill or its references. What is
84
+ fine in a private repo is a leak in a public one.
85
+
86
+ ## Red Flags — STOP if you catch yourself thinking
87
+
88
+ | Thought | Reality |
89
+ |---------|---------|
90
+ | "I'll write it from memory of this chat" | Mine `similar`/`src` first — the codebase is the authority. |
91
+ | "One big skill covering everything" | One concern per skill, or nothing triggers cleanly. |
92
+ | "Paste the whole style guide in" | Budget is ~150 lines; long material goes to reference/. |
93
+ | "The description can be generic" | Generic descriptions never trigger. Use the developer's own phrases. |
94
+ | "It works, no need to test triggering" | Untested triggering = a skill nobody ever sees again. |
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: plsql-write
3
+ description: Use when writing or modifying PL/SQL source - a procedure, function, package, trigger, or view - after impact is known. The codebase's conventions already exist; copy them instead of inventing style, and anchor every type to the database's reality.
4
+ ---
5
+
6
+ # Writing PL/SQL
7
+
8
+ **Announce at start:** "Using plsql-write — mining the codebase's conventions first."
9
+
10
+ **Before anything:** if the project has `.pythia/conventions.md`, read it —
11
+ house rules there outrank every generic pattern below, and
12
+ `pythia conventions` shows the naming patterns the apply preview will check.
13
+
14
+ A codebase with thousands of procedures has already decided how procedures
15
+ look. Your job is to write one that a maintainer cannot tell from the
16
+ existing ones — not to introduce a better style.
17
+
18
+ ## The Workflow
19
+
20
+ 1. **Find the models.** `pythia similar <NEW_NAME>` ranks existing programs
21
+ sharing name tokens; the `MATCHED_TOKENS` column says why. Open the top
22
+ two or three with `pythia src` and imitate: naming, parameter prefixes,
23
+ cursor style, error handling, comment style.
24
+ 2. **Anchor the signatures.** For every program you call:
25
+ `pythia args NAME` — real parameter names, order, types, defaults. Never
26
+ guess a signature from memory of similar code.
27
+ 3. **Anchor the types.** For every table you touch: `pythia cols TABLE` —
28
+ then declare variables with `%TYPE` / `%ROWTYPE` against those columns
29
+ instead of copying the current type by hand. The declaration then
30
+ survives column changes.
31
+ 4. **Write the file.** Rules the write path enforces — follow them here:
32
+ - **One object per file.** Package spec and body are two files.
33
+ - End the file with the PL/SQL block's `;` and a final line holding `/`.
34
+ - Name the object unqualified, or qualified with the exact schema the
35
+ connection targets — a mismatch is refused at apply time.
36
+ 5. **Check yourself before handing off.** Reread against
37
+ `reference/patterns.md` — cursor and bulk patterns, exception discipline,
38
+ bind variables, commit ownership.
39
+
40
+ ## Conventions outrank preferences
41
+
42
+ If the codebase writes explicit cursors where you would write `FOR r IN`,
43
+ write explicit cursors. If its parameter prefixes look dated, use them
44
+ anyway. A mixed-style codebase is worse than a consistently dated one —
45
+ propose style changes to the developer separately, never silently.
46
+
47
+ ## When NOT to use this skill
48
+
49
+ - Understanding existing code → `plsql-explore`.
50
+ - Measuring what a change breaks → `plsql-impact` (must already be done).
51
+ - Landing the file on the database → `plsql-apply`, always — never
52
+ `run-sql`, never `sqlplus`.
@@ -0,0 +1,99 @@
1
+ # PL/SQL patterns worth copying
2
+
3
+ Generic patterns for when the codebase gives no model to imitate. When the
4
+ codebase disagrees with this file, **the codebase wins** — consistency beats
5
+ preference.
6
+
7
+ ## Anchor declarations to the dictionary
8
+
9
+ ```sql
10
+ -- fragile: copies today's type by hand
11
+ v_customer_name VARCHAR2(100);
12
+
13
+ -- survives column changes
14
+ v_customer_name customers.name%TYPE;
15
+ r_order orders%ROWTYPE;
16
+ ```
17
+
18
+ ## Bulk over row-by-row
19
+
20
+ ```sql
21
+ -- slow at scale: one context switch per row
22
+ FOR r IN (SELECT id FROM t_order WHERE status = 'NEW') LOOP
23
+ process_order(r.id);
24
+ END LOOP;
25
+
26
+ -- bulk: fetch in batches, write with FORALL
27
+ DECLARE
28
+ TYPE t_ids IS TABLE OF t_order.id%TYPE;
29
+ v_ids t_ids;
30
+ CURSOR c IS SELECT id FROM t_order WHERE status = 'NEW';
31
+ BEGIN
32
+ OPEN c;
33
+ LOOP
34
+ FETCH c BULK COLLECT INTO v_ids LIMIT 500;
35
+ EXIT WHEN v_ids.COUNT = 0;
36
+ FORALL i IN 1 .. v_ids.COUNT
37
+ UPDATE t_order SET status = 'DONE' WHERE id = v_ids(i);
38
+ END LOOP;
39
+ CLOSE c;
40
+ END;
41
+ ```
42
+
43
+ The `LIMIT` matters: unbounded `BULK COLLECT` trades the row-switch problem
44
+ for a memory problem.
45
+
46
+ ## Exceptions: handle what you can name, re-raise the rest
47
+
48
+ ```sql
49
+ BEGIN
50
+ ...
51
+ EXCEPTION
52
+ WHEN NO_DATA_FOUND THEN
53
+ RETURN NULL; -- a decision, made on purpose
54
+ WHEN OTHERS THEN
55
+ log_error(sqlcode, sqlerrm, 'PKG_ORDER.calc_total');
56
+ RAISE; -- never swallow; the caller must know
57
+ END;
58
+ ```
59
+
60
+ `WHEN OTHERS` without `RAISE` (or `RAISE_APPLICATION_ERROR`) converts every
61
+ future bug into silent wrong data.
62
+
63
+ ## Dynamic SQL: binds, never concatenation
64
+
65
+ ```sql
66
+ -- injection + hard parse per call
67
+ EXECUTE IMMEDIATE 'SELECT total FROM t_order WHERE id = ' || p_id INTO v_t;
68
+
69
+ -- shared cursor, safe
70
+ EXECUTE IMMEDIATE 'SELECT total FROM t_order WHERE id = :1'
71
+ INTO v_t USING p_id;
72
+ ```
73
+
74
+ Identifiers (table names) cannot be bound — validate them against
75
+ `ALL_TABLES`/`ALL_OBJECTS` before splicing, and say so in a comment.
76
+
77
+ ## Commit ownership
78
+
79
+ Utility and business procedures do not `COMMIT`; the outermost caller — the
80
+ one that knows the transaction's boundaries — does. A procedure that commits
81
+ mid-loop turns a failed run into a half-applied state nobody can reason
82
+ about, and breaks the caller's ability to roll back.
83
+
84
+ ## Large OUT parameters
85
+
86
+ ```sql
87
+ PROCEDURE render_report(p_clob IN OUT NOCOPY CLOB);
88
+ ```
89
+
90
+ Without `NOCOPY`, IN OUT copies the value both ways; for big CLOBs and
91
+ collections that is real memory and time. (It is a hint, not a guarantee —
92
+ still worth writing.)
93
+
94
+ ## Naming: mine, don't invent
95
+
96
+ Before naming anything: `pythia similar <intended_name>` and copy the
97
+ dominant token order, prefixes and casing of the top hits. Parameter
98
+ prefixes, cursor names, and constant style come from `pythia src` of a
99
+ neighboring program — not from this file.