plsqlwks 0.1.3__tar.gz → 0.1.4__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.
Files changed (69) hide show
  1. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/PKG-INFO +87 -11
  2. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/README.md +86 -10
  3. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/__init__.py +1 -1
  4. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_db.py +48 -7
  5. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_input.py +26 -1
  6. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks.egg-info/PKG-INFO +87 -11
  7. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_oracle_integration.py +65 -0
  8. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_ui_keys.py +309 -1
  9. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/license.txt +0 -0
  10. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/__main__.py +0 -0
  11. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/config/__init__.py +0 -0
  12. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/config/loader.py +0 -0
  13. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/config/models.py +0 -0
  14. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/config/paths.py +0 -0
  15. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/config/session.py +0 -0
  16. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/config/settings.py +0 -0
  17. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/__init__.py +0 -0
  18. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/editing.py +0 -0
  19. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/execution.py +0 -0
  20. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/explain.py +0 -0
  21. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/health.py +0 -0
  22. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/metadata.py +0 -0
  23. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/models.py +0 -0
  24. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/session.py +0 -0
  25. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/sql_analysis.py +0 -0
  26. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/db/transactions.py +0 -0
  27. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/sqlbinds.py +0 -0
  28. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/sqlsplit.py +0 -0
  29. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/__init__.py +0 -0
  30. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app.py +0 -0
  31. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_editor.py +0 -0
  32. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_files.py +0 -0
  33. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_render.py +0 -0
  34. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_results.py +0 -0
  35. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/app_tabs_browser.py +0 -0
  36. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/browser.py +0 -0
  37. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/buffer.py +0 -0
  38. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/clipboard.py +0 -0
  39. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/commands.py +0 -0
  40. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/completion.py +0 -0
  41. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/constants.py +0 -0
  42. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/db_worker.py +0 -0
  43. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/display.py +0 -0
  44. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/errors.py +0 -0
  45. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/help.py +0 -0
  46. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/keys.py +0 -0
  47. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/menu.py +0 -0
  48. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/results.py +0 -0
  49. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/sql.py +0 -0
  50. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/state.py +0 -0
  51. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/ui/syntax.py +0 -0
  52. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks/workspace.py +0 -0
  53. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks.egg-info/SOURCES.txt +0 -0
  54. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks.egg-info/dependency_links.txt +0 -0
  55. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks.egg-info/entry_points.txt +0 -0
  56. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks.egg-info/requires.txt +0 -0
  57. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/plsqlwks.egg-info/top_level.txt +0 -0
  58. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/pyproject.toml +0 -0
  59. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/setup.cfg +0 -0
  60. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_config_workspace.py +0 -0
  61. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_db_editing.py +0 -0
  62. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_db_helpers.py +0 -0
  63. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_db_worker.py +0 -0
  64. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_packaging.py +0 -0
  65. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_pty_keys.py +0 -0
  66. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_rchar.py +0 -0
  67. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_sqlbinds.py +0 -0
  68. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_sqlsplit.py +0 -0
  69. {plsqlwks-0.1.3 → plsqlwks-0.1.4}/tests/test_ui_flows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plsqlwks
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: An ncurses SQL and PL/SQL workspace for Oracle databases.
5
5
  License-Expression: LicenseRef-plsqlwks-Donationware
6
6
  Project-URL: Repository, https://gitlab.com/unununu/plsqlwks
@@ -23,7 +23,7 @@ Dynamic: license-file
23
23
 
24
24
  # plsqlwks
25
25
 
26
- An ncurses SQL and PL/SQL workspace for the local Oracle database.
26
+ An ncurses SQL and PL/SQL workspace for Oracle databases.
27
27
 
