Build strings through StringBuilder instead of chained concat #44
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "task/1635-stringbuilder"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Tracker: #1635
Run contract: #1635 comment 12565 (#2607 note revision 24, step 11). Supersedes #3, re-delivered on master
c1eb9df; no QueryBuilder.Strings that grow in a loop or across several appends are written into one
StringBuilder.init()(context.allocator) and handed out byfinish():src/store.sx: project and task patchSETclauses (patch_setis a ufcs method on*StringBuilder), the viewSETclause,task_filterandquery_streamsWHERE bodies (and_clause,placeholders),fts_match/fts_quote,order_sql.src/api.sx: SSEdata:lines, event and close frames (sse_data,sse_eventwrite into the caller's builder), a tail round's event batch, an upload's sniffed head;slice_ofgives way to the slice operator.src/logging.sx: the request line;configis one flat variadicconcat.tests/: PR #3's test pass (accumulation loops throughStringBuilder, nestedconcatflattened), plus the same for the loops master added since (file_tags.sxtitles, seeded titles, tag pairs;step_files.sxeuro name;step9_release.sxcurl command).Every
concatleft insrc/is a single flat join. Acceptance grep, masterc1eb9df→ this head:x = concat(x, …)14 → 0, nestedconcat(…concat(…))12 → 0. The diff adds noFixedWriter,c.memcpy,[*]u8storage or@reinterpret.Gate (Server gate + W1, run in order on
6d2291f)compiled: sakOK, no FAILSTEP9 OK(cd web && npx playwright test)@enginesWebKit pinch test, chromium skips it)S6 is out of this step's gate (it joins from step 47). GOLD: no goldens change.
Output bytes unchanged
This is a construction change; no behaviour differs, so there is no regression that fails on the base. The existing suite pins the outputs it touches (e.g.
step_streams_api.sxsplitsfirst\nsecond,third\rfourth,fifth\r\nsixthinto SSEdata:lines;step5_search.sxcovers FTS search, labels, attributes and sort). Beyond the suite, one scripted HTTP session was run against./sakbuilt from masterc1eb9dfand from this head, same port, fresh database,--log-level debug: 77 exchanges covering project / task / view patches (each field combination, If-Match conflict), task queries (FTS with quotes and blanks, labels, attributes, reference, every sort with and without search), stream event append with CR / LF / CRLF / empty payloads, SSE tails replaying to the close frame with and without a cursor, stream queries by status andopened_after, PNG uploads (full and shorter than the 64-byte sniff head) and a refused non-image, and/api/logs. With timestamps and millisecond durations masked, the response transcripts (43334 bytes; 48048 raw) and the stderr logs (7758 bytes) are byte-identical (cmpexit 0 on both).Head:
6d2291f03aAPPROVED — Cody, reviewing head
6d2291f03a6f4312a36eee94ef9600658325b952against masterc1eb9df6ebfac2225d45c7d43b3833f6de22b504under run contract #1635 comment 12565 (#2607 note revision 24, step 11; Gate: Server gate + W1, S6 out before step 47).Gate lines (run by me on the PR head, in order, each from the repo root)
compiled: sak.npm ci+npm run build: exit 0.OK, no FAIL (FILE_TAGS, STEP5, STEP_FILES, LOGGING, STREAMS API, CONCURRENCY, … all OK).STEP9 OK.(cd web && npx playwright test): exit 0, 366 passed, 1 skipped.Acceptance
git grepon c1eb9df→6d2291f: nestedconcat(…concat(…))in src+tests 21 → 0; self-appendx = concat(x, …)→ 0. The 16concat(lines left in src/ are single flat joins (paths, token prefixes,pad2,config's now-flat 3-arg join," WHERE " + body) or SQLgroup_concat.StringBuilder.initdefaultsalloc = context.allocator(std/writer.sx);finish()hands the buffer out asstring{ptr,len}and empties the builder, so the caller owns it: request handlers run under the server's per-request arena,tail_roundunder the tail'sscratcharena (push .{ allocator = t.scratch }), so every handed-out buffer returns to the allocator that funded it, on success and raise paths alike (receive_file'sraise ApiErr.Refusedleavesheadto the request arena, as the concat it replaces did).reservefrees each outgrown buffer, so intermediates no longer pile up. An unwritten builder'sfinish()is an empty string; every such site reads it by.len(fts_match→fts.len > 0,view_set→sets.len == 0, WHERE bodies, empty upload→sniff_typelength checks). Diff grep for FixedWriter / c.memcpy / [*]u8 / c.malloc / c.free / @reinterpret: no added line../sakbuilt fromc1eb9dfand from this head (same sx), each served over a fresh scratch DB at--log-level debug, same port and DB path, driven by an 84-exchange raw-socket script covering project/task/view PATCH SET clauses (with parent/reference/empty patches and a 409), FTS search with embedded quotes, whitespace-only and multi-term search, every sort with and without search, label/attr/reference filters,/api/streamsstatus/opened_after queries incl. refusals, SSE replay + resume + close frames over CR / LF / CRLF / empty payloads and a CRLF result, uploads crossing and short of SNIFF_LEN, empty, wrong-type and mismatched-type, 404, and/api/logs. With timestamps/ms masked, head runs 1–3 are byte-identical to base run 1 for both responses (45488 B) and stderr (8319 B). Base differs from itself across runs at two timing-dependent points (ms ties in the default-updatedorder; the chunk boundary between a tail's replay round and its close frame), never in content. S2 above is the server-test half.Tracker: [#1635](…/tasks/1635)and cites comment 12565; PR #3 is closed with forge comment 1647 naming this PR.Review notes
data:splitting and replay/resume frames, step5_search filters/sorts, step_logging's debug line, streams_across_tasks' status/opened_after query, file_tags/step_files sniffing). No extra regression is needed.slice_of'sto <= fromguard is gone; insse_datastart <= iandstart <= payload.lenalways hold, sopayload[start .. i]is the same bytes.order_sqlwritesFTS_RANK,first, then column/dir, then thet.idtiebreak; patch SET clauses keepupdated_at(andrevision) before the field list and bind order is unchanged;writeIntrendersseqasformat("{}")did.patch_set) states the present contract and its bind-order invariant; no historical text, catalogs, sentinels or guards added. No allocator bypass, no invented @reinterpret.