Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b0c6d27
feat(db): add versioned migration prototype
Zxf-xufeng Sep 28, 2026
847f706
fix(db): repair migration prototype CI checks
Zxf-xufeng Sep 28, 2026
40594b0
docs: explain database migration design
Zxf-xufeng Sep 29, 2026
8824d0c
Merge remote-tracking branch 'origin/master' into codex/unified-datab…
Zxf-xufeng Sep 29, 2026
0584b76
test: check runtime health after cancelled usage write
Zxf-xufeng Sep 29, 2026
fb616ca
test: align usage lock test with write budget
Zxf-xufeng Sep 29, 2026
2bcc5e6
test: wait for SQLite writer release after cancellation
Zxf-xufeng Sep 29, 2026
7478cea
fix(db): finish transaction cleanup after cancellation
Zxf-xufeng Sep 29, 2026
06cc752
test: release stalled writer before awaiting cancellation
Zxf-xufeng Sep 29, 2026
b779129
chore(ci): isolate topic memory test stall
Zxf-xufeng Sep 29, 2026
05f2b0d
chore(test): capture stalled asyncio tasks in CI
Zxf-xufeng Sep 29, 2026
7ec1669
chore(ci): stream asyncio task diagnostics
Zxf-xufeng Sep 29, 2026
24a7ba9
fix(ci): restore full checks before applying driver cleanup fix
Zxf-xufeng Sep 29, 2026
1b3a904
fix(persistence): finish connection cleanup before a cancellation pro…
guamiao Sep 29, 2026
39bf56e
fix(persistence): roll back an interrupted transaction instead of com…
guamiao Sep 29, 2026
b246d2e
fix(persistence): scope the interrupted-transaction mark to its own t…
guamiao Sep 29, 2026
76c9c71
fix(persistence): read transaction state without touching a dead conn…
guamiao Sep 29, 2026
e0e7656
test: separate native lock waits from cancellation deadlines
Zxf-xufeng Sep 30, 2026
4f99df1
feat(db): align migration prototype with single-table RFC
Zxf-xufeng Sep 30, 2026
d428889
fix(migrations): preserve recovery evidence and resume service switches
Zxf-xufeng Sep 30, 2026
e97cbbc
fix(service): stop services when startup is interrupted
Zxf-xufeng Oct 8, 2026
dce0bb9
chore: resolve migration branch conflicts with master
Zxf-xufeng Oct 8, 2026
719af54
fix(db): validate retained tables during recovery
Zxf-xufeng Oct 8, 2026
2050234
fix(service): update manually stopped registrations
Zxf-xufeng Oct 8, 2026
63303b9
fix: verify migration targets and service startup
Zxf-xufeng Oct 8, 2026
279df94
feat(db): add seekdb and OceanBase migration executors
Zxf-xufeng Oct 8, 2026
0c524c0
fix(db): coordinate OceanBase migrations safely through ODP
Zxf-xufeng Oct 8, 2026
e9662ef
merge: resolve master conflicts in migration implementation
Zxf-xufeng Oct 9, 2026
f1a934e
ci: capture stalled seekdb migration diagnostics
Zxf-xufeng Oct 9, 2026
81d0a11
test: avoid blocking seekdb owner stderr
Zxf-xufeng Oct 9, 2026
1f38aa5
test: cover noisy seekdb owner startup
Zxf-xufeng Oct 9, 2026
c7c8d9d
test: reclaim native migration databases after each case
Zxf-xufeng Oct 9, 2026
3570cc8
ci: trace SQL in seekdb migration acceptance
Zxf-xufeng Oct 9, 2026
1eeb3fc
merge: sync master desktop validation
Zxf-xufeng Oct 9, 2026
819329b
fix(desktop): synchronize generated API contract
Zxf-xufeng Oct 9, 2026
3d418ef
fix(service): clear maintenance marker on uninstall retry
Zxf-xufeng Oct 9, 2026
5d584bf
ci: isolate seekdb migration runner cases
Zxf-xufeng Oct 9, 2026
76f8431
merge: sync master artifact search and desktop qualification
Zxf-xufeng Oct 9, 2026
4ccaa6b
test(desktop): wait for navigation focus before editing
Zxf-xufeng Oct 9, 2026
a46ce3e
ci: preserve native seekdb logs on migration timeout
Zxf-xufeng Oct 9, 2026
d76cdc0
ci: retain seekdb transaction history in timeout diagnostics
Zxf-xufeng Oct 9, 2026
f92c0ee
fix(migrations): explicitly begin maintenance transactions
Zxf-xufeng Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,62 @@ jobs:
- name: Test code indexing with embedded seekdb
run: make code-seekdb-test