28
28
  ![PLSQLWKS PREVIEW](https://gitlab.com/unununu/plsqlwks/-/raw/main/img/preview.png)
29
29
 
@@ -48,15 +48,24 @@ Override the defaults with environment variables:
48
48
  export ORACLE_USER=hr
49
49
  export ORACLE_PASSWORD_FILE=~/.config/plsqlwks/orapass
50
50
  export ORACLE_DSN='127.0.0.1:1521/free'
51
- export PLSQLWKS_WORKSPACE=/mnt/d/dev/plsqlwks/workspace
51
+ export PLSQLWKS_WORKSPACE=/path/to/workspace
52
+ export PLSQLWKS_MAX_ROWS=200
53
+ export PLSQLWKS_ARRAYSIZE=100
52
54
  ```
53
55
 
54
56
  Password files preserve leading and trailing spaces; only final CR/LF line
55
57
  endings are removed. On POSIX systems, create the file for the current user and
56
58
  protect it with `chmod 600`.
57
59
 
60
+ `PLSQLWKS_MAX_ROWS` sets the maximum number of rows in each result page, while
61
+ `PLSQLWKS_ARRAYSIZE` sets the Oracle cursor array size. Both values must be
62
+ positive integers.
63
+
58
64
  ## Install
59
65
 
66
+ Python 3.10 or newer is required, along with a compatible terminal and a Python
67
+ build that provides the standard-library `curses` module.
68
+
60
69
  For development, install the package in editable mode:
61
70
 
62
71
  ```bash
@@ -74,6 +83,21 @@ For a regular local install, use:
74
83
  python3 -m pip install .
75
84
  ```
76
85
 
86
+ Run the default test and lint checks from a development checkout with:
87
+
88
+ ```bash
89
+ python3 -m pytest -q
90
+ python3 -m ruff check .
91
+ ```
92
+
93
+ Oracle integration tests require the connection environment variables shown
94
+ above and an accessible password file. Enable every test group with:
95
+
96
+ ```bash
97
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
98
+ python3 -m pytest -q -rs
99
+ ```
100
+
77
101
  ## Run
78
102
 
79
103
  After installation, start the workspace with:
@@ -92,6 +116,15 @@ The CLI option takes precedence over `PLSQLWKS_WORKSPACE`. For compatibility, a
92
116
  source checkout that already contains a configured `workspace/` continues to
93
117
  use it and displays a migration notice; files are never moved automatically.
94
118
 
119
+ Choose the initial transaction mode for one invocation with `--manual` or
120
+ `--autocommit`. These options override `[database] autocommit` from the active
121
+ `config.ini`:
122
+
123
+ ```bash
124
+ plsqlwks --manual
125
+ plsqlwks --autocommit
126
+ ```
127
+
95
128
  You can also run it directly as a module from the source tree:
96
129
 
97
130
  ```bash
@@ -104,7 +137,25 @@ The app opens a split-screen terminal workspace:
104
137
  - results/messages below
105
138
  - command/status bar at the bottom
106
139
 
107
- The terminal UI uses the active UTF-8 locale for keyboard input and display. If a terminal starts in a plain `C` locale, the app falls back to `C.UTF-8` when available.
140
+ The terminal UI uses the active locale for keyboard input and display. If that
141
+ locale cannot be initialized, the app tries `C.UTF-8`, `en_US.UTF-8`, and
142
+ `UTF-8`. A valid plain `C` locale remains active, so configure a UTF-8 locale in
143
+ the shell when non-ASCII input or display is required.
144
+
145
+ Oracle operations run serially on one persistent background worker. The UI
146
+ continues to redraw and accept input while an operation runs, but it rejects a
147
+ second database operation until the active one finishes. `Ctrl-C` requests
148
+ interruption of the active operation. Reconnecting closes open result pages and
149
+ clears query results loaded by the previous connection. If a manual transaction
150
+ has pending changes, reconnect first asks whether to commit, roll back, discard
151
+ the session, or cancel. Commit or rollback must succeed before reconnect closes
152
+ the old connection. If either action fails, plsqlwks does not close the current
153
+ connection or clear its loaded results; reconnect can be tried again with an
154
+ explicit discard. Discard closes the session without resolving the transaction
155
+ explicitly, so Oracle rolls its uncommitted work back.
156
+ If the old connection is already dead and reports an error while closing,
157
+ plsqlwks still attempts the replacement connection and reports the close error
158
+ as a warning after a successful reconnect.
108
159
 
109
160
  After a successful quit, plsqlwks records the open file-backed tabs, active tab, and cursor positions in the managed `[session.tabs]` section of `config.ini`. Fresh platform-default workspaces use the user-config directory; explicitly selected and legacy workspaces keep `config.ini` inside the workspace for compatibility. On the next start the app tries each saved path independently, silently skips files that are missing or unreadable, and leaves the initial empty tab in place when none can be opened. A saved cursor position that no longer exists in its file starts at the beginning instead. Untitled, template, and generated schema tabs are not persisted because they do not have a file to reopen.
110
161
 
@@ -127,7 +178,7 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
127
178
  | `Alt-1`..`Alt-9` | Jump to visible file tab |
128
179
  | `Ctrl-Q` | Quit |
129
180
  | `Ctrl-C` while running | Interrupt the active database operation |
130
- | `Ctrl-Alt-C` | Commit the current transaction |
181
+ | `Ctrl-Alt-C` | Insert the active result-grid draft, or commit when no draft is active |
131
182
  | `Ctrl-Alt-R` | Roll back the current transaction |
132
183
 
133
184
  ### Editor
@@ -182,7 +233,12 @@ Set `[database] remember_bind_values = yes` in the active `config.ini` to prefil
182
233
 
183
234
  Editor search is literal and case-insensitive. `Ctrl-F` prompts for text and selects the found occurrence; `Ctrl-N` and `Ctrl-P` repeat the last search forward or backward with wraparound.
184
235
 
185
- Editor autocomplete is available with `Shift-Tab`. It completes PL/SQL keywords, current-schema object names, and table/view columns from cached or lazily loaded database metadata; multiple matches open a picker. Use `Alt-+` to reload schema-object metadata and clear cached column metadata.
236
+ Editor autocomplete is available with `Shift-Tab`. It completes PL/SQL keywords,
237
+ current-schema object names, and table/view columns from cached or lazily loaded
238
+ database metadata; multiple matches open a picker. Database-object and column
239
+ completion currently supports conventional unquoted identifiers; quoted
240
+ mixed-case names are not preserved. Use `Alt-+` to reload schema-object metadata
241
+ and clear cached column metadata.
186
242
 
187
243
  Editor syntax and explain-plan colors can be overridden in the active `config.ini` with color names or numeric curses color indexes. Unsupported values fall back to the built-in palette for the current terminal:
188
244
 
@@ -225,16 +281,35 @@ Read-only mode is a client-side guardrail that rejects statements which the SQL
225
281
  | `PageUp` / `PageDown` | Scroll explain-plan lines by page |
226
282
  | `Home` / `End` | Move to the first/last explain-plan line |
227
283
 
228
- When more result rows are available, `PageDown` at the loaded end fetches and appends the next result page. For simple single-table queries that select `ROWID`, press `Enter` on a non-`ROWID` cell to update that table column by rowid. Press `INS` in the grid to prepare a draft row at the top of the grid, edit its cells with `Enter`, use `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
284
+ When more result rows are available, `PageDown` at the loaded end fetches and
285
+ appends the next result page. For simple queries against one conventional
286
+ unquoted current-schema table that include `ROWID`, press `Enter` on a directly
287
+ selected unquoted table column to update it by rowid. Press `INS` in the grid to
288
+ prepare a draft row at the top of the grid, edit its cells with `Enter`, use
289
+ `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
229
290
  Database null values are displayed and entered as `<NULL>` in editable results. Plain `NULL` is stored as literal text.
230
- Grid edits compare the typed value that was originally loaded as well as the `ROWID`. If another session changes or deletes the row first, the edit is rejected and the query must be refreshed. NUMBER input uses decimal notation with a period; DATE and TIMESTAMP input uses ISO `YYYY-MM-DD[ HH:MM:SS[.ffffff]]`; RAW and BLOB input uses hexadecimal bytes. Character and CLOB input is preserved as entered. Types that cannot be compared without losing information, including time-zone timestamps and timestamps with precision above six, are read-only in the grid.
231
- Displayed CLOB and BLOB values are limited to the first 65,536 characters or bytes and include a truncation marker with the full size. Truncated LOB cells cannot be edited safely. CSV export uses the displayed result values and therefore retains the same explicit LOB limit; schema-browser DDL is always read in full.
291
+ Grid edits compare the selected cell's originally loaded typed value as well as
292
+ the `ROWID`. If another session changes that cell or deletes the row first, the
293
+ edit is rejected and the query must be refreshed. Changes to other cells in the
294
+ same row do not cause a conflict. NUMBER input uses decimal notation with a
295
+ period; DATE and TIMESTAMP input uses ISO
296
+ `YYYY-MM-DD[ HH:MM:SS[.ffffff]]`; RAW and BLOB input uses hexadecimal bytes.
297
+ Character and CLOB input is preserved as entered. Types that cannot be compared
298
+ without losing information, including time-zone timestamps and timestamps with
299
+ precision above six, are read-only in the grid.
300
+ Displayed CLOB and BLOB values are limited to the first 65,536 characters or
301
+ bytes and include a truncation marker with the full size. Truncated LOB cells
302
+ cannot be edited safely. Schema-browser DDL is always read in full.
232
303
  `F7` cycles a grid fullscreen view that starts with the table header on the first terminal line and uses the last line for data, an editor-only fullscreen view, and a split layout with 2/3 editor and 1/3 data grid.
233
304
 
234
305
  ### Schema Browser
235
306
 
236
307
  The `F9` schema browser groups tables, views, procedures, functions, packages, triggers, sequences, indexes, and private synonyms from the current schema. Type while the browser is focused to filter object names with a case-insensitive substring match; groups without matches are hidden and matching groups expand automatically without changing their saved expansion state.
237
308
 
309
+ Definition loading supports conventional unquoted identifiers. Quoted
310
+ mixed-case objects can appear in the browser but their definitions cannot
311
+ currently be loaded from it.
312
+
238
313
  | Key | Action |
239
314
  | --- | --- |
240
315
  | Printable text | Filter object names |
@@ -272,7 +347,8 @@ The `F9` schema browser groups tables, views, procedures, functions, packages, t
272
347
  workspace/
273
348
  sql/ saved SQL files
274
349
  plsql/ saved PL/SQL files
275
- results/ exported query output
350
+ results/ reserved output directory
276
351
  ```
277
352
 
278
- The first launch creates the workspace folders and starter SQL/PLSQL files.
353
+ The first launch creates the workspace folders and starter SQL/PLSQL files. The
354
+ terminal UI does not currently provide a query-export command.
@@ -1,6 +1,6 @@
1
1
  # plsqlwks
2
2
 
3
- An ncurses SQL and PL/SQL workspace for the local Oracle database.
3
+ An ncurses SQL and PL/SQL workspace for Oracle databases.
4
4
 
5
5
  ![PLSQLWKS PREVIEW](https://gitlab.com/unununu/plsqlwks/-/raw/main/img/preview.png)
6
6
 
@@ -25,15 +25,24 @@ Override the defaults with environment variables:
25
25
  export ORACLE_USER=hr
26
26
  export ORACLE_PASSWORD_FILE=~/.config/plsqlwks/orapass
27
27
  export ORACLE_DSN='127.0.0.1:1521/free'
28
- export PLSQLWKS_WORKSPACE=/mnt/d/dev/plsqlwks/workspace
28
+ export PLSQLWKS_WORKSPACE=/path/to/workspace
29
+ export PLSQLWKS_MAX_ROWS=200
30
+ export PLSQLWKS_ARRAYSIZE=100
29
31
  ```
30
32
 
31
33
  Password files preserve leading and trailing spaces; only final CR/LF line
32
34
  endings are removed. On POSIX systems, create the file for the current user and
33
35
  protect it with `chmod 600`.
34
36
 
37
+ `PLSQLWKS_MAX_ROWS` sets the maximum number of rows in each result page, while
38
+ `PLSQLWKS_ARRAYSIZE` sets the Oracle cursor array size. Both values must be
39
+ positive integers.
40
+
35
41
  ## Install
36
42
 
43
+ Python 3.10 or newer is required, along with a compatible terminal and a Python
44
+ build that provides the standard-library `curses` module.
45
+
37
46
  For development, install the package in editable mode:
38
47
 
39
48
  ```bash
@@ -51,6 +60,21 @@ For a regular local install, use:
51
60
  python3 -m pip install .
52
61
  ```
53
62
 
63
+ Run the default test and lint checks from a development checkout with:
64
+
65
+ ```bash
66
+ python3 -m pytest -q
67
+ python3 -m ruff check .
68
+ ```
69
+
70
+ Oracle integration tests require the connection environment variables shown
71
+ above and an accessible password file. Enable every test group with:
72
+
73
+ ```bash
74
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
75
+ python3 -m pytest -q -rs
76
+ ```
77
+
54
78
  ## Run
55
79
 
56
80
  After installation, start the workspace with:
@@ -69,6 +93,15 @@ The CLI option takes precedence over `PLSQLWKS_WORKSPACE`. For compatibility, a
69
93
  source checkout that already contains a configured `workspace/` continues to
70
94
  use it and displays a migration notice; files are never moved automatically.
71
95
 
96
+ Choose the initial transaction mode for one invocation with `--manual` or
97
+ `--autocommit`. These options override `[database] autocommit` from the active
98
+ `config.ini`:
99
+
100
+ ```bash
101
+ plsqlwks --manual
102
+ plsqlwks --autocommit
103
+ ```
104
+
72
105
  You can also run it directly as a module from the source tree:
73
106
 
74
107
  ```bash
@@ -81,7 +114,25 @@ The app opens a split-screen terminal workspace:
81
114
  - results/messages below
82
115
  - command/status bar at the bottom
83
116
 
84
- The terminal UI uses the active UTF-8 locale for keyboard input and display. If a terminal starts in a plain `C` locale, the app falls back to `C.UTF-8` when available.
117
+ The terminal UI uses the active locale for keyboard input and display. If that
118
+ locale cannot be initialized, the app tries `C.UTF-8`, `en_US.UTF-8`, and
119
+ `UTF-8`. A valid plain `C` locale remains active, so configure a UTF-8 locale in
120
+ the shell when non-ASCII input or display is required.
121
+
122
+ Oracle operations run serially on one persistent background worker. The UI
123
+ continues to redraw and accept input while an operation runs, but it rejects a
124
+ second database operation until the active one finishes. `Ctrl-C` requests
125
+ interruption of the active operation. Reconnecting closes open result pages and
126
+ clears query results loaded by the previous connection. If a manual transaction
127
+ has pending changes, reconnect first asks whether to commit, roll back, discard
128
+ the session, or cancel. Commit or rollback must succeed before reconnect closes
129
+ the old connection. If either action fails, plsqlwks does not close the current
130
+ connection or clear its loaded results; reconnect can be tried again with an
131
+ explicit discard. Discard closes the session without resolving the transaction
132
+ explicitly, so Oracle rolls its uncommitted work back.
133
+ If the old connection is already dead and reports an error while closing,
134
+ plsqlwks still attempts the replacement connection and reports the close error
135
+ as a warning after a successful reconnect.
85
136
 
86
137
  After a successful quit, plsqlwks records the open file-backed tabs, active tab, and cursor positions in the managed `[session.tabs]` section of `config.ini`. Fresh platform-default workspaces use the user-config directory; explicitly selected and legacy workspaces keep `config.ini` inside the workspace for compatibility. On the next start the app tries each saved path independently, silently skips files that are missing or unreadable, and leaves the initial empty tab in place when none can be opened. A saved cursor position that no longer exists in its file starts at the beginning instead. Untitled, template, and generated schema tabs are not persisted because they do not have a file to reopen.
87
138
 
@@ -104,7 +155,7 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
104
155
  | `Alt-1`..`Alt-9` | Jump to visible file tab |
105
156
  | `Ctrl-Q` | Quit |
106
157
  | `Ctrl-C` while running | Interrupt the active database operation |
107
- | `Ctrl-Alt-C` | Commit the current transaction |
158
+ | `Ctrl-Alt-C` | Insert the active result-grid draft, or commit when no draft is active |
108
159
  | `Ctrl-Alt-R` | Roll back the current transaction |
109
160
 
110
161
  ### Editor
@@ -159,7 +210,12 @@ Set `[database] remember_bind_values = yes` in the active `config.ini` to prefil
159
210
 
160
211
  Editor search is literal and case-insensitive. `Ctrl-F` prompts for text and selects the found occurrence; `Ctrl-N` and `Ctrl-P` repeat the last search forward or backward with wraparound.
161
212
 
162
- Editor autocomplete is available with `Shift-Tab`. It completes PL/SQL keywords, current-schema object names, and table/view columns from cached or lazily loaded database metadata; multiple matches open a picker. Use `Alt-+` to reload schema-object metadata and clear cached column metadata.
213
+ Editor autocomplete is available with `Shift-Tab`. It completes PL/SQL keywords,
214
+ current-schema object names, and table/view columns from cached or lazily loaded
215
+ database metadata; multiple matches open a picker. Database-object and column
216
+ completion currently supports conventional unquoted identifiers; quoted
217
+ mixed-case names are not preserved. Use `Alt-+` to reload schema-object metadata
218
+ and clear cached column metadata.
163
219
 
164
220
  Editor syntax and explain-plan colors can be overridden in the active `config.ini` with color names or numeric curses color indexes. Unsupported values fall back to the built-in palette for the current terminal:
165
221
 
@@ -202,16 +258,35 @@ Read-only mode is a client-side guardrail that rejects statements which the SQL
202
258
  | `PageUp` / `PageDown` | Scroll explain-plan lines by page |
203
259
  | `Home` / `End` | Move to the first/last explain-plan line |
204
260
 
205
- When more result rows are available, `PageDown` at the loaded end fetches and appends the next result page. For simple single-table queries that select `ROWID`, press `Enter` on a non-`ROWID` cell to update that table column by rowid. Press `INS` in the grid to prepare a draft row at the top of the grid, edit its cells with `Enter`, use `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
261
+ When more result rows are available, `PageDown` at the loaded end fetches and
262
+ appends the next result page. For simple queries against one conventional
263
+ unquoted current-schema table that include `ROWID`, press `Enter` on a directly
264
+ selected unquoted table column to update it by rowid. Press `INS` in the grid to
265
+ prepare a draft row at the top of the grid, edit its cells with `Enter`, use
266
+ `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
206
267
  Database null values are displayed and entered as `<NULL>` in editable results. Plain `NULL` is stored as literal text.
207
- Grid edits compare the typed value that was originally loaded as well as the `ROWID`. If another session changes or deletes the row first, the edit is rejected and the query must be refreshed. NUMBER input uses decimal notation with a period; DATE and TIMESTAMP input uses ISO `YYYY-MM-DD[ HH:MM:SS[.ffffff]]`; RAW and BLOB input uses hexadecimal bytes. Character and CLOB input is preserved as entered. Types that cannot be compared without losing information, including time-zone timestamps and timestamps with precision above six, are read-only in the grid.
208
- Displayed CLOB and BLOB values are limited to the first 65,536 characters or bytes and include a truncation marker with the full size. Truncated LOB cells cannot be edited safely. CSV export uses the displayed result values and therefore retains the same explicit LOB limit; schema-browser DDL is always read in full.
268
+ Grid edits compare the selected cell's originally loaded typed value as well as
269
+ the `ROWID`. If another session changes that cell or deletes the row first, the
270
+ edit is rejected and the query must be refreshed. Changes to other cells in the
271
+ same row do not cause a conflict. NUMBER input uses decimal notation with a
272
+ period; DATE and TIMESTAMP input uses ISO
273
+ `YYYY-MM-DD[ HH:MM:SS[.ffffff]]`; RAW and BLOB input uses hexadecimal bytes.
274
+ Character and CLOB input is preserved as entered. Types that cannot be compared
275
+ without losing information, including time-zone timestamps and timestamps with
276
+ precision above six, are read-only in the grid.
277
+ Displayed CLOB and BLOB values are limited to the first 65,536 characters or
278
+ bytes and include a truncation marker with the full size. Truncated LOB cells
279
+ cannot be edited safely. Schema-browser DDL is always read in full.
209
280
  `F7` cycles a grid fullscreen view that starts with the table header on the first terminal line and uses the last line for data, an editor-only fullscreen view, and a split layout with 2/3 editor and 1/3 data grid.
210
281
 
211
282
  ### Schema Browser
212
283
 
213
284
  The `F9` schema browser groups tables, views, procedures, functions, packages, triggers, sequences, indexes, and private synonyms from the current schema. Type while the browser is focused to filter object names with a case-insensitive substring match; groups without matches are hidden and matching groups expand automatically without changing their saved expansion state.
214
285
 
286
+ Definition loading supports conventional unquoted identifiers. Quoted
287
+ mixed-case objects can appear in the browser but their definitions cannot
288
+ currently be loaded from it.
289
+
215
290
  | Key | Action |
216
291
  | --- | --- |
217
292
  | Printable text | Filter object names |
@@ -249,7 +324,8 @@ The `F9` schema browser groups tables, views, procedures, functions, packages, t
249
324
  workspace/
250
325
  sql/ saved SQL files
251
326
  plsql/ saved PL/SQL files
252
- results/ exported query output
327
+ results/ reserved output directory
253
328
  ```
254
329
 
255
- The first launch creates the workspace folders and starter SQL/PLSQL files.
330
+ The first launch creates the workspace folders and starter SQL/PLSQL files. The
331
+ terminal UI does not currently provide a query-export command.
@@ -1,3 +1,3 @@
1
1
  """ncurses SQL and PL/SQL workspace."""
2
2
 
3
- __version__ = "0.1.3"
3
+ __version__ = "0.1.4"
@@ -41,6 +41,11 @@ class TransactionCompletion:
41
41
  cleanup_error: Exception | None = None
42
42
 
43
43
 
44
+ @dataclass(frozen=True)
45
+ class ConnectionCompletion:
46
+ old_session_close_error: Exception | None = None
47
+
48
+
44
49
  @dataclass(frozen=True)
45
50
  class TransactionModeChange:
46
51
  resolution: str | None
@@ -224,6 +229,12 @@ class AppDbMixin:
224
229
  worker.shutdown(timeout=timeout)
225
230
 
226
231
  def try_connect(self, force: bool = False) -> None:
232
+ if force:
233
+ self.reconnect_database()
234
+ return
235
+ self._start_connect(force=False)
236
+
237
+ def _start_connect(self, *, force: bool) -> None:
227
238
  if self.reject_if_db_operation_active():
228
239
  return
229
240
 
@@ -231,13 +242,23 @@ class AppDbMixin:
231
242
  self.close_all_result_continuations()
232
243
  self.invalidate_results_after_rollback()
233
244
 
234
- def connect(db: Any, progress: Callable[[str], None]) -> None:
245
+ def connect(db: Any, progress: Callable[[str], None]) -> ConnectionCompletion:
246
+ close_error = None
235
247
  if force:
236
- db.close()
248
+ try:
249
+ db.close()
250
+ except Exception as exc:
251
+ close_error = exc
237
252
  db.ensure_connected()
253
+ return ConnectionCompletion(close_error)
238
254
 
239
- def connected(_result: Any) -> None:
255
+ def connected(result: ConnectionCompletion) -> None:
240
256
  self.state.status = f"Connected as {self.state.config.user}"
257
+ if result.old_session_close_error is not None:
258
+ self.state.status += (
259
+ " (warning: old session close failed: "
260
+ f"{short_error(result.old_session_close_error)})"
261
+ )
241
262
 
242
263
  def connect_failed(exc: Exception) -> None:
243
264
  self.state.status = "Connection failed"
@@ -273,11 +294,19 @@ class AppDbMixin:
273
294
  return
274
295
  self.state.status = "Transaction mode unchanged"
275
296
 
276
- def prompt_pending_transaction(self, cancel_status: str) -> str | None:
297
+ def prompt_pending_transaction(
298
+ self,
299
+ cancel_status: str,
300
+ *,
301
+ allow_discard: bool = False,
302
+ ) -> str | None:
277
303
  if not has_uncommitted_changes(self.state.db):
278
304
  return "none"
305
+ choices = "c=commit, r=rollback, x=cancel"
306
+ if allow_discard:
307
+ choices = "c=commit, r=rollback, d=discard session, x=cancel"
279
308
  answer = self.prompt(
280
- "Pending transaction: c=commit, r=rollback, x=cancel",
309
+ f"Pending transaction: {choices}",
281
310
  "",
282
311
  )
283
312
  if answer is None:
@@ -288,6 +317,8 @@ class AppDbMixin:
288
317
  return "commit"
289
318
  if normalized in {"r", "rollback"}:
290
319
  return "rollback"
320
+ if allow_discard and normalized in {"d", "discard", "discard session"}:
321
+ return "discard"
291
322
  self.state.status = cancel_status
292
323
  return None
293
324
 
@@ -369,6 +400,8 @@ class AppDbMixin:
369
400
  self,
370
401
  after_success: Callable[[], None] | None = None,
371
402
  after_error: Callable[[], None] | None = None,
403
+ *,
404
+ preserve_results_on_error: bool = False,
372
405
  ) -> bool:
