plsqlwks 0.1.3__tar.gz → 0.1.5__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.5}/PKG-INFO +87 -14
  2. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/README.md +86 -13
  3. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/__init__.py +1 -1
  4. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_db.py +48 -7
  5. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_input.py +26 -1
  6. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks.egg-info/PKG-INFO +87 -14
  7. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_oracle_integration.py +65 -0
  8. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_ui_keys.py +309 -1
  9. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/license.txt +0 -0
  10. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/__main__.py +0 -0
  11. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/config/__init__.py +0 -0
  12. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/config/loader.py +0 -0
  13. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/config/models.py +0 -0
  14. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/config/paths.py +0 -0
  15. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/config/session.py +0 -0
  16. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/config/settings.py +0 -0
  17. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/__init__.py +0 -0
  18. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/editing.py +0 -0
  19. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/execution.py +0 -0
  20. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/explain.py +0 -0
  21. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/health.py +0 -0
  22. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/metadata.py +0 -0
  23. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/models.py +0 -0
  24. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/session.py +0 -0
  25. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/sql_analysis.py +0 -0
  26. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/db/transactions.py +0 -0
  27. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/sqlbinds.py +0 -0
  28. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/sqlsplit.py +0 -0
  29. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/__init__.py +0 -0
  30. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app.py +0 -0
  31. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_editor.py +0 -0
  32. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_files.py +0 -0
  33. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_render.py +0 -0
  34. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_results.py +0 -0
  35. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/app_tabs_browser.py +0 -0
  36. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/browser.py +0 -0
  37. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/buffer.py +0 -0
  38. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/clipboard.py +0 -0
  39. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/commands.py +0 -0
  40. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/completion.py +0 -0
  41. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/constants.py +0 -0
  42. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/db_worker.py +0 -0
  43. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/display.py +0 -0
  44. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/errors.py +0 -0
  45. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/help.py +0 -0
  46. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/keys.py +0 -0
  47. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/menu.py +0 -0
  48. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/results.py +0 -0
  49. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/sql.py +0 -0
  50. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/state.py +0 -0
  51. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/ui/syntax.py +0 -0
  52. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks/workspace.py +0 -0
  53. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks.egg-info/SOURCES.txt +0 -0
  54. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks.egg-info/dependency_links.txt +0 -0
  55. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks.egg-info/entry_points.txt +0 -0
  56. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks.egg-info/requires.txt +0 -0
  57. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/plsqlwks.egg-info/top_level.txt +0 -0
  58. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/pyproject.toml +0 -0
  59. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/setup.cfg +0 -0
  60. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_config_workspace.py +0 -0
  61. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_db_editing.py +0 -0
  62. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_db_helpers.py +0 -0
  63. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_db_worker.py +0 -0
  64. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_packaging.py +0 -0
  65. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_pty_keys.py +0 -0
  66. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_rchar.py +0 -0
  67. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_sqlbinds.py +0 -0
  68. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/tests/test_sqlsplit.py +0 -0
  69. {plsqlwks-0.1.3 → plsqlwks-0.1.5}/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.5
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
@@ -65,15 +74,27 @@ python3 -m pip install -e '.[dev]'
65
74
 
66
75
  `oracledb` is the only runtime dependency. Supporting functionality uses the Python standard library.
67
76
 