- name: Test unified migrations with embedded seekdb
timeout-minutes: 10
env:
POWERCONTEXT_TEST_MIGRATION_SEEKDB: "1"
POWERCONTEXT_TEST_MIGRATION_SQL_LOG: "1"
PYTHONUNBUFFERED: "1"
TMPDIR: ${{ runner.temp }}/seekdb-migration
run: |
set -o pipefail
mkdir -p "$TMPDIR"
collect_diagnostics() {
df -h . /tmp
# A timed-out pytest process cannot run fixture cleanup. Retain
# bounded native logs from its isolated data directories as well
# as the Python stack and SQL trace.
find "$TMPDIR" -type f \( -name '*.log' -o -name '*.log.wf' \) \
-print -exec tail -c 8388608 {} \; > seekdb-native.log
}
trap collect_diagnostics EXIT
df -h . /tmp
uv run --locked --extra seekdb pytest -vv \
-o faulthandler_timeout=120 --durations=20 \
tests/builtin/persistence/test_database_migration_backends.py \
tests/builtin/persistence/test_database_backup_providers.py \
tests/builtin/persistence/test_database_migration_connections.py \
tests/builtin/persistence/test_mysql_migration_schema.py -k seekdb \
2>&1 | tee seekdb-migrations.log
uv run --locked --extra seekdb pytest --collect-only -q --color=no \
tests/builtin/persistence/test_mysql_migration_runner.py -k seekdb \
2>&1 | tee seekdb-runner-collection.log | tee -a seekdb-migrations.log
mapfile -t seekdb_runner_nodes < <(
sed -n '/^tests\/builtin\/persistence\/test_mysql_migration_runner\.py::/p' \
seekdb-runner-collection.log
)
if [ "${#seekdb_runner_nodes[@]}" -eq 0 ]; then
echo "No seekdb migration runner tests were collected" >&2
exit 1
fi
for seekdb_runner_node in "${seekdb_runner_nodes[@]}"; do
timeout --signal=TERM --kill-after=10s 180s \
uv run --locked --extra seekdb pytest -vv \
-o faulthandler_timeout=120 --durations=20 --log-cli-level=INFO \
"$seekdb_runner_node" -k seekdb \
2>&1 | tee -a seekdb-migrations.log
done

- name: Upload seekdb migration diagnostics
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: seekdb-migrations-${{ matrix.python-version }}
path: |
seekdb-migrations.log
seekdb-native.log
if-no-files-found: ignore

windows-unit-portability:
timeout-minutes: 20
runs-on: windows-latest
Expand Down
30 changes: 23 additions & 7 deletions desktop/ui/tests/connections.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { act, cleanup, render, screen } from "@testing-library/react";
import { act, cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { App } from "../src/app/App";
import { Connections } from "../src/app/Connections";
Expand Down Expand Up @@ -148,23 +148,39 @@ test("a save from the previous connection editor cannot dismiss a new draft", as
const user = userEvent.setup();
vi.mocked(desktopApi.state).mockResolvedValue(state());
let finish!: (next: DesktopState) => void;
vi.mocked(desktopApi.save).mockImplementationOnce(
() =>
new Promise((resolve) => {
finish = resolve;
}),
);
const pendingSave = new Promise<DesktopState>((resolve) => {
finish = resolve;
});
vi.mocked(desktopApi.save).mockReturnValueOnce(pendingSave);
const confirm = vi.spyOn(window, "confirm").mockReturnValue(true);
render(<App />);
await user.click(screen.getByRole("button", { name: "连接" }));
// Let navigation finish moving focus before keyboard input begins.
await waitFor(() => {
expect(document.activeElement).toBe(
screen.getByRole("heading", { level: 1, name: "连接" }),
);
expect(screen.getByLabelText("连接名称").matches(":enabled")).toBe(true);
});
await user.type(screen.getByLabelText("连接名称"), "First");
await user.type(
screen.getByLabelText("Server 地址"),
"http://localhost:8000",
);
await user.click(screen.getByRole("button", { name: "保存配置" }));
expect(desktopApi.save).toHaveBeenCalledWith(
expect.objectContaining({
name: "First",
endpoint: "http://localhost:8000",
}),
);
await user.click(screen.getByRole("button", { name: "总览" }));
await user.click(screen.getByRole("button", { name: "连接" }));
await waitFor(() => {
expect(document.activeElement).toBe(
screen.getByRole("heading", { level: 1, name: "连接" }),
);
});
await user.type(screen.getByLabelText("连接名称"), "New draft");
await act(async () => {
finish(state({ ...profile, name: "First" }));
Expand Down
Loading
Loading