373
406
  if self.reject_if_db_operation_active():
374
407
  return False
@@ -380,7 +413,10 @@ class AppDbMixin:
380
413
 
381
414
  def commit_failed(exc: Exception) -> None:
382
415
  self.state.status = "Commit failed"
383
- self.set_results(["ERROR committing transaction:", *wrap_error(exc)])
416
+ self.set_results(
417
+ ["ERROR committing transaction:", *wrap_error(exc)],
418
+ clear_table=not preserve_results_on_error,
419
+ )
384
420
  if after_error is not None:
385
421
  after_error()
386
422
 
@@ -396,6 +432,8 @@ class AppDbMixin:
396
432
  self,
397
433
  after_success: Callable[[], None] | None = None,
398
434
  after_error: Callable[[], None] | None = None,
435
+ *,
436
+ preserve_results_on_error: bool = False,
399
437
  ) -> bool:
400
438
  if self.reject_if_db_operation_active():
401
439
  return False
@@ -423,7 +461,10 @@ class AppDbMixin:
423
461
 
424
462
  def rollback_failed(exc: Exception) -> None:
425
463
  self.state.status = "Rollback failed"
426
- self.set_results(["ERROR rolling back transaction:", *wrap_error(exc)])
464
+ self.set_results(
465
+ ["ERROR rolling back transaction:", *wrap_error(exc)],
466
+ clear_table=not preserve_results_on_error,
467
+ )
427
468
  if after_error is not None:
428
469
  after_error()
429
470
 
@@ -161,7 +161,32 @@ class AppInputMixin:
161
161
  self.commit_transaction()
162
162
 
163
163
  def reconnect_database(self) -> None:
164
- self.try_connect(force=True)
164
+ if self.reject_if_db_operation_active():
165
+ return
166
+ resolution = self.prompt_pending_transaction(
167
+ "Reconnect cancelled",
168
+ allow_discard=True,
169
+ )
170
+ if resolution is None:
171
+ return
172
+
173
+ def reconnect_after_resolution() -> None:
174
+ self._start_connect(force=True)
175
+
176
+ if resolution == "commit":
177
+ self.commit_transaction(
178
+ after_success=reconnect_after_resolution,
179
+ preserve_results_on_error=True,
180
+ )
181
+ elif resolution == "rollback":
182
+ self.rollback_transaction(
183
+ after_success=reconnect_after_resolution,
184
+ preserve_results_on_error=True,
185
+ )
186
+ elif resolution in {"discard", "none"}:
187
+ reconnect_after_resolution()
188
+ else:
189
+ self.state.status = "Reconnect cancelled"
165
190
 
166
191
  def refresh_workspace_file_list(self) -> None:
167
192
  self.state.files = _legacy_ui_attr("list_workspace_files", list_workspace_files)(self.state.config)