1 MiB CopyFromStdin streams CSV bytes from r to a remote worker, then runs copySQL on the worker against a worker-local spool file. copySQL must contain CopyFromStdinPathPlaceholder where the file path should appear. Returns the number of rows the worker reports COPY-affected. Wire layout: frame
(ctx context.Context, copySQL string, r io.Reader)
| 41 | // from the control plane to the worker during a COPY upload. Large |
| 42 | // enough to amortise per-frame overhead, small enough to keep memory |
| 43 | // bounded if the worker is slow to drain. |
| 44 | const CopyFromStdinChunkSize = 1 << 20 // 1 MiB |
| 45 | |
| 46 | // CopyFromStdin streams COPY input bytes from r to a remote worker, then runs |
| 47 | // request.SQLTemplate against a worker-local spool file. The SQL template must |
| 48 | // contain CopyFromStdinPathPlaceholder where the file path should appear. It |
| 49 | // returns the number of rows the worker reports COPY-affected. |
| 50 | // |
| 51 | // Wire layout: |
| 52 | // |
| 53 | // frame 0: FlightDescriptor{Type=PATH, Path=[CopyFromStdinDescriptorPath, optional version], Cmd=SQL or structured request} |
| 54 | // frame 1..N: DataBody=<chunk of COPY input bytes> |
| 55 | // (client closes send) |
| 56 | // server: PutResult{AppMetadata=DoPutUpdateResult{RecordCount=N}} |
| 57 | func (e *FlightExecutor) CopyFromStdin(ctx context.Context, request sqlcore.CopyFromStdinRequest, r io.Reader) (int64, error) { |
| 58 | if e.dead.Load() { |
| 59 | return 0, ErrWorkerDead |
| 60 | } |
| 61 | |
| 62 | reqCtx, cancel := e.mergedContext(ctx) |
| 63 | defer cancel() |
| 64 | reqCtx = e.withSession(reqCtx) |
| 65 | |
| 66 | stream, err := e.client.Client.DoPut(reqCtx) |
| 67 | if err != nil { |
| 68 | return 0, fmt.Errorf("flight doput: %w", err) |
| 69 | } |
| 70 | |
| 71 | // Frame 0: descriptor + COPY SQL. |
| 72 | path := []string{CopyFromStdinDescriptorPath} |
| 73 | cmd := []byte(request.SQLTemplate) |
| 74 | if len(request.PostgresBinaryDatabaseTypeNames) > 0 { |
| 75 | path = append(path, CopyFromStdinPostgresBinaryPathVersion) |
| 76 | cmd, err = json.Marshal(request) |
| 77 | if err != nil { |
| 78 | return 0, fmt.Errorf("marshal binary COPY request: %w", err) |
| 79 | } |
| 80 | } |
| 81 | desc := &flight.FlightDescriptor{ |
| 82 | Type: flight.DescriptorPATH, |
| 83 | Path: path, |
| 84 | Cmd: cmd, |
| 85 | } |
| 86 | if err := stream.Send(&flight.FlightData{FlightDescriptor: desc}); err != nil { |
| 87 | return 0, fmt.Errorf("flight doput send descriptor: %w", err) |
| 88 | } |
| 89 | |
| 90 | // Frames 1..N: COPY input byte chunks. We re-use one buffer; gRPC marshals the |
| 91 | // proto synchronously inside Send, so reusing is safe across iterations. |
| 92 | // |
| 93 | // On a non-EOF read error we return immediately WITHOUT calling |
| 94 | // CloseSend. The deferred cancel() then aborts the gRPC stream, so the |
| 95 | // worker's Recv fails with Canceled and the worker bails before |
| 96 | // running COPY on a partial spool file. This is what makes wire-level |
| 97 | // COPY cancellation correct end-to-end. |
| 98 | buf := make([]byte, CopyFromStdinChunkSize) |
| 99 | for { |
| 100 | n, readErr := r.Read(buf) |
nothing calls this directly
no test coverage detected