plsqlwks 0.1.7__tar.gz → 0.1.8__tar.gz
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.
- plsqlwks-0.1.8/ARCHITECTURE.md +86 -0
- plsqlwks-0.1.8/COMPATIBILITY.md +498 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/MANIFEST.in +5 -0
- plsqlwks-0.1.7/README.md → plsqlwks-0.1.8/PKG-INFO +178 -49
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/PLUGINS.md +39 -31
- plsqlwks-0.1.8/QUICKSTART.html +597 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/QUICKSTART.md +11 -5
- plsqlwks-0.1.7/PKG-INFO → plsqlwks-0.1.8/README.md +147 -76
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/__init__.py +1 -1
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/__main__.py +0 -1
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/config/__init__.py +3 -4
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/config/loader.py +2 -3
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/config/paths.py +2 -3
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/config/session.py +2 -3
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/config/settings.py +4 -6
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/__init__.py +14 -5
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/editing.py +155 -109
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/execution.py +74 -60
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/explain.py +3 -2
- plsqlwks-0.1.8/plsqlwks/db/identifiers.py +64 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/metadata.py +47 -8
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/models.py +4 -7
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/session.py +16 -9
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/sql_analysis.py +39 -2
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/transactions.py +28 -26
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/exporting.py +45 -25
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/html_exporting.py +21 -14
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/__init__.py +0 -1
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/_result_export.py +58 -19
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/api.py +2 -2
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/csv_export.py +58 -23
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/html_export.py +66 -28
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/loader.py +17 -15
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/plugins/xlsx_export.py +65 -30
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/sqlbinds.py +3 -9
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/sqlsplit.py +25 -25
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/__init__.py +12 -12
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/app.py +38 -35
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/application_controller.py +16 -2
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/browser.py +7 -4
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/buffer.py +6 -7
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/catalog.py +188 -42
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/clipboard.py +5 -5
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/commands.py +9 -3
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/completion.py +93 -34
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/constants.py +16 -1
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/db_operations.py +143 -65
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/db_session.py +21 -45
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/db_worker.py +36 -31
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/dialogs.py +13 -37
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/documents.py +8 -30
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/editor_controller.py +73 -71
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/errors.py +9 -20
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/help.py +13 -2
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/input_controller.py +15 -17
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/keys.py +88 -1
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/menu.py +6 -18
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/plugin_host.py +56 -17
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/ports.py +10 -2
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/query_controller.py +32 -59
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/renderer.py +43 -99
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/result_controller.py +53 -67
- plsqlwks-0.1.8/plsqlwks/ui/result_export.py +404 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/result_presenter.py +62 -99
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/results.py +44 -17
- plsqlwks-0.1.8/plsqlwks/ui/sql.py +226 -0
- plsqlwks-0.1.8/plsqlwks/ui/state.py +841 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/syntax.py +19 -6
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/viewport.py +254 -17
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/workspace.py +0 -1
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/xlsx_exporting.py +177 -79
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks.egg-info/SOURCES.txt +5 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/pyproject.toml +31 -20
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/pytest.ini +1 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/tests/conftest.py +6 -16
- plsqlwks-0.1.7/plsqlwks/ui/sql.py +0 -158
- plsqlwks-0.1.7/plsqlwks/ui/state.py +0 -284
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/license.txt +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/config/models.py +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/db/health.py +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/command_dispatcher.py +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/display.py +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plsqlwks/ui/key_reader.py +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plugin-requirements/csv-export/requirements.txt +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plugin-requirements/html-export/requirements.txt +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/plugin-requirements/xlsx-export/requirements.txt +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/rchar.py +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/requirements.txt +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/setup.cfg +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/tests/fixtures/config_exports.txt +0 -0
- {plsqlwks-0.1.7 → plsqlwks-0.1.8}/tests/fixtures/ui_exports.txt +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`plsqlwks` is a terminal-first application with one curses thread, one
|
|
4
|
+
long-lived database worker thread, and explicit boundaries around Oracle and
|
|
5
|
+
installed plugins.
|
|
6
|
+
|
|
7
|
+
## Runtime flow
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
KeyReader
|
|
11
|
+
-> InputController / CommandDispatcher
|
|
12
|
+
-> focused controller
|
|
13
|
+
-> DatabaseOperations
|
|
14
|
+
-> DatabaseWorker (OracleWorkspace)
|
|
15
|
+
-> completion event
|
|
16
|
+
-> DatabaseOperations.poll
|
|
17
|
+
-> ResultPresenter or operation callback
|
|
18
|
+
-> UIState
|
|
19
|
+
-> ViewportController
|
|
20
|
+
-> Renderer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`ui.app.App` is the composition root and owns the event loop. `KeyReader`
|
|
24
|
+
normalizes terminal input, `InputController` routes it by focus, and
|
|
25
|
+
`CommandDispatcher` resolves stable command IDs. A focused controller validates
|
|
26
|
+
input and prepares an operation on the curses thread. Local document, editor,
|
|
27
|
+
and plugin actions may finish there without crossing the database boundary.
|
|
28
|
+
|
|
29
|
+
Database work goes through `DatabaseOperations`, which allows one foreground
|
|
30
|
+
operation and submits a database-only callable to `DatabaseWorker`. The worker
|
|
31
|
+
owns the single `OracleWorkspace`, connection, cursors, and continuation
|
|
32
|
+
registry; it executes commands in FIFO order and emits progress or completion
|
|
33
|
+
events. Polling those events on the curses thread updates `UIState` and invokes
|
|
34
|
+
`ResultPresenter` or an operation-specific callback. The presenter materializes
|
|
35
|
+
status, focus, errors, and per-tab results. On the next frame,
|
|
36
|
+
`ViewportController` prepares a layout snapshot and `Renderer` reads that
|
|
37
|
+
snapshot plus `UIState` to draw curses. The curses thread never calls Oracle
|
|
38
|
+
directly, and the worker never draws or prompts.
|
|
39
|
+
|
|
40
|
+
## Transaction state
|
|
41
|
+
|
|
42
|
+
The worker-owned `OracleWorkspace` is the detailed source of truth. It tracks
|
|
43
|
+
the connection, `autocommit`, the client-side `read_only` guardrail, known
|
|
44
|
+
pending row changes, and whether uncommitted work has an unknown size or
|
|
45
|
+
outcome. After every command the UI receives only an immutable
|
|
46
|
+
`DbSessionState(connected, autocommit, read_only, has_uncommitted_changes)`
|
|
47
|
+
snapshot.
|
|
48
|
+
|
|
49
|
+
- In manual mode, successful DML adds its row count and PL/SQL marks pending
|
|
50
|
+
work as unknown. Rollback to a savepoint is also treated conservatively as
|
|
51
|
+
unknown.
|
|
52
|
+
- Successful commit, rollback, and DDL clear tracked pending state. Driver
|
|
53
|
+
transaction status may clear stale state or mark an otherwise untracked live
|
|
54
|
+
transaction as unknown. State is cleared only after the driver operation
|
|
55
|
+
succeeds.
|
|
56
|
+
- Changing from manual mode to autocommit and quitting with pending work require
|
|
57
|
+
an explicit commit, rollback, or cancel decision. Reconnect may additionally
|
|
58
|
+
discard a dead session. Interrupts and disconnects preserve conservative
|
|
59
|
+
warnings; live cursor and editing handles are detached while already
|
|
60
|
+
materialized rows remain viewable.
|
|
61
|
+
|
|
62
|
+
Database privileges remain the security boundary. Read-only mode only rejects
|
|
63
|
+
statements the client recognizes as writing.
|
|
64
|
+
|
|
65
|
+
## Plugin API boundary
|
|
66
|
+
|
|
67
|
+
The supported public surface is exactly the names exported by
|
|
68
|
+
`plsqlwks.plugins`. Installed factories are discovered through the
|
|
69
|
+
`plsqlwks.plugins` entry-point group, return `Plugin` and `PluginCommand`
|
|
70
|
+
metadata, and must match Plugin API version 1. The loader validates IDs,
|
|
71
|
+
versions, command uniqueness, and callability; a broken external plugin becomes
|
|
72
|
+
a startup warning instead of preventing the application from starting.
|
|
73
|
+
|
|
74
|
+
`PluginHost` and `UIPluginContext` are the sole UI adapters. A synchronous
|
|
75
|
+
handler can inspect an immutable snapshot of already loaded display rows, read
|
|
76
|
+
the results directory, detect an insert draft, prompt for text or overwrite
|
|
77
|
+
confirmation, set status, and report an error. A snapshot exposes no cursor or
|
|
78
|
+
continuation token, and only aligned immutable numeric source values may cross
|
|
79
|
+
alongside display text.
|
|
80
|
+
|
|
81
|
+
Plugin API version 1 does not expose the database worker, `OracleWorkspace`,
|
|
82
|
+
mutable results or `UIState`, curses objects, global shortcut registration,
|
|
83
|
+
lifecycle events, background jobs, settings schemas, hot reload, or
|
|
84
|
+
workspace-local executable modules. Plugins are trusted in-process Python code,
|
|
85
|
+
not a security sandbox. See [PLUGINS.md](PLUGINS.md) for the complete author
|
|
86
|
+
contract and examples.
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# Oracle compatibility
|
|
2
|
+
|
|
3
|
+
This document is the canonical compatibility and integration-test contract for
|
|
4
|
+
plsqlwks. Oracle Database 19c and Oracle AI Database 26ai are mandatory
|
|
5
|
+
release-gate targets. A target is described as continuously tested only after
|
|
6
|
+
both protected CI systems record ten consecutive qualifying green runs spanning
|
|
7
|
+
at least 30 days; until then, the narrower release-gate-target wording applies.
|
|
8
|
+
|
|
9
|
+
## Compatibility matrix
|
|
10
|
+
|
|
11
|
+
Both releases use python-oracledb Thin mode with password-file authentication.
|
|
12
|
+
`Easy Connect` means a `host:port/service` DSN; `full descriptor` means a
|
|
13
|
+
complete `(DESCRIPTION=...)` connect descriptor for the same service.
|
|
14
|
+
|
|
15
|
+
| Profile | Easy Connect coverage | Full-descriptor coverage |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Developer | Safety preflight and the complete live Oracle suite | Connection, identity, version, endpoint-fingerprint, and driver-mode smoke test |
|
|
18
|
+
| DML-only | Qualified SELECT/INSERT/UPDATE/DELETE against the developer-owned fixture, followed by cleanup and rollback; DDL must be denied | Connection, identity, version, endpoint-fingerprint, and driver-mode smoke test |
|
|
19
|
+
| Read-only | Qualified SELECT succeeds; DML and DDL must be denied by Oracle | Connection, identity, version, endpoint-fingerprint, and driver-mode smoke test |
|
|
20
|
+
|
|
21
|
+
The complete six-cell matrix runs for each release-gate target:
|
|
22
|
+
|
|
23
|
+
| Target | Accepted server version |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| Oracle Database 19c | Reported numeric major version is `19` |
|
|
26
|
+
| Oracle AI Database 26ai | Reported numeric release is `23.26` or later in the `23.x` family |
|
|
27
|
+
|
|
28
|
+
The developer suite creates, discovers, loads, and removes representative
|
|
29
|
+
tables, views, procedures, functions, packages, triggers, sequences, indexes,
|
|
30
|
+
and private synonyms, covering every object type advertised by the schema
|
|
31
|
+
browser. It also covers two contrasting session NLS profiles, quoted Unicode
|
|
32
|
+
identifiers, DATE and timestamp types, exact-boundary and approximately 1 MiB
|
|
33
|
+
CLOB/BLOB values, compiler warnings, cancellation of an active long-running
|
|
34
|
+
query, and bounded connection-failure recovery. These mutation-heavy scenarios
|
|
35
|
+
run in the developer/Easy Connect cell; the other five cells retain their
|
|
36
|
+
connection, identity, fingerprint, and privilege-boundary purposes.
|
|
37
|
+
|
|
38
|
+
A qualifying protected pipeline must select both Oracle target jobs after all
|
|
39
|
+
required hygiene, quality, Python 3.10/3.14 test, and build jobs; every selected
|
|
40
|
+
Oracle test must pass without a skip, allowed failure, cancellation, or pending
|
|
41
|
+
gate. A failed or incomplete qualifying pipeline restarts the consecutive-run
|
|
42
|
+
count. Ad-hoc local runs and experimental connection jobs never count toward
|
|
43
|
+
the public claim.
|
|
44
|
+
|
|
45
|
+
Other Oracle releases may be used for ad-hoc development, but are not part of
|
|
46
|
+
the compatibility claim. Thick mode, TNS aliases, wallets, TCPS-specific
|
|
47
|
+
configuration, external authentication, proxy authentication, and driver or
|
|
48
|
+
database failover configurations are not release-gating combinations.
|
|
49
|
+
|
|
50
|
+
### Time-zone timestamp behavior
|
|
51
|
+
|
|
52
|
+
Oracle `DATE` has no time-zone component. Plain `DATE` and `TIMESTAMP(6)` values
|
|
53
|
+
are materialized as NLS-independent Python datetimes and ISO display strings.
|
|
54
|
+
The current pinned python-oracledb Thin driver does not preserve the offset or
|
|
55
|
+
zone of `TIMESTAMP WITH TIME ZONE` and `TIMESTAMP WITH LOCAL TIME ZONE` values,
|
|
56
|
+
and it truncates precision beyond Python microseconds. Returning the resulting
|
|
57
|
+
naive datetime would falsely imply a lossless value.
|
|
58
|
+
|
|
59
|
+
For that reason, non-null zoned timestamp cells are read-only and use these
|
|
60
|
+
fail-safe display and export values:
|
|
61
|
+
|
|
62
|
+
- `<TIMESTAMP WITH TIME ZONE: lossless Thin-mode fetch unavailable; use TO_CHAR>`
|
|
63
|
+
- `<TIMESTAMP WITH LOCAL TIME ZONE: lossless Thin-mode fetch unavailable; use TO_CHAR>`
|
|
64
|
+
|
|
65
|
+
The result also carries a warning. Select a lossless companion string explicitly
|
|
66
|
+
when exact data is required, for example:
|
|
67
|
+
|
|
68
|
+
```sql
|
|
69
|
+
to_char(zoned_timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.FF9 TZH:TZM')
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For a local-time-zone column, first cast it at the required precision to
|
|
73
|
+
`TIMESTAMP(9) WITH TIME ZONE`; its rendered value follows `SESSIONTIMEZONE`.
|
|
74
|
+
Named-zone timestamp fetches remain an explicit Thin-mode limitation.
|
|
75
|
+
|
|
76
|
+
### Experimental connection matrices
|
|
77
|
+
|
|
78
|
+
No executable experimental connection job or runtime switch is part of the
|
|
79
|
+
current release gate. The following names reserve separate future contracts so
|
|
80
|
+
their results cannot be mistaken for coverage of the mandatory Thin matrix:
|
|
81
|
+
|
|
82
|
+
| Reserved matrix | Required isolated infrastructure |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `oracle-experimental-thick` | A separate process and protected runner image with reviewed Oracle Instant Client libraries |
|
|
85
|
+
| `oracle-experimental-wallet-tcps` | A dedicated TCPS endpoint and disposable protected wallet/configuration files |
|
|
86
|
+
| `oracle-experimental-tns-alias` | A disposable `tnsnames.ora`/`TNS_ADMIN` configuration and a dedicated alias |
|
|
87
|
+
|
|
88
|
+
Each future experiment must retain the identity, version, endpoint-fingerprint,
|
|
89
|
+
privilege, guard-token, and database-lock checks used by the base matrix. It
|
|
90
|
+
starts as a separate non-gating job with separate secrets and no artifacts or
|
|
91
|
+
caches. It can be added to the compatibility claim only after becoming
|
|
92
|
+
compulsory on protected pipelines and independently meeting the same ten-run,
|
|
93
|
+
30-day evidence rule.
|
|
94
|
+
|
|
95
|
+
The schema browser and metadata completion deliberately enumerate the current
|
|
96
|
+
login schema. Qualified cross-schema SQL is covered by the DML-only and
|
|
97
|
+
read-only profiles, but it does not add cross-schema objects to the browser.
|
|
98
|
+
ROWID-backed grid editing and insertion also remain limited to one base table
|
|
99
|
+
in the current schema. Exact quoted and Unicode table/column names are covered;
|
|
100
|
+
qualified cross-schema results are viewable but not grid-editable.
|
|
101
|
+
|
|
102
|
+
## Fail-closed safety gate
|
|
103
|
+
|
|
104
|
+
Matrix endpoints must be isolated, disposable databases containing no
|
|
105
|
+
production data. Before any selected Oracle test executes, the session-level
|
|
106
|
+
preflight performs all of these checks:
|
|
107
|
+
|
|
108
|
+
- every required value is present and every password/token path is a nonempty
|
|
109
|
+
regular file;
|
|
110
|
+
- the three usernames are distinct conventional unquoted identifiers;
|
|
111
|
+
- every profile connects through both DSN forms as its expected session user
|
|
112
|
+
and current schema, without proxy authentication, and each account is local
|
|
113
|
+
and not Oracle-maintained, inherited, implicit, or shard-wide;
|
|
114
|
+
- server version, `DB_UNIQUE_NAME`, container name, and service name match the
|
|
115
|
+
protected expected values, and the database role is `PRIMARY`;
|
|
116
|
+
- python-oracledb is using Thin mode with password authentication, and the
|
|
117
|
+
guard token matches the protected digest in the developer schema;
|
|
118
|
+
- effective/direct system privileges, direct/effective object grants, column
|
|
119
|
+
grants, outgoing grants, and empty roles exactly match the contracts below;
|
|
120
|
+
restricted schemas own no objects, every grant option is `NO`, and DBA,
|
|
121
|
+
user-administration, `ANY`, unlimited-tablespace, and public mutation paths
|
|
122
|
+
are rejected;
|
|
123
|
+
- on 26ai, both effective and directly received schema privileges are empty.
|
|
124
|
+
- the developer's effective `CONNECT_TIME` and `IDLE_TIME` profile limits are
|
|
125
|
+
both `UNLIMITED`, so they cannot terminate and roll back the guard session.
|
|
126
|
+
|
|
127
|
+
Any mismatch is an infrastructure failure, not a skipped test. Preflight errors
|
|
128
|
+
identify the failed setting but do not print usernames, DSNs, password paths,
|
|
129
|
+
passwords, or the guard token. The token is bound into this comparison and is
|
|
130
|
+
never stored or logged as plaintext:
|
|
131
|
+
|
|
132
|
+
```sql
|
|
133
|
+
lower(rawtohex(standard_hash(:token, 'SHA256')))
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Only after every non-mutating check succeeds, a dedicated developer connection
|
|
137
|
+
acquires the verified `PLSQLWKS_ORACLE_MATRIX` guard row with
|
|
138
|
+
`SELECT ... FOR UPDATE NOWAIT` (the zero-wait equivalent of `WAIT 0`) and holds
|
|
139
|
+
that row lock for the entire pytest session. If another
|
|
140
|
+
GitHub, GitLab, or local run already holds it, the newcomer fails before test
|
|
141
|
+
mutation instead of waiting or overlapping. Teardown rolls back and closes the
|
|
142
|
+
dedicated connection; Oracle also releases the lock automatically if the test
|
|
143
|
+
process or connection dies. This database-level mutex is the cross-platform
|
|
144
|
+
serialization boundary.
|
|
145
|
+
Before and after every selected Oracle test, the harness performs another
|
|
146
|
+
token-bound `SELECT ... FOR UPDATE NOWAIT` on the existing lock connection. It
|
|
147
|
+
first requires the driver's transaction-in-progress flag and never reconnects
|
|
148
|
+
that guard session. A lost connection, rolled-back lock, or competing holder
|
|
149
|
+
therefore fails closed before the next test can mutate.
|
|
150
|
+
|
|
151
|
+
DML probes use a new 32-character UUID hex key, delete their own probe row, and
|
|
152
|
+
roll back. DDL-denial probes run only after rollback because Oracle DDL can
|
|
153
|
+
implicitly commit pending work. If a denial probe unexpectedly creates an
|
|
154
|
+
object, the test removes that object in `finally` and fails. Read-only denial
|
|
155
|
+
probes disable the plsqlwks client guard so the Oracle privilege boundary is
|
|
156
|
+
what rejects DML and DDL.
|
|
157
|
+
|
|
158
|
+
CI contains application credentials only. It must never contain or use a DBA,
|
|
159
|
+
`SYS`, `SYSTEM`, user-management, grant-management, or schema-bootstrap
|
|
160
|
+
credential. Account creation and grants are a separate, deliberate DBA action.
|
|
161
|
+
The live harness proves that its ordinary sessions are not DBA sessions and
|
|
162
|
+
rejects every unexpected normal-session privilege. It intentionally cannot
|
|
163
|
+
query the password file and does not make repeated administrative-login
|
|
164
|
+
attempts, which could trigger authentication controls. Before storing any test
|
|
165
|
+
credential, a DBA must therefore verify that none of the three accounts has a
|
|
166
|
+
password-file administrative authorization:
|
|
167
|
+
|
|
168
|
+
```sql
|
|
169
|
+
select username, sysdba, sysoper, sysasm, sysbackup, sysdg, syskm
|
|
170
|
+
from v$pwfile_users
|
|
171
|
+
where username in (upper('<DEV_USER>'),
|
|
172
|
+
upper('<DML_USER>'),
|
|
173
|
+
upper('<READ_ONLY_USER>'));
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This query must return no rows. The private runner host must not be a database
|
|
177
|
+
host and its service accounts must not belong to Oracle operating-system
|
|
178
|
+
administration groups.
|
|
179
|
+
|
|
180
|
+
## Pre-provisioning contract
|
|
181
|
+
|
|
182
|
+
Provision three different conventional unquoted users on each disposable
|
|
183
|
+
target: `<DEV_USER>`, `<DML_USER>`, and `<READ_ONLY_USER>`. Use the site's secret
|
|
184
|
+
management process to create password-authenticated accounts. Give only the
|
|
185
|
+
developer exactly one positive finite quota, no greater than 64 MiB, on a
|
|
186
|
+
dedicated test tablespace; the other accounts must have no tablespace quota.
|
|
187
|
+
|
|
188
|
+
The effective rows from `SESSION_PRIVS` must be exactly:
|
|
189
|
+
|
|
190
|
+
- developer: `CREATE SESSION`, `ALTER SESSION`, `CREATE TABLE`, `CREATE VIEW`,
|
|
191
|
+
`CREATE PROCEDURE`, `CREATE TRIGGER`, `CREATE SEQUENCE`, and `CREATE SYNONYM`;
|
|
192
|
+
- DML-only and read-only: `CREATE SESSION`.
|
|
193
|
+
|
|
194
|
+
`SESSION_ROLES` must be empty for all three profiles, and all required
|
|
195
|
+
privileges must be direct grants. In particular, do not grant `CONNECT`,
|
|
196
|
+
`RESOURCE`, `DBA`, `UNLIMITED TABLESPACE`, catalog roles, or a privilege
|
|
197
|
+
containing `ANY`. This avoids trusting object privileges inherited through a
|
|
198
|
+
role that `SESSION_PRIVS` cannot expose.
|
|
199
|
+
|
|
200
|
+
The DML-only and read-only schemas must have zero rows in `USER_OBJECTS` and no
|
|
201
|
+
column-level grants. On 26ai, `SESSION_SCHEMA_PRIVS` and `USER_SCHEMA_PRIVS`
|
|
202
|
+
must also be empty for every profile. The developer's outgoing object grants
|
|
203
|
+
must be exactly the five fixture grants shown below; it must have no outgoing
|
|
204
|
+
column grants, no grant on the guard table, and no grant to `PUBLIC`. The
|
|
205
|
+
preflight also rejects any effective public table/column mutation privilege
|
|
206
|
+
visible to a profile.
|
|
207
|
+
|
|
208
|
+
Assign the developer a dedicated profile whose `CONNECT_TIME` and `IDLE_TIME`
|
|
209
|
+
are both `UNLIMITED`; the preflight verifies the effective values through
|
|
210
|
+
`USER_RESOURCE_LIMITS`. A DBA must also ensure that no Database Resource Manager
|
|
211
|
+
`MAX_IDLE_BLOCKER_TIME` or priority-transaction policy targets this isolated
|
|
212
|
+
test account, because such a policy could roll back the guard-row transaction.
|
|
213
|
+
|
|
214
|
+
After creating the accounts, a DBA applies the following template. Angle
|
|
215
|
+
brackets are deliberate non-executable placeholders and must be replaced with
|
|
216
|
+
the chosen safe identifiers.
|
|
217
|
+
|
|
218
|
+
```sql
|
|
219
|
+
grant create session, alter session, create table, create view,
|
|
220
|
+
create procedure, create trigger, create sequence, create synonym
|
|
221
|
+
to <DEV_USER>;
|
|
222
|
+
alter user <DEV_USER> quota 64M on <TEST_TABLESPACE>;
|
|
223
|
+
|
|
224
|
+
grant create session to <DML_USER>;
|
|
225
|
+
grant create session to <READ_ONLY_USER>;
|
|
226
|
+
|
|
227
|
+
grant execute on sys.dbms_output to <DEV_USER>;
|
|
228
|
+
grant execute on sys.dbms_metadata to <DEV_USER>;
|
|
229
|
+
grant execute on sys.dbms_xplan to <DEV_USER>;
|
|
230
|
+
grant execute on sys.dbms_session to <DEV_USER>;
|
|
231
|
+
grant execute on sys.dbms_utility to <DEV_USER>;
|
|
232
|
+
grant execute on sys.dbms_lob to <DEV_USER>;
|
|
233
|
+
|
|
234
|
+
grant execute on sys.dbms_output to <DML_USER>;
|
|
235
|
+
grant execute on sys.dbms_output to <READ_ONLY_USER>;
|
|
236
|
+
|
|
237
|
+
grant select on sys.v_$sql to <DEV_USER>;
|
|
238
|
+
grant select on sys.v_$sql_plan to <DEV_USER>;
|
|
239
|
+
grant select on sys.v_$sql_plan_statistics_all to <DEV_USER>;
|
|
240
|
+
grant select on sys.v_$session to <DEV_USER>;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
These are exact allowlists, not minimums. An additional direct system or object
|
|
244
|
+
grant, an admin/grant option, or any received role makes preflight fail.
|
|
245
|
+
|
|
246
|
+
Generate a high-entropy token outside Oracle, keep its plaintext only in a
|
|
247
|
+
protected local file or CI secret, and calculate its lowercase SHA-256 hex
|
|
248
|
+
digest.
|
|
249
|
+
Then connect as `<DEV_USER>` and create the two permanent fixtures. Replace
|
|
250
|
+
`<LOWERCASE_TOKEN_SHA256>` with the digest, never the plaintext token.
|
|
251
|
+
|
|
252
|
+
For example, this standard-library command creates a new token file
|
|
253
|
+
exclusively, sets mode `0600`, and prints only the digest to copy into the SQL:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
python3 - /protected/guard-token <<'PY'
|
|
257
|
+
from hashlib import sha256
|
|
258
|
+
from pathlib import Path
|
|
259
|
+
from secrets import token_hex
|
|
260
|
+
import sys
|
|
261
|
+
|
|
262
|
+
path = Path(sys.argv[1])
|
|
263
|
+
token = token_hex(32)
|
|
264
|
+
with path.open("x", encoding="ascii") as stream:
|
|
265
|
+
stream.write(token)
|
|
266
|
+
path.chmod(0o600)
|
|
267
|
+
print(sha256(token.encode("ascii")).hexdigest())
|
|
268
|
+
PY
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```sql
|
|
272
|
+
create table plsqlwks_compat_guard (
|
|
273
|
+
guard_name varchar2(30 char) primary key,
|
|
274
|
+
token_sha256 varchar2(64 char) not null,
|
|
275
|
+
constraint plsqlwks_guard_token_sha_ck check (
|
|
276
|
+
regexp_like(token_sha256, '^[0-9a-f]{64}$', 'c')
|
|
277
|
+
)
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
insert into plsqlwks_compat_guard (guard_name, token_sha256)
|
|
281
|
+
values ('PLSQLWKS_ORACLE_MATRIX', '<LOWERCASE_TOKEN_SHA256>');
|
|
282
|
+
|
|
283
|
+
create table plsqlwks_compat_fixture (
|
|
284
|
+
probe_id varchar2(32 char) primary key,
|
|
285
|
+
probe_value varchar2(200 char) not null
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
insert into plsqlwks_compat_fixture (probe_id, probe_value)
|
|
289
|
+
values ('READ_ONLY_BASELINE', 'compatibility fixture');
|
|
290
|
+
|
|
291
|
+
grant select, insert, update, delete on plsqlwks_compat_fixture to <DML_USER>;
|
|
292
|
+
grant select on plsqlwks_compat_fixture to <READ_ONLY_USER>;
|
|
293
|
+
commit;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Do not grant either limited user access to `PLSQLWKS_COMPAT_GUARD`. Verify each
|
|
297
|
+
profile in a new session with `SESSION_PRIVS`, `SESSION_ROLES`,
|
|
298
|
+
`USER_SYS_PRIVS`, `USER_ROLE_PRIVS`, `USER_TAB_PRIVS_RECD`,
|
|
299
|
+
`USER_COL_PRIVS_RECD`, `USER_TAB_PRIVS_MADE`, `USER_COL_PRIVS_MADE`, and
|
|
300
|
+
`USER_OBJECTS`, `USER_TS_QUOTAS`, and `USER_RESOURCE_LIMITS`. On 26ai also inspect `SESSION_SCHEMA_PRIVS` and
|
|
301
|
+
`USER_SCHEMA_PRIVS`. Obtain the fingerprint values used by CI with:
|
|
302
|
+
|
|
303
|
+
```sql
|
|
304
|
+
select sys_context('USERENV', 'DB_UNIQUE_NAME') as db_unique_name,
|
|
305
|
+
sys_context('USERENV', 'CON_NAME') as con_name,
|
|
306
|
+
sys_context('USERENV', 'SERVICE_NAME') as service_name
|
|
307
|
+
from dual;
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Running the matrix locally
|
|
311
|
+
|
|
312
|
+
The existing developer-only live suite remains available through
|
|
313
|
+
`python3 tools/dev.py test oracle`. A complete matrix run uses the
|
|
314
|
+
`oracle-matrix` profile, chooses `19c` or `26ai`, and supplies all of the
|
|
315
|
+
following generic variables:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
export ORACLE_USER='<developer user>'
|
|
319
|
+
export ORACLE_DSN='db.example.test:1521/service'
|
|
320
|
+
export ORACLE_PASSWORD_FILE='/protected/dev-password'
|
|
321
|
+
export PLSQLWKS_TEST_ORACLE_DESCRIPTOR_DSN='(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=db.example.test)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=service)))'
|
|
322
|
+
export PLSQLWKS_TEST_ORACLE_DML_USER='<dml user>'
|
|
323
|
+
export PLSQLWKS_TEST_ORACLE_DML_PASSWORD_FILE='/protected/dml-password'
|
|
324
|
+
export PLSQLWKS_TEST_ORACLE_READ_ONLY_USER='<read-only user>'
|
|
325
|
+
export PLSQLWKS_TEST_ORACLE_READ_ONLY_PASSWORD_FILE='/protected/read-only-password'
|
|
326
|
+
export PLSQLWKS_TEST_ORACLE_EXPECTED_DB_UNIQUE_NAME='<DB_UNIQUE_NAME>'
|
|
327
|
+
export PLSQLWKS_TEST_ORACLE_EXPECTED_CON_NAME='<container name>'
|
|
328
|
+
export PLSQLWKS_TEST_ORACLE_EXPECTED_SERVICE_NAME='<service name>'
|
|
329
|
+
export PLSQLWKS_TEST_ORACLE_GUARD_TOKEN_FILE='/protected/guard-token'
|
|
330
|
+
|
|
331
|
+
PLSQLWKS_TEST_ORACLE_TARGET=19c \
|
|
332
|
+
python3 tools/dev.py test oracle-matrix
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Password and token files must be nonempty regular files; protect them with
|
|
336
|
+
mode `0600` on POSIX systems. The developer DSN must be Easy Connect and the
|
|
337
|
+
descriptor variable must be a full descriptor for the same endpoint. Omitting
|
|
338
|
+
the matrix flag preserves the existing ad-hoc developer-suite behavior, but it
|
|
339
|
+
does not acquire the guard-row mutex. Never point an unguarded ad-hoc run at a
|
|
340
|
+
shared release-gating endpoint; use a separate disposable database or enable
|
|
341
|
+
the complete matrix.
|
|
342
|
+
|
|
343
|
+
## Protected CI release gates
|
|
344
|
+
|
|
345
|
+
Both CI systems define the same required 19c and 26ai matrix, use only private
|
|
346
|
+
self-hosted runners, and have no schedule or nightly trigger. GitLab explicitly
|
|
347
|
+
rejects scheduled pipelines and fork merge-request pipelines. Missing
|
|
348
|
+
configuration fails the selected job. Test output stays in the job log; the CI
|
|
349
|
+
definitions do not upload artifacts or use vendor-hosted remote caches from the
|
|
350
|
+
live Oracle jobs. Separate non-Oracle Python 3.10 and 3.14 gates retain JUnit,
|
|
351
|
+
Cobertura XML, and coverage JSON for seven days. Build and installed-wheel smoke
|
|
352
|
+
verification stay in one job so no distribution artifact transfer is needed.
|
|
353
|
+
The shared GitLab Oracle template explicitly sets both live-test opt-in flags,
|
|
354
|
+
declares blocking `needs` edges to repository hygiene, quality, both parallel
|
|
355
|
+
Python test instances, and build smoke, disables dependency artifact downloads,
|
|
356
|
+
and is non-interruptible. The GitHub jobs use the equivalent explicit flags,
|
|
357
|
+
blocking dependencies, and non-cancelling concurrency groups.
|
|
358
|
+
|
|
359
|
+
The authoritative CI definitions are `.github/workflows/ci.yml` and
|
|
360
|
+
`.gitlab-ci.yml`; a duplicate root `ci.yml` is intentionally not maintained.
|
|
361
|
+
Both definitions call the versioned `tools/dev.py` commands for installation,
|
|
362
|
+
linting, test profiles, hygiene, and build smoke checks, keeping shared behavior
|
|
363
|
+
in one locally runnable source. Installs and nested wheel smoke environments use
|
|
364
|
+
the exact Linux/Python 3.10/3.14 dependency set in `constraints/ci.txt`; dependency
|
|
365
|
+
updates are deliberate reviewed changes rather than upgrades performed by each job.
|
|
366
|
+
|
|
367
|
+
Every GitLab job runs a standard-library preflight before installation or test
|
|
368
|
+
work. It verifies the requested Python version, `curses`, usable PTYs, the selected
|
|
369
|
+
Python image, a disposable Docker environment, both required runner tags, and
|
|
370
|
+
required checkout inputs. Oracle jobs additionally validate every mapped matrix
|
|
371
|
+
variable, the job-specific workspace prerequisites, and all four private secret
|
|
372
|
+
files. Failures name only the unmet condition or variable label: the preflight
|
|
373
|
+
never prints a username, DSN, secret-file path or contents, raw operating-system
|
|
374
|
+
error, or configuration representation.
|
|
375
|
+
|
|
376
|
+
Every live Oracle job creates a new mode-`0700` workspace with a job-unique,
|
|
377
|
+
validated path and exports it as `PLSQLWKS_WORKSPACE`. The always-run cleanup
|
|
378
|
+
deletes it only after verifying a job-created ownership marker; GitLab binds
|
|
379
|
+
that marker to the job token with an HMAC. A pre-existing path fails the job
|
|
380
|
+
and is not deleted. This prevents stale `config.ini` settings on a persistent
|
|
381
|
+
private runner from changing transaction or read-only behavior.
|
|
382
|
+
|
|
383
|
+
### GitLab variables and jobs
|
|
384
|
+
|
|
385
|
+
For each prefix `ORACLE_19C` and `ORACLE_26AI`, configure these protected CI/CD
|
|
386
|
+
variables:
|
|
387
|
+
|
|
388
|
+
- `<PREFIX>_USER`, `<PREFIX>_DSN`, and file-type
|
|
389
|
+
`<PREFIX>_PASSWORD_FILE` for the developer profile;
|
|
390
|
+
- `<PREFIX>_DESCRIPTOR_DSN`;
|
|
391
|
+
- `<PREFIX>_DML_USER` and file-type `<PREFIX>_DML_PASSWORD_FILE`;
|
|
392
|
+
- `<PREFIX>_READ_ONLY_USER` and file-type
|
|
393
|
+
`<PREFIX>_READ_ONLY_PASSWORD_FILE`;
|
|
394
|
+
- `<PREFIX>_EXPECTED_DB_UNIQUE_NAME`, `<PREFIX>_EXPECTED_CON_NAME`, and
|
|
395
|
+
`<PREFIX>_EXPECTED_SERVICE_NAME`;
|
|
396
|
+
- file-type `<PREFIX>_GUARD_TOKEN_FILE`.
|
|
397
|
+
|
|
398
|
+
Mark values protected and mask values where GitLab supports masking. The two
|
|
399
|
+
explicit jobs, `oracle-integration-19c` and `oracle-integration-26ai`, run only
|
|
400
|
+
on protected refs, fail on an incomplete matrix, and use per-release resource
|
|
401
|
+
groups so two GitLab pipelines cannot mutate one endpoint at the same time.
|
|
402
|
+
They are release-gating jobs, not scheduled or nightly jobs. The guard-row
|
|
403
|
+
mutex also prevents overlap with GitHub or local runs.
|
|
404
|
+
|
|
405
|
+
All GitLab jobs require both private runner tags, `plsqlwks` and `docker`. Project
|
|
406
|
+
maintainers must disable GitLab instance runners and every non-compliant group or
|
|
407
|
+
project runner, protect the selected project runner, and disable **Run untagged
|
|
408
|
+
jobs**. Without a runner carrying both tags, jobs intentionally remain pending,
|
|
409
|
+
so hosted-runner minutes are never consumed.
|
|
410
|
+
|
|
411
|
+
### GitHub secrets and jobs
|
|
412
|
+
|
|
413
|
+
For each prefix `ORACLE_19C` and `ORACLE_26AI`, configure these protected
|
|
414
|
+
GitHub Actions secrets:
|
|
415
|
+
|
|
416
|
+
- `<PREFIX>_USER`, `<PREFIX>_DSN`, `<PREFIX>_PASSWORD`, and
|
|
417
|
+
`<PREFIX>_DESCRIPTOR_DSN`;
|
|
418
|
+
- `<PREFIX>_DML_USER` and `<PREFIX>_DML_PASSWORD`;
|
|
419
|
+
- `<PREFIX>_READ_ONLY_USER` and `<PREFIX>_READ_ONLY_PASSWORD`;
|
|
420
|
+
- `<PREFIX>_EXPECTED_DB_UNIQUE_NAME`, `<PREFIX>_EXPECTED_CON_NAME`, and
|
|
421
|
+
`<PREFIX>_EXPECTED_SERVICE_NAME`;
|
|
422
|
+
- `<PREFIX>_GUARD_TOKEN`.
|
|
423
|
+
|
|
424
|
+
Store these secrets in the protected `oracle-integration` environment and
|
|
425
|
+
restrict that environment to trusted branches and reviewers. The workflow
|
|
426
|
+
writes each password and token value to a temporary mode-`0600` file, maps that
|
|
427
|
+
path to the generic test variable, and removes the file during job cleanup.
|
|
428
|
+
Live Oracle jobs run only for repository-owned protected refs or a trusted
|
|
429
|
+
manual dispatch; they have no schedule, do not run for pull requests from
|
|
430
|
+
forks, and never expose these secrets to fork code. All other workflow jobs
|
|
431
|
+
also skip fork pull requests before selecting the private runner. Each job uses
|
|
432
|
+
`runs-on: [self-hosted, linux, x64, plsqlwks]`; no job names a GitHub-hosted
|
|
433
|
+
runner label. The two live jobs have separate per-release concurrency groups
|
|
434
|
+
and do not cancel an in-progress database run. Disable any hosted-runner
|
|
435
|
+
fallback allowed by the repository or organization policy. With no matching
|
|
436
|
+
private runner, a job must remain queued instead of consuming hosted-runner
|
|
437
|
+
minutes. The guard-row mutex additionally prevents overlap with GitLab or local
|
|
438
|
+
runs that use the same endpoint. Every referenced GitHub action is pinned to a
|
|
439
|
+
reviewed full commit SHA; update those pins only as a deliberate dependency
|
|
440
|
+
change.
|
|
441
|
+
|
|
442
|
+
### Private runner setup and first run
|
|
443
|
+
|
|
444
|
+
Use a dedicated Linux x64 runner host that contains no production credentials
|
|
445
|
+
or data and can reach only the required disposable Oracle endpoints and package
|
|
446
|
+
sources. Do not reuse a credentialed runner for untrusted fork code.
|
|
447
|
+
|
|
448
|
+
1. Install Python 3.10 and 3.14, Python build tooling, a compiler/toolchain, and
|
|
449
|
+
the platform prerequisites needed by this repository. For GitLab, use the
|
|
450
|
+
Docker executor because the Python 3.10/3.14 matrix selects
|
|
451
|
+
`python:<version>-slim` images and rejects other executor environments.
|
|
452
|
+
2. In the GitHub repository or organization runner UI, follow the official
|
|
453
|
+
[self-hosted runner setup](https://docs.github.com/en/actions/reference/runners/self-hosted-runners),
|
|
454
|
+
create a runner, and run the displayed registration command and short-lived
|
|
455
|
+
token on the host. Keep the default `self-hosted`, `linux`, and `x64` labels
|
|
456
|
+
and add `plsqlwks`; never store the registration token in the repository.
|
|
457
|
+
3. In the GitLab project or group runner UI, follow the official
|
|
458
|
+
[runner setup](https://docs.gitlab.com/ci/runners/), create a protected project
|
|
459
|
+
runner with tags `plsqlwks` and `docker`, disable **Run untagged jobs**, and run
|
|
460
|
+
the displayed registration command and token on the same host or a separately
|
|
461
|
+
isolated host. Select the Docker executor and `python:3.14-slim` as its default
|
|
462
|
+
image. Do not retain the token outside the runner's protected configuration.
|
|
463
|
+
4. Install both runner agents as system services under dedicated unprivileged
|
|
464
|
+
operating-system accounts. Do not store Oracle passwords or guard tokens in
|
|
465
|
+
the runner checkout or service environment. From the configured GitHub
|
|
466
|
+
runner directory, `sudo ./svc.sh install`, `sudo ./svc.sh start`, and
|
|
467
|
+
`sudo ./svc.sh status` install, start, and inspect that service. For a
|
|
468
|
+
package-installed GitLab runner, use `sudo gitlab-runner start`,
|
|
469
|
+
`sudo gitlab-runner status`, and `sudo gitlab-runner verify`.
|
|
470
|
+
5. Disable GitLab instance runners, non-compliant group/project runners, and
|
|
471
|
+
GitHub-hosted fallback in project, organization, and billing/policy settings.
|
|
472
|
+
Disable vendor-hosted remote-cache use for these workflows. Confirm a
|
|
473
|
+
deliberately unmatched label leaves a harmless test job pending/queued rather
|
|
474
|
+
than selecting a hosted runner, then cancel that job.
|
|
475
|
+
6. Add the protected GitLab variables and GitHub secrets listed above. Protect
|
|
476
|
+
the GitLab runner and variables, and configure the GitHub
|
|
477
|
+
`oracle-integration` environment for trusted branches or tags with the
|
|
478
|
+
desired approval rules.
|
|
479
|
+
7. Start the services. In GitHub open **Actions > CI > Run workflow** and choose
|
|
480
|
+
a protected branch. In GitLab open **Build > Pipelines > New pipeline** and
|
|
481
|
+
choose a protected ref. Verify in both job UIs that the selected runner has
|
|
482
|
+
the expected private identity and the required `plsqlwks` and `docker` tags
|
|
483
|
+
before allowing the Oracle commands to run.
|
|
484
|
+
8. Confirm both Oracle version jobs pass, leave only secret-free test output in
|
|
485
|
+
the logs, remove temporary credential files, and upload no Oracle artifacts
|
|
486
|
+
or caches.
|
|
487
|
+
Stop and investigate immediately if either platform selects an unexpected
|
|
488
|
+
runner.
|
|
489
|
+
|
|
490
|
+
Self-hosted runner hardware or cloud instances, network traffic, electricity,
|
|
491
|
+
maintenance, and Oracle licensing remain the operator's responsibility. The
|
|
492
|
+
private-runner guarantee is specifically that these definitions consume no
|
|
493
|
+
GitHub/GitLab hosted-runner minutes and upload no Oracle artifacts or remote
|
|
494
|
+
caches; only non-Oracle machine-readable test and coverage reports are retained
|
|
495
|
+
for seven days. A job waits when the private runner is unavailable. Confirm the
|
|
496
|
+
account-side boundary in the current [GitHub Actions billing documentation](https://docs.github.com/en/billing/concepts/product-billing/github-actions)
|
|
497
|
+
and [GitLab compute-minute documentation](https://docs.gitlab.com/ci/pipelines/instance_runner_compute_minutes/)
|
|
498
|
+
before the first run.
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
global-exclude *
|
|
4
4
|
|
|
5
5
|
include MANIFEST.in
|
|
6
|
+
include ARCHITECTURE.md
|
|
7
|
+
include COMPATIBILITY.md
|
|
6
8
|
include PLUGINS.md
|
|
9
|
+
include QUICKSTART.html
|
|
7
10
|
include QUICKSTART.md
|
|
8
11
|
include README.md
|
|
9
12
|
include license.txt
|
|
@@ -33,6 +36,7 @@ include plsqlwks/db/editing.py
|
|
|
33
36
|
include plsqlwks/db/execution.py
|
|
34
37
|
include plsqlwks/db/explain.py
|
|
35
38
|
include plsqlwks/db/health.py
|
|
39
|
+
include plsqlwks/db/identifiers.py
|
|
36
40
|
include plsqlwks/db/metadata.py
|
|
37
41
|
include plsqlwks/db/models.py
|
|
38
42
|
include plsqlwks/db/session.py
|
|
@@ -76,6 +80,7 @@ include plsqlwks/ui/ports.py
|
|
|
76
80
|
include plsqlwks/ui/query_controller.py
|
|
77
81
|
include plsqlwks/ui/renderer.py
|
|
78
82
|
include plsqlwks/ui/result_controller.py
|
|
83
|
+
include plsqlwks/ui/result_export.py
|
|
79
84
|
include plsqlwks/ui/result_presenter.py
|
|
80
85
|
include plsqlwks/ui/results.py
|
|
81
86
|
include plsqlwks/ui/sql.py
|