68
- See the [release checklist](https://gitlab.com/unununu/plsqlwks/-/blob/main/RELEASING.md)
69
- for the quality, packaging, and GitLab release process.
70
-
71
77
  For a regular local install, use:
72
78
 
73
79
  ```bash
74
80
  python3 -m pip install .
75
81
  ```
76
82
 
83
+ Run the default test and lint checks from a development checkout with:
84
+
85
+ ```bash
86
+ python3 -m pytest -q
87
+ python3 -m ruff check .
88
+ ```
89
+
90
+ Oracle integration tests require the connection environment variables shown
91
+ above and an accessible password file. Enable every test group with:
92
+
93
+ ```bash
94
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
95
+ python3 -m pytest -q -rs
96
+ ```
97
+
77
98
  ## Run
78
99
 
79
100
  After installation, start the workspace with:
@@ -92,6 +113,15 @@ The CLI option takes precedence over `PLSQLWKS_WORKSPACE`. For compatibility, a
92
113
  source checkout that already contains a configured `workspace/` continues to
93
114
  use it and displays a migration notice; files are never moved automatically.
94
115
 
116
+ Choose the initial transaction mode for one invocation with `--manual` or
117
+ `--autocommit`. These options override `[database] autocommit` from the active
118
+ `config.ini`:
119
+
120
+ ```bash
121
+ plsqlwks --manual
122
+ plsqlwks --autocommit
123
+ ```
124
+
95
125
  You can also run it directly as a module from the source tree:
96
126
 
97
127
  ```bash
@@ -104,7 +134,25 @@ The app opens a split-screen terminal workspace:
104
134
  - results/messages below
105
135
  - command/status bar at the bottom
106
136
 
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.
137
+ The terminal UI uses the active locale for keyboard input and display. If that
138
+ locale cannot be initialized, the app tries `C.UTF-8`, `en_US.UTF-8`, and
139
+ `UTF-8`. A valid plain `C` locale remains active, so configure a UTF-8 locale in
140
+ the shell when non-ASCII input or display is required.
141
+
142
+ Oracle operations run serially on one persistent background worker. The UI
143
+ continues to redraw and accept input while an operation runs, but it rejects a
144
+ second database operation until the active one finishes. `Ctrl-C` requests
145
+ interruption of the active operation. Reconnecting closes open result pages and
146
+ clears query results loaded by the previous connection. If a manual transaction
147
+ has pending changes, reconnect first asks whether to commit, roll back, discard
148
+ the session, or cancel. Commit or rollback must succeed before reconnect closes
149
+ the old connection. If either action fails, plsqlwks does not close the current
150
+ connection or clear its loaded results; reconnect can be tried again with an
151
+ explicit discard. Discard closes the session without resolving the transaction
152
+ explicitly, so Oracle rolls its uncommitted work back.
153
+ If the old connection is already dead and reports an error while closing,
154
+ plsqlwks still attempts the replacement connection and reports the close error
155
+ as a warning after a successful reconnect.
108
156
 
109
157
  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
158
 
@@ -127,7 +175,7 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
127
175
  | `Alt-1`..`Alt-9` | Jump to visible file tab |
128
176
  | `Ctrl-Q` | Quit |
129
177
  | `Ctrl-C` while running | Interrupt the active database operation |
130
- | `Ctrl-Alt-C` | Commit the current transaction |
178
+ | `Ctrl-Alt-C` | Insert the active result-grid draft, or commit when no draft is active |
131
179
  | `Ctrl-Alt-R` | Roll back the current transaction |
132
180
 
133
181
  ### Editor
@@ -182,7 +230,12 @@ Set `[database] remember_bind_values = yes` in the active `config.ini` to prefil
182
230
 
183
231
  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
232
 
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.
233
+ Editor autocomplete is available with `Shift-Tab`. It completes PL/SQL keywords,
234
+ current-schema object names, and table/view columns from cached or lazily loaded
235
+ database metadata; multiple matches open a picker. Database-object and column
236
+ completion currently supports conventional unquoted identifiers; quoted
237
+ mixed-case names are not preserved. Use `Alt-+` to reload schema-object metadata
238
+ and clear cached column metadata.
186
239
 
187
240
  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
241
 
@@ -225,16 +278,35 @@ Read-only mode is a client-side guardrail that rejects statements which the SQL
225
278
  | `PageUp` / `PageDown` | Scroll explain-plan lines by page |
226
279
  | `Home` / `End` | Move to the first/last explain-plan line |
227
280
 
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.
281
+ When more result rows are available, `PageDown` at the loaded end fetches and
282
+ appends the next result page. For simple queries against one conventional
283
+ unquoted current-schema table that include `ROWID`, press `Enter` on a directly
284
+ selected unquoted table column to update it by rowid. Press `INS` in the grid to
285
+ prepare a draft row at the top of the grid, edit its cells with `Enter`, use
286
+ `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
229
287
  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.
288
+ Grid edits compare the selected cell's originally loaded typed value as well as
289
+ the `ROWID`. If another session changes that cell or deletes the row first, the
290
+ edit is rejected and the query must be refreshed. Changes to other cells in the
291
+ same row do not cause a conflict. NUMBER input uses decimal notation with a
292
+ period; DATE and TIMESTAMP input uses ISO
293
+ `YYYY-MM-DD[ HH:MM:SS[.ffffff]]`; RAW and BLOB input uses hexadecimal bytes.
294
+ Character and CLOB input is preserved as entered. Types that cannot be compared
295
+ without losing information, including time-zone timestamps and timestamps with
296
+ precision above six, are read-only in the grid.
297
+ Displayed CLOB and BLOB values are limited to the first 65,536 characters or
298
+ bytes and include a truncation marker with the full size. Truncated LOB cells
299
+ cannot be edited safely. Schema-browser DDL is always read in full.
232
300
  `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
301
 
234
302
  ### Schema Browser
235
303
 
236
304
  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
305
 
306
+ Definition loading supports conventional unquoted identifiers. Quoted
307
+ mixed-case objects can appear in the browser but their definitions cannot
308
+ currently be loaded from it.
309
+
238
310
  | Key | Action |
239
311
  | --- | --- |
240
312
  | Printable text | Filter object names |
@@ -272,7 +344,8 @@ The `F9` schema browser groups tables, views, procedures, functions, packages, t
272
344
  workspace/
273
345
  sql/ saved SQL files
274
346
  plsql/ saved PL/SQL files
275
- results/ exported query output
347
+ results/ reserved output directory
276
348
  ```
277
349
 
278
- The first launch creates the workspace folders and starter SQL/PLSQL files.
350
+ The first launch creates the workspace folders and starter SQL/PLSQL files. The
351
+ 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
@@ -42,15 +51,27 @@ python3 -m pip install -e '.[dev]'
42
51
 
43
52
  `oracledb` is the only runtime dependency. Supporting functionality uses the Python standard library.
44
53
 
45
- See the [release checklist](https://gitlab.com/unununu/plsqlwks/-/blob/main/RELEASING.md)
46
- for the quality, packaging, and GitLab release process.
47
-
48
54
  For a regular local install, use:
49
55
 
50
56
  ```bash
51
57
  python3 -m pip install .
52
58
  ```
53
59
 
60
+ Run the default test and lint checks from a development checkout with:
61
+
62
+ ```bash
63
+ python3 -m pytest -q
64
+ python3 -m ruff check .
65
+ ```
66
+
67
+ Oracle integration tests require the connection environment variables shown
68
+ above and an accessible password file. Enable every test group with:
69
+
70
+ ```bash
71
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
72
+ python3 -m pytest -q -rs
73
+ ```
74
+
54
75
  ## Run
55
76
 
56
77
  After installation, start the workspace with:
@@ -69,6 +90,15 @@ The CLI option takes precedence over `PLSQLWKS_WORKSPACE`. For compatibility, a
69
90
  source checkout that already contains a configured `workspace/` continues to
70
91
  use it and displays a migration notice; files are never moved automatically.
71
92
 
93
+ Choose the initial transaction mode for one invocation with `--manual` or
94
+ `--autocommit`. These options override `[database] autocommit` from the active
95
+ `config.ini`:
96
+
97
+ ```bash
98
+ plsqlwks --manual
99
+ plsqlwks --autocommit
100
+ ```
101
+
72
102
  You can also run it directly as a module from the source tree:
73
103
 
74
104
  ```bash
@@ -81,7 +111,25 @@ The app opens a split-screen terminal workspace:
81
111
  - results/messages below
82
112
  - command/status bar at the bottom
83
113
 
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.
114
+ The terminal UI uses the active locale for keyboard input and display. If that
115
+ locale cannot be initialized, the app tries `C.UTF-8`, `en_US.UTF-8`, and
116
+ `UTF-8`. A valid plain `C` locale remains active, so configure a UTF-8 locale in
117
+ the shell when non-ASCII input or display is required.
118
+
119
+ Oracle operations run serially on one persistent background worker. The UI
120
+ continues to redraw and accept input while an operation runs, but it rejects a
121
+ second database operation until the active one finishes. `Ctrl-C` requests
122
+ interruption of the active operation. Reconnecting closes open result pages and
123
+ clears query results loaded by the previous connection. If a manual transaction
124
+ has pending changes, reconnect first asks whether to commit, roll back, discard
125
+ the session, or cancel. Commit or rollback must succeed before reconnect closes
126
+ the old connection. If either action fails, plsqlwks does not close the current
127
+ connection or clear its loaded results; reconnect can be tried again with an
128
+ explicit discard. Discard closes the session without resolving the transaction
129
+ explicitly, so Oracle rolls its uncommitted work back.
130
+ If the old connection is already dead and reports an error while closing,
131
+ plsqlwks still attempts the replacement connection and reports the close error
132
+ as a warning after a successful reconnect.
85
133
 
86
134
  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
135
 
@@ -104,7 +152,7 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
104
152
  | `Alt-1`..`Alt-9` | Jump to visible file tab |
105
153
  | `Ctrl-Q` | Quit |
106
154
  | `Ctrl-C` while running | Interrupt the active database operation |
107
- | `Ctrl-Alt-C` | Commit the current transaction |
155
+ | `Ctrl-Alt-C` | Insert the active result-grid draft, or commit when no draft is active |
108
156
  | `Ctrl-Alt-R` | Roll back the current transaction |
109
157
 
110
158
  ### Editor
@@ -159,7 +207,12 @@ Set `[database] remember_bind_values = yes` in the active `config.ini` to prefil
159
207
 
160
208
  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
209
 
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.
210
+ Editor autocomplete is available with `Shift-Tab`. It completes PL/SQL keywords,
211
+ current-schema object names, and table/view columns from cached or lazily loaded
212
+ database metadata; multiple matches open a picker. Database-object and column
213
+ completion currently supports conventional unquoted identifiers; quoted
214
+ mixed-case names are not preserved. Use `Alt-+` to reload schema-object metadata
215
+ and clear cached column metadata.
163
216
 
164
217
  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
218
 
@@ -202,16 +255,35 @@ Read-only mode is a client-side guardrail that rejects statements which the SQL
202
255
  | `PageUp` / `PageDown` | Scroll explain-plan lines by page |
203
256
  | `Home` / `End` | Move to the first/last explain-plan line |
204
257
 
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.
258
+ When more result rows are available, `PageDown` at the loaded end fetches and
259
+ appends the next result page. For simple queries against one conventional
260
+ unquoted current-schema table that include `ROWID`, press `Enter` on a directly
261
+ selected unquoted table column to update it by rowid. Press `INS` in the grid to
262
+ prepare a draft row at the top of the grid, edit its cells with `Enter`, use
263
+ `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
206
264
  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.
265
+ Grid edits compare the selected cell's originally loaded typed value as well as
266
+ the `ROWID`. If another session changes that cell or deletes the row first, the
267
+ edit is rejected and the query must be refreshed. Changes to other cells in the
268
+ same row do not cause a conflict. NUMBER input uses decimal notation with a
269
+ period; DATE and TIMESTAMP input uses ISO
270
+ `YYYY-MM-DD[ HH:MM:SS[.ffffff]]`; RAW and BLOB input uses hexadecimal bytes.
271
+ Character and CLOB input is preserved as entered. Types that cannot be compared
272
+ without losing information, including time-zone timestamps and timestamps with
273
+ precision above six, are read-only in the grid.
274
+ Displayed CLOB and BLOB values are limited to the first 65,536 characters or
275
+ bytes and include a truncation marker with the full size. Truncated LOB cells
276
+ cannot be edited safely. Schema-browser DDL is always read in full.
209
277
  `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
278
 
211
279
  ### Schema Browser
212
280
 
213
281
  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
282
 
283
+ Definition loading supports conventional unquoted identifiers. Quoted
284
+ mixed-case objects can appear in the browser but their definitions cannot
285
+ currently be loaded from it.
286
+
215
287
  | Key | Action |
216
288
  | --- | --- |
217
289
  | Printable text | Filter object names |
@@ -249,7 +321,8 @@ The `F9` schema browser groups tables, views, procedures, functions, packages, t
249
321
  workspace/
250
322
  sql/ saved SQL files
251
323
  plsql/ saved PL/SQL files
252
- results/ exported query output
324
+ results/ reserved output directory
253
325
  ```
254
326
 
255
- The first launch creates the workspace folders and starter SQL/PLSQL files.
327
+ The first launch creates the workspace folders and starter SQL/PLSQL files. The
328
+ 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.5"
@@ -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)