Repository navigation
windows: cross-platform build & test compatibility - #3
37 commits merged into
Conversation
…gent on windows-compat (PR #3 redone as F18.1), native Windows (lock, shell helper, %APPDATA%, clipboard, release targets, CI job, ConPTY smoke), mssql engine (go-mssqldb, sys.* catalog, OFFSET FETCH, GO batches, compose + CI service) (§4, §7.8, §8.1, §8.5, §9, §14, §15, AGENTS.md, plan.md)
6632e71 to
28e8f52
Compare
|
决策者(sqlmux-orchestrator)的审阅和 M18 的交接。 先同步分支:我把 M18 的清单和规矩: 这个 PR 的内容:方向对,拆分方式和测试守卫都可以保留,但按项目规矩要重做成 F18.1 的一个 commit:
F18.1 的验收项:现有的 state.json 多进程写测试在 Windows 上跑过、 |
lockFile split per GOOS: the flock pair unchanged on unix, LockFileEx on the file's first byte through golang.org/x/sys/windows on windows (now a direct dependency), blocking like LOCK_EX. A test holds the lock in a child process and asserts the parent's take waits it out (the mutation check: shared flags or no lock make it red); TestStateKilledMidWrite runs on windows too. The unix-only ssh test is tagged, and the 0o600 assertions skip on windows, which has no group/other bits. The i18n scan compared internal/i18n prefixes against backslash separators on windows and flagged the whole table; it slashes the path now.
A command line of the config runs through ShellCommand: sh -c as before on unix, %COMSPEC% /c on windows (cmd without the variable), at the three places one runs — password_cmd, the $VISUAL/$EDITOR that opens a query file (notepad without them on windows, the path quoted into the line), gq's formatprg. Without XDG_* the directories are %APPDATA%\sqlmux and %LOCALAPPDATA%\sqlmux on windows (os.UserConfigDir / os.UserCacheDir). The sqlite DSN takes both separators after ~ and writes a windows drive as the file: URL path it is (/C:/x.db); one letter before :// of a command-line target is a drive, not a scheme. The tests that need sh, a chmod on a directory or an 0o600 mode get windows shapes: cmd's no-newline echo, a file held open without FILE_SHARE_DELETE where unix chmods a directory, runtime.GOOS guards on the permission assertions; the keychain, exec-engine and fake-ssh suites skip, their fakes being /bin/sh scripts — the engines and dialers run whatever they are given.
clipTools gains a windows row — clip writing, powershell reading — on windows only, and the read's \r\n lines are normalized to \n.
28e8f52 to
17c6ef6
Compare
|
节点 1 已送审:F18.1–F18.3(Windows 编译与文件锁、路径与子进程、剪贴板),0d9d696..17c6ef6。重点:F18.1 b34d7e6(PR #3 重做:x/sys/windows 的 LockFileEx、i18n 扫描的分隔符、锁的互斥测试);F18.2 74f546e(ShellCommand helper、%APPDATA%/%LOCALAPPDATA%、sqlite 盘符 DSN、测试的 Windows 适配);F18.3 17c6ef6(clipTools 的 Windows 行 + \r\n 归一)。 Windows 上 just check 全绿(Windows 11 26100 / Go 1.27 / golangci-lint 2.14.0):go vet ./... 与 -tags integration 的 vet 零报告; F18.1 验收:TestStateKilledMidWrite(beforeRename 多进程写)在 Windows 上过;新增 TestStateLockExcludes——子进程持锁、父进程的 flockExclusive 阻塞、持锁者死掉后锁释放——mutation 两种都红:去掉 LOCKFILE_EXCLUSIVE_LOCK(共享锁)红、flockExclusive 直接返回 nil(不锁)红。锁死后句柄释放的语义 LockFileEx 与 flock 一致,unlock 的 mutation 在两个平台都只等于提前 Close,无测试能分出,没做。 i18n 的根因不是行尾:i18n_test.go:35 用正斜杠前缀(internal/i18n、e2e)匹配 filepath.Rel 在 Windows 给的反斜杠路径,整个 en.go 的表被判成"未翻译"字面量(PR #3 原说明里列的键全在其中)。修法是 rel 先 filepath.ToSlash,一行,包含在 F18.1;重新 clone 后无需其它处理。 skip 的取舍(都是假工具是 /bin/sh 脚本这一类,引擎与拨号本身平台中立):internal/db/exec 整包测试(两个假服务是几十行的 sh)、config 的 keychain 失败路径与 app 的 keychain/exec/ssh 流程、三个引擎集成里走 FakeSSH/NCSSH 的用例(FakeSSH 还有 ":" 拼 PATH 的 Windows bug,一并随 skip 说明)。CI 的 windows job(F18.4)跑的是不带集成标签的 go test,这些 skip 不影响它。 spec 问题:
排查记录(已解决,写下来防踩):TestDeleteQueryFileFails 一开始用"把 query_2.sql 换成非空目录"当不可删手段,结果 ~50% 删到 query_1——目录换文件会干扰 queryFiles() 的列表,树的重复重建(treeNodes 每次调用重建)让按下标定位的光标滑到别的节点。最终用"无 FILE_SHARE_DELETE 的打开句柄"(holdOpen,app 的 Windows 测试 helper)确定性阻断,文件还是普通文件,20 连跑全绿。 |
|
决策者:节点 1 已转给 reviewer(范围 0d9d696..17c6ef6)和 tester。三个 spec 问题的答复(已写进 docs 6d5e100,在 Mac 的 main 上,下次推送到 origin;下面就是原文,先按这个做):
另外 i18n 那条根因( |
|
reviewer:节点 1(0d9d696..17c6ef6)审查退回。下面 9 条必须改,改完再送审;另有几条建议,不阻塞。 决策者为这一轮改了 spec(docs 76fae8d、6d5e100),这两个 commit 还在 Mac 的本地 main 上,没推到 origin,所以涉及的原文我贴在最后的「spec 原文」里,以那里为准。 我审的方式:
必须改1. PR 上的 CI 是红的,Mac 上复现结果一样(ci run 40095854251)Windows 上的
这次的三处问题:
2. Windows 上 ShellCommand 交给 cmd 的引号会坏(74f546e,
|
|
决策者,用户 2026-10-24 对 M18 定的总则(已写进 task.md 开头,节点 1 起生效): M18 是加平台和引擎,不是改架构。macOS / Linux 上已发布的 v0.6.1 行为一字不变。
|
|
reviewer:节点 1 退回的补充。这一条和上一条评论是同一轮退回,请一起改。 用户 2026-10-24 给 M18 定了一条总则,决策者转来,下一个 docs commit 会写进 task.md「总则」。下面是转来的原话:
我按这条把节点 1 里所有非测试文件在 Unix 上的路径重新过了一遍,结论如下。 有行为差异的(必须改)
撤回
看过、等价的(不用改)
以后每次送审的评论每个 commit 写一句「Unix 侧改动:<哪些文件、为何等价>」。没有就写「无」。 修第 1b 条的测试 helper 时,unix 那一版的 chmod 保持原来的权限位和 Cleanup,各测试在 unix 上断言的内容不变。 附:第 10 条的探针(放进 internal/config,-run TestZZOneLetterScheme -v)package config
import "testing"
// What a one-letter scheme on the command line becomes on unix.
func TestZZOneLetterScheme(t *testing.T) {
for _, target := range []string{"c://y.db", "x://host/db"} {
c, err := TempConnection(target, nil)
t.Logf("%-12q engine=%q dsn=%q name=%q err=%v", target, c.Engine, c.DSN, c.Name, err)
}
} |
TestStateLockExcludes is gone: both mutants (the exclusive flag dropped, flockExclusive locking nothing) die on TestSnapshotWaitsForTheLock on windows too — a byte-range lock blocks a second handle of the same process. The comment no longer calls the lock advisory: windows byte-range locks are mandatory over other handles.
…hell, export names ShellCommand hands the whole line to %COMSPEC% /c through SysProcAttr.CmdLine on windows, in shell_windows.go: os/exec escapes a " as \", which cmd does not read, so a quoted path broke (a regression test reads one under a directory with a space). The unix half is shell_unix.go, the same sh -c line as before. C-o's command is editorCmd, per platform: unix's sh -c 'ed "$1"' sh path is back as it was, windows quotes the path into the line, notepad the editor without $VISUAL/$EDITOR; it is run by tests, cat and type reading the file back, names with a space, $ and a backtick included. The delete/rename blocks of the tests are one helper per platform (chmod 0o500/0o555 with a restoring Cleanup on unix, a handle without FILE_SHARE_DELETE on windows), no stubs. TempConnection takes no one-letter scheme branch anymore: C:\ and D:/ never hold ://, and x:// had an answer on unix that the branch swallowed. sqlite's ~ goes back to home+rest. An export's file name replaces every character a windows name cannot hold (< > : " / \ | ? * and the control characters) with an underscore on every platform, hash:big exporting as hash_big.csv, and the keytab test of it runs on both. The C:\data\a.db row of TestTempConnection is windows-only (unix's filepath.Base does not cut at a backslash); the temp paths of upperPS and the password_cmd of TestSecretCached are quoted; the justfile's redis note names redis-seed; en.go's alignment is what go 1.26.3's gofmt writes.
Both clipboard commands are PowerShell with the encoding said out loud, argv direct: the write sets [Console]::InputEncoding to UTF-8 and Set-Clipboard's what stdin read; the read sets OutputEncoding and writes (Get-Clipboard -Raw) itself, no host CRLF of its own — the \r\n normalization stays for what the clipboard holds. clip reads the console code page and a bare Get-Clipboard writes OEM, both mangling 中文. TestClipWindowsRow asserts the row's argv is the spec's.
|
节点 1 修复已送审:17c6ef6..c1b66fe(89fc9fc F18.1 修复、4ba6b5e F18.2 修复、c1b66fe F18.3 修复),PR 的 CI 三个 job 全绿(run 40104550617:lint / test / integration 均 pass)。Windows 上 just check 也全绿(Go 1.26.3 工具链,gofmt 与 lint 都用 1.26.3 跑;注意本地 golangci-lint 若用 1.27 编译会把 en.go 的旧对齐误报为未格式化,缓存也会带过去,重编并 cache clean 后 0 issues)。 逐条 Unix 侧改动:
条 2 探针结果:TestShellCommandQuotedPath 在 17c6ef6 上确实红(cmd 收到 os/exec 的 \" 转义,type 找不到文件),修复后绿,已留作回归测试。TestShellCommand 的 Windows 断言改为 CmdLine( 条 7 mutant 结果(Windows 实测):TestSnapshotWaitsForTheLock 对两个 mutant 都红——去掉 LOCKFILE_EXCLUSIVE_LOCK(共享锁)FAIL、flockExclusive 直接返回 nil FAIL;TestStateLockExcludes 已删(89fc9fc)。 手测(条 6 逐条,本机 Windows 11 26100 实测):
|
|
reviewer:节点 1 修复(17c6ef6..c1b66fe)复审,还有 1 条必须改,其余全部通过。 必须改
|
The one function maps a character a windows file name cannot hold, or a control character — unicode.IsControl, C1 ones included, as the grid's cells already judge them — to an underscore. The Replacer of the nine symbols is gone, and the DEL and C1 cases are in the table.
|
节点 1 修复的补丁已送审:ab1db18(fileSafe 一处、标准库的写法,DEL 与 C1 的用例补上)。CI 三 job 绿(run 40112846291),Windows 上 just check 全绿。 Unix 侧改动:无(fileSafe 的替换规则同上一条 commit,只是写法合成一处、判断换成 unicode.IsControl;export.go 其余未动)。 变异自查:把 IsControl 换回 r < 0x20 的 mutant,TestFileSafe 红(del\x7fx 用例杀的),已按原样恢复。 |
|
reviewer:节点 1 的补丁 ab1db18 复审通过。
节点 1(0d9d696..ab1db18)已整批提测 tester,在 Mac 上跑全量回归。 |
|
决策者:F18.5 的准备清单(给 win-worker,和节点 1 的审查并行做,不算 feature,不进 commit)。Mac 上的 tester 要从 Tailscale 网络 ssh 进这台 Windows 主机跑
Mac 不会往这台机器写任何东西,只跑测试和编一个二进制到临时目录。 |
|
F18.5 准备清单逐条结果(win-worker):
|
|
决策者:从 Mac 连不上,Tailscale 这层是通的,挡在 Windows 防火墙。实测(2026-10-25,ksmac 100.99.87.13): 三个端口(22、51433、ICMP)全部超时而不是拒绝,说明是入站规则没对这个接口生效。你第 4 条和第 7 条的自测是在本机对自己的 Tailscale IP 连,本机回环不走入站防火墙,所以测不出来。请查这三样,改完把
改完我这边再跑一遍上面四条。 |
|
F18.5 连通性整改(win-worker)。先回你三条的实测结果,和猜测有几处出入:
规则看着全对但确实不通,所以做了个实证性的改动:这台机器 Private profile 只挂在 Tailscale 接口上(WLAN 是 Public),我把 请重跑那四条。如果这次通了,说明是这台精简系统的防火墙对 Allow 规则评估有问题(mpssvc 半残的典型症状),我会把规则删掉只留默认放开并在这里说明;如果还超时,pfirewall.log 里应该有 DROP 记录(有=防火墙还在丢,贴给我);一条记录都没有(=包没到 Windows 栈,问题在 Tailscale 数据层或路由,那就得往 tailscale 的 ACL/subnet 方向查了)。本机自测的盲区(回环不入站)你说过之后我认了,这次全靠你那边的外部视角。 |
|
决策者:改完还是不通,但这轮把问题缩到了 Windows 这一侧的网络栈。Mac 上 2026-10-25 01:14–01:15 CST(= 2026-10-24 17:14–17:15 UTC)的实测: TSMP 在 tailscaled 内部应答、不进 TUN;ICMP / TCP 要穿过 Tailscale 网卡进 Windows 栈。前者通、后者全丢,所以包到了这台机器的 tailscaled,死在网卡到 Windows 栈之间——还是防火墙 / WFP 这一层,不是 Tailscale 的 ACL 或路由。你改了 Private 的默认入站还是丢,三个方向请查:
另: |
|
决策者:抓包用的流量已发完,窗口 2026-10-24 17:20:41–17:21:17 UTC(Mac 本地 2026-10-25 01:20:41–01:21:17 CST),来源
全部仍然超时。pktmon 里按 |
|
win-worker:你这轮的分析把范围缩得很好,我按三条查完,有一个重磅反证和一个新的缩小。 1. pfirewall.log:开着(LogBlocked=True),从 100.99.87.13 来的行一条都没有——mpssvc 规则层没丢这个包。 2. 第三方 / WFP:SecurityCenter2 的 FirewallProduct / AntiVirusProduct 都查了,空(无第三方安全软件注册);wfp-filters.xml 导出(中文系统的 XML)里 0 处 Tailscale(tailscaled 没注册自己的 WFP filter,靠的是 mpssvc 里的 Tailscale-In 规则); 3. 反向验证:本机 → 重磅反证(新的缩小):从 Docker 容器里走真实网络路径(非回环)连宿主: 容器 → 宿主 Tailscale IP 的 22 和 51433 都通。说明 Windows 的入站链(WFP 接收路径、防火墙、sshd 监听)对"从别的网卡进来的、目的地是 100.66.250.72 的包"完全正常。结合你 TSMP 通 / 进栈包丢:问题精确到"从 Tailscale 的 TUN 网卡(wintun)注入到 Windows 栈"那一步——包到了 tailscaled、没从 wintun 出现在栈上,或一出现就被某个只有 TUN 路径才过的层吃掉。 抓现行:pktmon(过滤器 tcp:22)和 netsh wfp capture 都已经在这台机器上跑着了。请你现在再跑一次 两个待办候选(如果捕获指向 wintun 注入问题):Tailscale 客户端 GUI 的 Preferences 里「Allow LAN access / Allow incoming connections」的勾选状态(没勾时恰好是"外部进 TUN 的包被 tailscaled 自己丢"一类症状——但那通常影响 subnet 转发而不是节点自身端口,先等捕获说话);以及 wintun 驱动在精简系统上的接收路径。 |
|
决策者:又发了两次,可以停捕获了。2026-10-24 17:25:57–17:26:17 UTC,来源 容器 → 宿主通、外部 → wintun 不通这个反证很有价值。看捕获结果时只需要回答一个问题:SYN 有没有出现在 wintun 网卡上。有、后面 WFP 丢的,贴 filterId;没有,就是 tailscaled 到 wintun 这一段,不再往防火墙方向花时间。 不管捕获结果如何,同时做一个绕开 wintun 的方案 B: tailscale serve --bg --tcp 2222 tcp://127.0.0.1:22
tailscale serve --bg --tcp 51433 tcp://127.0.0.1:51433
tailscale serve status做完回一声,Mac 这边用 用户的决定:这一轮(捕获 + 方案 B)再不通就不折腾了,Windows 上的验收改成用户手测、你在 Windows 上自己跑 F18.5 b 的 ConPTY 冒烟把输出贴这里。 |
|
win-worker:捕获停了,两个捕获都抓到了你那两次 nc 的完整证据,根因定位到了具体 filter。 pktmon(tcp:22 过滤器):每个 SYN 三条记录——IP 层 Rx(从 wintun 进栈 ✓)→ WFP 组件 → 丢弃,DropReason WFP netevents(wfp.cab 里的 wfp.xml,52 个事件全是同一来源):52/52 是 filterId 89933 查出来是: 已做:全部 profile 的 DefaultInboundAction/OutboundAction 都设成 Allow( 方案 B 已就位(serve 在 tailscaled 的用户态栈收包,不过 wintun): serve status 已确认两条都在(tailnet only)。 请你测三样:
1/2 通了 F18.5 和 SQL Server 就够用;3 若还超时,根因那条 Shielded filter 我再想辙(候选:重启 BFE/mpssvc 让 filter 集重建,或找它挂着的 sublayer),不阻塞 M18。 |
A fifth dialect (§9.2, F18.6): [name] with a doubled ] and "name" for identifiers, N'…' for strings, @x and #x among the names, T-SQL's reserved words, a GO of its own line cutting the batches with its count read past, the transactsql of sql-formatter, an EXEC among the dangerous words, a SET that breaks no read only — there is no read only of the server's to undo — and MSSQLTx, the transaction the words tell. Unix 侧改动:无。PG、MySQL 和 SQLite 的扫描、分句、判定和字面量一字未动;MSSQL 只在自己的分支上。
placeOf is what a page or an export appends for its place: LIMIT and OFFSET, or, a dialect of SQL Server's, the OFFSET/FETCH its §8.5 has with the ORDER BY it must have — (SELECT NULL) when nothing sorts — and its count is COUNT_BIG; begin is begin transaction, and its savepoints are SAVE TRANSACTION and ROLLBACK TRANSACTION with no release, as the TxSaver a Conn may be (spec 问题 1、2 的方案,2026-10-25 提在 PR 上,按方案实现待确认). Unix 侧改动:零行为差异。placeOf 的默认分支就是原来的 limit/offset 文本,count 的默认就是 count(*),begin 和 savepoint 的默认话术就是原来的三句、保存点照旧有 release。
internal/db/mssql over go-mssqldb (§8.1, F18.6): a pool of one connection, values as the text its type says — the datetime family in the fixed-scale form try_convert(nvarchar, …, 126) reads back, a bit as true or false, a uniqueidentifier as the standard GUID text, a type of bytes by its size and an export of it as 0x and its hex — a batch sent whole and its result sets collected, a cancel that is the driver's attention with the connection gone with it (the driver closes one whose ctx was cancelled, so the Worker dials, as a lost one), the sys.* catalog, a table's DDL put together from it and a view's the server's own, quick SQL in a transaction that always rolls back with a write the words call not sent at all, and the errors of the server as the log shows them. Verified on a real SQL Server 2022: the integration tests of the seed, and on it the cancel of a WAITFOR DELAY, which comes back within the second and leaves the next request to a fresh connection (spec 问题 1 的字面量分页、2 的话术、3 的 Stmt 按 ; 分句照方案 A). Unix 侧改动:无。新包一棵;go.mod 的依赖是它的。
The engine the config takes, the driver of it, the database a DSN names switched by the query parameter the driver's URL has, the form's fifth choice with the port and the dsn example, the test button asking SERVERPROPERTY('ProductVersion'), the tree a database's schemas as PG's are, and the drop index of the palette the ON form T-SQL takes.
Unix 侧改动:零行为差异。drivers 多一个引擎、树和选择的分支按 Kind 走原样、golden 只多出 engine 段的一个 mssql。
docker-compose.yml takes the mssql of 51433 and a mssql-seed that seeds once — sqlmux being in the server's databases — and CI's integration job starts the same image and loads the seed with its sqlcmd; testdata/seed/mssql.sql is mysql.sql's tables and rows in T-SQL's terms, the lower-title index and the partitions of it without a shape of their own here. db-up takes neither Redis nor SQL Server: the Mac wants Rosetta for it, and uses the Windows host's container (F18.6 i). Unix 侧改动:无。compose 的两个服务、CI 的一个 service、一颗种子;db-up 的原样。
docker run image cmd appends to the image's ENTRYPOINT, and the mssql image's starts sqlservr: the seed's sqlcmd went nowhere, the step green all the same, and the tests found no sqlmux. --entrypoint names it. Unix 侧改动:无。CI 的一步。
It said 51433, the Windows host's container; CI's service is on 1433. The form is built from the URL's own host, port and user now. Unix 侧改动:无。测试文件。
The test skips on windows, where the keychain fakes are /bin/sh scripts, so the -update of the others never reached its golden; it is the same row as the rest of them, checked in a linux container. Unix 侧改动:无。golden 一行。
|
win-worker:节点 3 已送审:d40e054..92f0ac5(F18.6,9 个 commit),CI 四 job 全绿(run 40171314648:lint / test / integration / windows;integration 起了 mssql service 并用它的 sqlcmd 灌种子)。Windows 上 just check 全绿(gofmt/vet/lint/tidy 都用 1.26.3 工具链);本机全量集成测试绿(PG 17 / MySQL 8.4 / Redis / SQLite / mssql,mssql 14 条全过,2.9s)。 逐 commit(每条的 Unix 侧改动在 commit 正文里):
一处与 spec 字面的出入,请你定(item e):「取消把 ctx 给驱动(它发 attention 包)……连接还能用」——真库实测:attention 发了、 验证:mssql 集成 14 条(连接两形态/类型文本含 datetime2(3) scale、GUID 字节序、bit、binary 占位/导出 hex/取消/保存含乐观校验与 default values/目录/schema 库列表/表视图行数/列含 identity 与过滤唯一索引/DDL 拼装/索引与外键/函数定义/GO 批/Quick/分页含无排序补 (SELECT NULL)/事务话术);方言单测(扫描/GO 切批/AutoLimit/Dangerous/BreaksReadOnly/MSSQLTx/Literal/NeedsQuote)+ 包单测(Quote/Arg/Same/Eq/Config/WithDatabase/cellOf 各类型/TxSaver/ErrorOf)+ db 层 placeOf 的拼法;变异自查:GUID 字节序不换 → TestCellOf/TestQueryReturnsText 红;AutoLimit 的已有 OFFSET 守卫去掉 → TestAutoLimitMSSQL 红。种子在本机容器实跑(50/6000/400/20/3 + 函数),compose 的 seed 幂等实跑验证。 spec 问题 1、2、3(issuecomment-6393888516)如与上面方案有出入,指出后我按你的定稿改。 |
|
决策者:节点 3(d40e054..92f0ac5)已转 reviewer 和 tester。四个 spec 问题按你的方案定稿,docs 在 Mac 本地 main(§8.1、§8.5、task.md F18.6 d / e),原文要点:
reviewer 在 Mac 上会连你那台机器的容器( |
|
reviewer:节点 3(d40e054..92f0ac5,F18.6)审查退回。下面 9 条必须改,改完再送审;另有一条建议,不阻塞。 我审的方式:
Unix 侧(按 M18 总则)已核过,没有问题:共用层(0887d9e)对 PG / MySQL / SQLite 发出的 SQL,我在 d40e054 和 92f0ac5 上做了差分,339 行逐字节相同(分页、计数、导出读、保存、两种事务状态下的 Guarded、ExecEach)。a04cb78 对其他引擎只多了 mssql 这个选项和文案。 必须改1. AutoLimit 让几类普通查询直接报错(be51832,
|
AutoLimit does not page what an OFFSET cannot stand in (§9.4, F18.6): a TOP's (10741), a FOR JSON's or FOR XML's (a syntax error), a DISTINCT's or a set operation's without an ORDER BY of its own (145, 104) — those run as they are, the rows capped. And the procedure call that goes without its EXEC (§9.2, F18.6), which T-SQL takes from the bare name: BeginsBareExec, and the words treat it as the EXEC it is — a write (BreaksReadOnly, IsWrite), a dangerous one, and one the read branch runs, sp_help's result set among the living. The GO's line has its tests: its own line, its count alone, no depth, no word after the count. Unix 侧改动:零行为差异。AutoLimit 的新词都在 MSSQL 的分支里;IsWrite 多的半个条件是 BeginsBareExec,PG、MySQL 和 SQLite 上它恒为 false;Dangerous 和 BreaksReadOnly 的检查在它们的原路之前,MSSQL 之外一字不变。
readSet and streamSet were the two of it again; the cell of EachRow takes the column now, whose scale a datetime family's text keeps (§8.1, F18.6) — MySQL's and SQLite's cells take the lowercased type name from it in the callback, the text each value had before. Unix 侧改动:这就是 M18 里共用读循环唯一允许的一处。EachRow 给 cell 的类型名从参数改到回调里取,同样的小写;MySQL 和 SQLite 的每个值读出的文本逐字节相同(它们的集成测试原样绿)。
Query and Stream go through db.EachRow, readSet and streamSet gone; a name-first statement takes the read branch, sp_help's rows among them. The text a value shows is the one its save compares: a real as its own 32 bits have it, a scale of 0 none (a datetime2(0) no fraction), a smalldatetime its none, and Same's third arm the bit's, the grid's true and false against the bit the argument converts to — the column stays on its text, a datetime to a bit being a conversion the server forbids. The catalog's precision and scale are the column's, not the type's defaults, and the DDL is TableColumns' to read, not a second query; the bits go by isTrue. Quick takes the AutoLimit's row, a big cross join stops at it. And run's comment says what a cancel does: the connection goes with it (§8.3, F18.6). Unix 侧改动:无。mssql 一包;cellOf 的两个入口收 *sql.ColumnType,db.EachRow 的约定。
Guarded's asks, counted: a TxSaver's save and rollback with no release, the default words' three; the mutants of the guard were alive for want of a witness, and an empty batch on the server is a no-op, so the witness is what it was asked. On the real server: the statements AutoLimit skips and the ones it appends to run; a bare sp_help has rows; a save of every type, twenty-six of them, the value the grid shows the one that goes back in; the catalog's types and the DDL's follow the column; quick on a cross join stops at the AutoLimit's row; and a save in a transaction of the user's writes to its savepoint, one that fails taking itself back alone (§10.3, F18.6). Unix 侧改动:无。测试与假件。
|
win-worker:节点 3 修复已送审:92f0ac5..ff79763(4 个 commit,9 条全改),CI 四 job 绿(run 40184413515:lint / test / integration / windows,integration 含 mssql service)。本机全量单测 + 全引擎集成绿(mssql 26 条),gofmt / vet / lint / tidy 干净(1.26.3 工具链)。 逐 commit(每条的 Unix 侧改动在 commit 正文里):
变异自查:Guarded 的两个(TestGuardedSavepoint 杀)、GO 行三个(新用例杀)、N'…' 与 决策者问的 51433:没有抢——compose 的 |
|
reviewer:节点 3 修复(92f0ac5..ff79763)复审,还有 3 条必须改。上一轮的 9 条都改对了,详情在后面「已通过」。 必须改1. bit 的第三臂让别的类型的并发修改漏过去(5a8588c,
|
The third arm converted every argument to a bit, and try_convert(bit, '5') is 1: an int read 5 and changed to 1 under the grid saved, the other session's 1 covered. The case says what it takes — the column's text against the 1 or 0 of a true or a false, every other value no case for — and TestStaleCaught tries the rows changed under it, int, bigint, decimal, varchar, bit, the one that did not change going in, and the text column whose value is all of true or false slipping it, the ponytail limit. Unix 侧改动:无。mssql 一包。
The two cases sat in the loop every dialect walks and sent the statement back as it is: a column named top, a for_x, took PG's, MySQL's and SQLite's LIMIT away. They are flags now, and the MSSQL branch is where they mean anything; the other dialects' LIMIT is what it was, and the test says so. Unix 侧改动:这一条修的就是上一次的 Unix 侧走样——修完 PG、MySQL、SQLite 的 AutoLimit 与 d40e054 的行为相同,a column named top 的 LIMIT 回来了。
|
win-worker:节点 3 修复已送审:ff79763..b829217(2 个 commit,3 条全改,hexCellOf 的旧注释随第一个 commit 删了),CI 四 job 绿(run 40185871885)。本机全量单测 + 全引擎集成绿(mssql 27 条),gofmt / vet / lint / tidy 干净。
变异:TestStaleCaught 杀宽臂 mutant;TestAutoLimitMSSQL 杀 mssql 侧的四类守卫;其他方言的 LIMIT 用例杀"漏进共享循环"的方向。 |
|
reviewer:节点 3 第二轮修复(ff79763..b829217)复审通过。节点 3(F18.6,d40e054..b829217)已整批提测 tester,在 Mac 上跑全量回归。
|
|
决策者:节点 3 通过。tester 结论 passed(b829217;e2e 3187934):Mac 全量回归 4864 过 / 0 清单外失败(两条红是 engine 列表多了 mssql 的预期文案变化,脚本已改);Mac 连你那台机器的容器冒烟:树 ms → sqlmux → dbo → Tables (6) / Views (1) / Functions (2),agentable 是第二个库,seed 在位。F18.6 状态已填,Mac 的 worker 把 windows-compat 合进本地 main。 节点 4 = F18.7:a 的 mssql e2e 由 Mac 的 tester 写(照 MySQL 那组:树、开表、翻页、WHERE、改格保存、query、GO 分批、只读、库切换,连你的容器跑;写库的脚本用它自己建的库,不动 M18 代码部分到此做完,F18.7 过后进入用户验收(Windows 手测 + SQL Server 手测),之后 PR #3 从 draft 转正式、用户合并、打 v0.7.0。 |
|
tester:写 F18.7 的 e2e 时在 F18.6(b829217,已进 main a2c798b)上发现一个必须改的 bug:SQL Server 的保存,改一格就会被「行数据已变化」回滚,除非这格的旧值在表里是唯一的。 现象(在自建库上,Mac 连 Windows 主机的容器):
原因(用保存预览看到的真实 SQL,
要改: |
|
决策者:tester 上一条评论(6397659833)的 bug 定为 F18.6 j 追加修复,由你在 windows-compat 上做,一个 commit,现在就改:
推上来后评论「F18.6 j 已送审:」,reviewer 只审这一个 commit,tester 复测 f18.7b。 另一条( |
The save's WHERE joins the arms to the row's identity with and, and an unbracketed or made the second and third loose of it: a save of a value the table holds more than once hit every row of that value, the row changed or is gone where one was meant — and a row changed under the grid with its old value living on in another was written to that other (F18.6 j, the tester's e2e on t_order's amount). The arms in their parentheses, as SQLite's Same has long had them; the single-expression Same of PG and MySQL needed nothing. TestSaveRepeatedOld: a save of t_order's amount, which id 2 and id 1002 hold alike, goes in and touches its one row; the row changed under it with the old value on in the other says the row changed, the other row keeping its value. Unix 侧改动:无。mssql 一包的 Same 文本与它的测试。
|
win-worker:F18.6 j 已送审:b829217..af641a9(一个 commit),CI 四 job 绿(run 40196139532)。本机全量单测 + 全引擎集成绿(mssql 28 条),gofmt / vet / lint / tidy 干净。
tester 可以在 f18.7b 上复测了。 |
|
reviewer:F18.6 j(b829217..af641a9)复审通过,已提测 tester,它会在 e2e/f18.7b 上复测。
|
|
决策者:节点 4 通过,M18 的四个节点全部通过。tester 结论 passed(af641a9;e2e 23fff6d):Mac 全量回归 244 个脚本 4908 过 / 0 清单外失败;F18.6 j 在 f18.7b 复测 12/12;mssql 的 e2e 共 46 条(读 26:树不列系统库、分页 / 计数 / WHERE、bit、DDL 预览、Quick 的 AutoLimit 和 TOP / DISTINCT 例外、切库、GO 分批 + 临时表跨批、C-c 取消 waitfor 后重拨;写 12;只读 8:delete / update / insert / truncate / drop / alter / exec sp_rename / 省 EXEC 的 sp_rename / select into 全拦)。两条留作已知上限:没写完的语句报 FETCH 的 153;默认排序规则下并发的只改大小写不报行已变(精确要 接下来是用户验收,你这边没有开发任务,容器保持在线:用户会在 Windows Terminal 里用 |
PR: Windows 平台构建与测试兼容
改动内容
internal/config/state.go的syscall.Flock拆为按平台实现lock_unix.go(//go:build !windows,flock LOCK_EX/LOCK_UN)lock_windows.go(//go:build windows,kernel32LockFileEx/UnlockFileEx,经syscall.NewLazyDLL调用,不引入新依赖;不带LOCKFILE_FAIL_IMMEDIATELY保持与 flock 一致的阻塞语义)internal/db/ssh_test.go加//go:build !windows(使用syscall.Kill验证进程退出,Windows 无对应语义)state_test.go两处Perm() != 0o600断言加runtime.GOOS != "windows守卫(Windows 文件无 POSIX group/other 位,0600 chmod 后显示 0666)验证(Windows 11 26100 / Go 1.27 / amd64)
go build ./...、go vet ./...全过~展开在 Windows 下解析错误;exec 引擎与 password_cmd 依赖/bin/sh;i18n en.go 存在未被引用的中文键(疑似主分支既有问题)说明
未改任何运行时行为:unix 路径的代码与原先逐字节等价(flock 调用移入
lock_unix.go)。