<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go on Flavio Silva</title><link>https://fsilva.me/tags/go/</link><description>Recent content in Go on Flavio Silva</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>flavio1110@gmail.com (Flavio Silva)</managingEditor><webMaster>flavio1110@gmail.com (Flavio Silva)</webMaster><lastBuildDate>Tue, 14 Jul 2026 14:00:00 +0100</lastBuildDate><atom:link href="https://fsilva.me/tags/go/feed.xml" rel="self" type="application/rss+xml"/><item><title>🤖 Building a propose-then-approve bot engine</title><link>https://fsilva.me/posts/the-autonomous-bot-engine/</link><pubDate>Tue, 14 Jul 2026 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/the-autonomous-bot-engine/</guid><description>&lt;img src="https://fsilva.me/posts/the-autonomous-bot-engine/autonomous-bot-engine.png" alt="Featured image of post 🤖 Building a propose-then-approve bot engine" /&gt;&lt;p&gt;After every race weekend on &lt;a class="link" href="https://apexclub.live" target="_blank" rel="noopener"
 &gt;apexclub.live&lt;/a&gt; I used to do the same three things by hand. Enter the starting grid, enter the final classifications, trigger the scoring calculation. Every step was a few clicks and a few minutes. The annoying part wasn&amp;rsquo;t the time. It was the &lt;em&gt;kind&lt;/em&gt; of work. It&amp;rsquo;s detectable, it&amp;rsquo;s repetitive, and getting it wrong silently corrupts a season&amp;rsquo;s standings. So I wanted it gone, but only on my terms.&lt;/p&gt;
&lt;p&gt;I wanted something autonomous enough that I didn&amp;rsquo;t have to remember. Deterministic enough that I could trust it. Small enough that I could still explain every line.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;TL;DR: A recurring worker tick runs a cheap &lt;code&gt;Detect&lt;/code&gt; query for each registered use case. If there&amp;rsquo;s nothing to do, no LLM runs. If there&amp;rsquo;s a candidate, the engine builds a proposal, persists it, sends me a Telegram message with Approve / Reject buttons, and only on my tap calls a typed executor to apply the change. The LLM proposes. The database and the human approve. The executor writes.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="the-shape-of-the-problem"&gt;The shape of the problem
&lt;/h2&gt;&lt;p&gt;The original setup wasn&amp;rsquo;t bad. Race results came from analytics on Monday. The application had a Telegram bot I already trusted for one-off commands. I had a database I could read and write to. The only thing missing was glue.&lt;/p&gt;
&lt;p&gt;A naive first attempt was to let the existing chat bot propose mutations during a conversation. Press a button, the model picks a tool, the tool runs. I tried it and closed it. The problem wasn&amp;rsquo;t Telegram. It was mixing recurring operations with a conversational tool loop. Most ticks have nothing to do: the race hasn&amp;rsquo;t finished, analytics hasn&amp;rsquo;t synced, results already exist, scores are already published. Paying for an LLM call to discover that &amp;ldquo;nothing is ready&amp;rdquo; is wasteful, and worse, &amp;ldquo;the model decided to call this tool&amp;rdquo; is not an audit record I want to publish against a league&amp;rsquo;s standings.&lt;/p&gt;
&lt;p&gt;I wanted a concrete payload, a visible proposal, a named human decision, and a deterministic function that could be retried safely. So I split the work in five pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;detect&lt;/strong&gt; step that is just SQL, cheap and easy to reason about;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;propose&lt;/strong&gt; step that builds a structured payload and a human-readable summary;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;persist&lt;/strong&gt; step that stores the proposal before anything is sent anywhere;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;human decision&lt;/strong&gt; delivered through a channel I already trust (Telegram, in my case);&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;typed executor&lt;/strong&gt; that applies an approved proposal and is required to be idempotent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each piece is small. The interesting bit is the contract between them.&lt;/p&gt;
&lt;h2 id="the-contract"&gt;The contract
&lt;/h2&gt;&lt;p&gt;A use case is anything that can detect a candidate, propose a payload, and apply it. The shape, in Go, is roughly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UseCase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Propose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Proposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The four methods divide responsibility cleanly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Detect&lt;/code&gt; is a cheap precondition, usually a single &lt;code&gt;SELECT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Propose&lt;/code&gt; turns one candidate into a structured payload and an admin-facing summary.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Execute&lt;/code&gt; applies an approved action. Idempotency is required.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Kind&lt;/code&gt; is the dispatch and dedup key.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Schedule&lt;/code&gt; lives on the use case, not in central wiring.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The engine has three pieces that move a use case through that contract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;runner&lt;/strong&gt; that ticks on schedule, calls &lt;code&gt;Detect&lt;/code&gt;, and for each candidate checks whether an action already exists for the same &lt;code&gt;kind&lt;/code&gt; + subject reference. If not, it asks &lt;code&gt;Propose&lt;/code&gt; to build a proposal and hands it to the coordinator.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;coordinator&lt;/strong&gt; that owns persistence and decisions. It stores the proposal, sends the notification, atomically claims an approval, dispatches the executor, and records the outcome.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;registry&lt;/strong&gt; that maps a kind such as &lt;code&gt;insert_race_results&lt;/code&gt; to its executor. Registering the same kind twice panics at startup, which is the right moment to discover a wiring mistake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lifecycle of a persisted action is small enough to draw in text:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pending -&amp;gt; executing -&amp;gt; executed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; failed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pending -&amp;gt; rejected
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A partial unique index allows only one open action per &lt;code&gt;(kind, subject_ref)&lt;/code&gt;. That single index is what makes the runner safe to call every 30 seconds across multiple application instances, and what makes a second admin press of &amp;ldquo;Approve&amp;rdquo; a no-op instead of a duplicate write.&lt;/p&gt;
&lt;p&gt;The approval transport can be anything with two buttons. Telegram was the obvious choice because I was already living there for one-off commands. A web page could work just as well. It just has to call back into the same coordinator.&lt;/p&gt;
&lt;h2 id="the-flow-end-to-end"&gt;The flow, end to end
&lt;/h2&gt;&lt;p&gt;Here is the whole path of one piece of work, written the way the code actually runs it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;worker tick (every 30s)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; for each registered use case:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Detect() with SQL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; zero candidates: stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; candidate: is there already an action for (kind, subject)?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yes: stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; no: Propose(candidate) -&amp;gt; Proposal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Coordinator.Submit(proposal)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; persist action with status=pending
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; send approval message with action UID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Telegram button press
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Coordinator.Approve(uid) (atomic: pending -&amp;gt; executing)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Registry.Dispatch(kind).Execute(action)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; mark action executed or failed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The decision to put deterministic work first is the entire insight. The LLM only runs after SQL has already said &amp;ldquo;there is something to do here&amp;rdquo;, and only to convert that &amp;ldquo;something&amp;rdquo; into a payload a human can review.&lt;/p&gt;
&lt;h2 id="why-this-set-of-qualities"&gt;Why this set of qualities
&lt;/h2&gt;&lt;p&gt;A few properties fall out of the shape, not from extra effort.&lt;/p&gt;
&lt;p&gt;When nothing needs doing, the worker runs only &lt;code&gt;SELECT&lt;/code&gt; queries. No model call, no proposal, no notification. I called that &amp;ldquo;cheap idle ticks&amp;rdquo; above, and I underestimated how much it mattered before I built it. Most ticks &lt;em&gt;are&lt;/em&gt; idle. Saving a model call on every idle tick is the difference between a system I forget exists and a system I worry about.&lt;/p&gt;
&lt;p&gt;Auditability is mostly free. Every action is a row: &lt;code&gt;kind&lt;/code&gt;, subject reference, JSONB payload, the human-readable summary, the Telegram message identifiers, who decided it, when, and the final outcome. That&amp;rsquo;s a complete answer to &amp;ldquo;why did the standings change on Sunday at 23:14?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The executor contract is &lt;code&gt;Execute(ctx, *Action)&lt;/code&gt;. Rerunning it with the same action must converge to the same outcome. That requirement is what makes a button-mash, a network retry, or a panic mid-flight all safe. The row in &lt;code&gt;pending&lt;/code&gt; / &lt;code&gt;executing&lt;/code&gt; is the source of truth, and the executor decides whether to insert, update, or do nothing.&lt;/p&gt;
&lt;p&gt;There is no global &amp;ldquo;AI: on&amp;rdquo; switch. The system is on whenever the worker is running with a configured approval channel. The absence of an admin to approve is the absence of writes.&lt;/p&gt;
&lt;p&gt;Failure modes are independent. Each use case has its own schedule and its own advisory lock. A broken results matcher doesn&amp;rsquo;t prevent scores from being checked.&lt;/p&gt;
&lt;h2 id="two-use-cases-end-to-end"&gt;Two use cases, end to end
&lt;/h2&gt;&lt;p&gt;The same engine runs two unrelated capabilities on apexclub.live. They&amp;rsquo;re not hardcoded into the engine. They&amp;rsquo;re ordinary use cases that happen to be the first ones I needed.&lt;/p&gt;
&lt;h3 id="insert-race-results"&gt;Insert race results
&lt;/h3&gt;&lt;p&gt;The use case owns kind &lt;code&gt;insert_race_results&lt;/code&gt; and schedule &lt;code&gt;*/30 * * * *&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Its &lt;code&gt;Detect&lt;/code&gt; query is &amp;ldquo;active-season races in the past with no row in &lt;code&gt;results&lt;/code&gt; yet&amp;rdquo;. When that returns nothing, no LLM runs.&lt;/p&gt;
&lt;p&gt;When it returns a candidate, the use case:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;matches the main race to a row in an analytics calendar (the LLM only picks from offered candidates, it can&amp;rsquo;t author the mapping);&lt;/li&gt;
&lt;li&gt;pulls qualifying pole and classified finishers from the analytics tables;&lt;/li&gt;
&lt;li&gt;resolves driver names to the application&amp;rsquo;s own grid entries (again, the LLM only picks from offered IDs);&lt;/li&gt;
&lt;li&gt;assembles a structured result plus the P1–P22 finishing order;&lt;/li&gt;
&lt;li&gt;refuses incomplete mappings and surfaces them as a manual-entry notice instead of a proposal;&lt;/li&gt;
&lt;li&gt;persists the payload for approval.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On approval, &lt;code&gt;Execute&lt;/code&gt; checks for an existing result before inserting and only writes final positions when none are already recorded. A retry after a partial failure converges without overwriting manual data.&lt;/p&gt;
&lt;h3 id="publish-scores"&gt;Publish scores
&lt;/h3&gt;&lt;p&gt;The second use case owns kind &lt;code&gt;publish_scores&lt;/code&gt;, also scheduled every 30 minutes. Its &lt;code&gt;Detect&lt;/code&gt; query is &amp;ldquo;an active-season race with a results row, not yet finalized, no scores row, and at least one real starting position recorded&amp;rdquo;. Same shape, a &lt;code&gt;SELECT&lt;/code&gt; that asks whether the next step&amp;rsquo;s preconditions are met.&lt;/p&gt;
&lt;p&gt;The proposal payload is just a race identifier. On approval, the executor runs the existing scoring code and flips a finalized flag. Re-running the executor upserts and sets the same boolean, which converges safely.&lt;/p&gt;
&lt;p&gt;There is no direct call between these two use cases. The database state is the hand-off: the results executor creates the data that makes scoring eligible on a later tick. Independent schedules, independent advisory locks, independent failure handling.&lt;/p&gt;
&lt;h2 id="the-traps-in-order"&gt;The traps, in order
&lt;/h2&gt;&lt;p&gt;Building the engine was less work than fixing the things I got wrong the first time. A few worth naming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approval state can stick.&lt;/strong&gt; The first version moved an action from &lt;code&gt;pending&lt;/code&gt; to &lt;code&gt;executing&lt;/code&gt; before calling the executor, which prevented two button presses from racing. But if the executor panicked, the action stayed in &lt;code&gt;executing&lt;/code&gt;, and because &lt;code&gt;executing&lt;/code&gt; counted as open, the runner could never propose it again without a manual database update. The fix was to move the lifecycle into the coordinator: a panic is converted into an error, and &lt;code&gt;Approve&lt;/code&gt; is the only transition that marks an action &lt;code&gt;failed&lt;/code&gt;. Atomic claiming was only half the story. Every transition needs a recovery path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source identities are fuzzy.&lt;/strong&gt; The first attempt matched races by season and round number. Real data proved that wrong. For one British Grand Prix, the main calendar said round 11 while analytics said round 9, and analytics round 11 was a different race with no results yet. The LLM&amp;rsquo;s job in this system is to resolve exactly the kind of ambiguity that round numbers and slightly different race names cause. The authority stays narrow: the model picks from offered source rows, never authors them. Deterministic code then checks whether the chosen mapping is safe enough to propose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approval message is part of the safety boundary.&lt;/strong&gt; A Telegram message that shows only the top three drivers is not a complete proposal if the scoring system needs the full finishing order. Anything required for approval belongs in deterministic presentation, not in prose the model is free to shorten. The pattern that worked: build the visible fields from the assembled payload in code, and append the model-generated summary as commentary. The fields are guaranteed. The commentary is colour.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preconditions encode data completeness.&lt;/strong&gt; Scoring requires both a result and a starting grid. A result without a grid produces a plausible-looking but silently-wrong score. The &lt;code&gt;Detect&lt;/code&gt; query for scoring was the right place to encode that requirement. The engine should refuse a valid-looking operation when its inputs would produce an incorrect result.&lt;/p&gt;
&lt;p&gt;These weren&amp;rsquo;t architectural mistakes. They were the price of admitting the second iteration. The contract didn&amp;rsquo;t have to change. The implementations got better at honouring it.&lt;/p&gt;
&lt;h2 id="tradeoffs-and-limits"&gt;Tradeoffs and limits
&lt;/h2&gt;&lt;p&gt;A few honest costs.&lt;/p&gt;
&lt;p&gt;Latency is human-shaped. A use case that detects at 30 seconds and a human who approves five minutes later means a five-minute gap between readiness and execution. For race weekend admin that&amp;rsquo;s fine. For anything time-sensitive, propose-then-approve is the wrong shape.&lt;/p&gt;
&lt;p&gt;Approval delivery is one channel. Today the only way to approve is Telegram. If the notification fails to send, the action is persisted in &lt;code&gt;pending&lt;/code&gt; and stays there until a human notices or a redelivery job is added. Persistence beats loss, but it&amp;rsquo;s not the same as a guaranteed delivery.&lt;/p&gt;
&lt;p&gt;One primary approver. Notifications go to the first ID in the allowlist. Every allowlisted user can press the buttons, but only one gets the message. A real multi-admin workflow needs a delivery and decision policy, not just an allowlist.&lt;/p&gt;
&lt;p&gt;Rejected actions don&amp;rsquo;t auto-retry. The repository layer suppresses future proposals for a rejected &lt;code&gt;(kind, subject_ref)&lt;/code&gt; to avoid nagging. There is no &amp;ldquo;retry this one&amp;rdquo; operation that doesn&amp;rsquo;t also require a policy or data change. For one-shot work that&amp;rsquo;s the right default. For evolving inputs it can feel stubborn.&lt;/p&gt;
&lt;p&gt;Fuzzy matching is bounded. The model can only pick from offered candidates. That&amp;rsquo;s a strong guardrail and also a real ceiling. When the input source gets a new spelling or a new driver, the matcher will refuse rather than guess. Human review remains mandatory.&lt;/p&gt;
&lt;h2 id="where-id-take-it-next"&gt;Where I&amp;rsquo;d take it next
&lt;/h2&gt;&lt;p&gt;The next changes I&amp;rsquo;d make are unglamorous:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A redelivery job for &lt;code&gt;pending&lt;/code&gt; actions whose Telegram message failed.&lt;/li&gt;
&lt;li&gt;A web approval page that calls the same coordinator. It doesn&amp;rsquo;t need to replace Telegram. It just needs to be a second transport.&lt;/li&gt;
&lt;li&gt;An explicit retry / reopen transition for rejected subjects, gated by policy rather than by data manipulation.&lt;/li&gt;
&lt;li&gt;More use cases. The shape proved out on results and scoring. The next two I&amp;rsquo;m eyeing are joker-round announcements and end-of-season standings exports. Both are detectable, both are one-shot, both have a payload I want to review before they go out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The thing I would &lt;em&gt;not&lt;/em&gt; change is the order. Cheap detect, deterministic propose, human approve, typed execute. Each piece is replaceable. The order is the engine.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The engine is small because it refuses to solve every agent problem. It works for recurring, detectable, one-shot changes with a payload that can be reviewed before it&amp;rsquo;s applied. &amp;ldquo;Autonomous&amp;rdquo; here means the system notices and prepares work by itself. Authority to change league results still belongs to a person.&lt;/p&gt;
&lt;p&gt;What kind of recurring work would you trust it to propose, but not to approve?&lt;/p&gt;</description></item><item><title>🐔 What happened with my project on the Rinha de Backend challenge</title><link>https://fsilva.me/posts/rinha-de-backend/</link><pubDate>Mon, 28 Aug 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/rinha-de-backend/</guid><description>&lt;img src="https://fsilva.me/posts/rinha-de-backend/rooster_fight.png" alt="Featured image of post 🐔 What happened with my project on the Rinha de Backend challenge" /&gt;&lt;p&gt;I participated in the &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3" target="_blank" rel="noopener"
 &gt;Rinha de backend (pt-BR)&lt;/a&gt; or &lt;a class="link" href="https://en.wikipedia.org/wiki/Cockfight" target="_blank" rel="noopener"
 &gt;&amp;ldquo;Backend Rooster Fight&amp;rdquo; in English&lt;/a&gt; challenge and I was super excited to see the results of my super hacky and performant solution.&lt;/p&gt;
&lt;p&gt;The results were scheduled to be published at 21:00 BRT (2 AM CEST my local time). So I stayed up til later that night grabbed popcorn, and waited for the results.&lt;/p&gt;
&lt;p&gt;Well, I wish I hadn&amp;rsquo;t waited&amp;hellip;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;TL;DR; My solution didn&amp;rsquo;t run on the official test server because I built the docker image to &lt;code&gt;linux/arm64&lt;/code&gt; instead of &lt;code&gt;linux/amd64&lt;/code&gt; 🤦🤦🤦 . I fixed the issue and ran the same tests afterward and I reached ~50% of points of the winner, mainly because in my solution I didn&amp;rsquo;t tolerate any risk of losing writes. Checkout my repository &lt;a class="link" href="https://github.com/flavio1110/rinha-de-backend" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;flavio1110/rinha-de-backend&lt;/strong&gt;&lt;/a&gt; with my solution.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="what-was-the-rinha-de-backend"&gt;What was the Rinha de Backend?
&lt;/h3&gt;&lt;p&gt;&lt;em&gt;Before we get into the details, a brief explanation of what was the challenge.&lt;/em&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;From July 28th to August 25th, the Backend Fight was held, a tournament in which the API that supported the most load during a stress test would be the winner. Participants had to implement an API with endpoints to create, query and search for &amp;lsquo;people&amp;rsquo; (a kind of CRUD without UPDATE and DELETE). In the tournament, participants still had to deal with CPU and memory restrictions – each participant had to deliver the API in docker-compose format and could only use 1.5 CPU units and 3GB of memory. More details on technical aspects can be found in the instructions) – &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/tree/main#o-que-%C3%A9" target="_blank" rel="noopener"
 &gt;(translated from its repository in English)&lt;/a&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;On top of the description above, some things important to highlight are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can write your API with any language or framework, as long as you can build a docker image out of it&lt;/li&gt;
&lt;li&gt;The solution has to run two instances of the API in parallel.&lt;/li&gt;
&lt;li&gt;The requests will be balanced as you wish between these instances via Nginx.&lt;/li&gt;
&lt;li&gt;You have to use one of the following data stores: MySQL, PostgreSQL, or MongoDB.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It was a super fun, informal, practical, and a very good opportunity to exercise and learn new things.&lt;/p&gt;
&lt;h4 id="how-the-winners-were-determined"&gt;How the winners were determined?
&lt;/h4&gt;&lt;p&gt;To make the comparison easier and fun, the &lt;strong&gt;single parameter&lt;/strong&gt; used to determine the winner of the challenge was the &lt;strong&gt;number of people inserted&lt;/strong&gt; into the database.&lt;/p&gt;
&lt;h2 id="how-did-i-do"&gt;How did I do?
&lt;/h2&gt;&lt;figure class="aligncenter"&gt;
 &lt;img src="https://fsilva.me/images/rinha-2023-q3-meme.jpeg" alt="my solution explodes" /&gt;
 &lt;figcaption&gt;POV you are looking at me while I check the results.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I didn&amp;rsquo;t. I was disqualified because the container with my APIs didn&amp;rsquo;t start. Therefore I got nothing. 🤦🤦🤦&lt;/p&gt;
&lt;h3 id="wrong-platform"&gt;Wrong platform?
&lt;/h3&gt;&lt;p&gt;Yes, that&amp;rsquo;s true. My solution didn&amp;rsquo;t even run because &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/blob/main/resultados/primeira-fase/flavio1110/docker-compose.logs#L63" target="_blank" rel="noopener"
 &gt;it failed to start the APIs&lt;/a&gt;. I built the images for &lt;code&gt;linux/arm64&lt;/code&gt; instead of &lt;code&gt;linux/amd64&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It was a mistake on my end, and it made me super disappointed. I ran the tests on the &lt;a class="link" href="https://github.com/flavio1110/rinha-de-backend/actions/runs/5949338191" target="_blank" rel="noopener"
 &gt;CI pipelines&lt;/a&gt;, and it gave me confidence the image was properly built and I didn&amp;rsquo;t double-check the requirements. Shame on me.&lt;/p&gt;
&lt;p&gt;Well, I was disqualified but I was curious to see how it would perform on a server with the &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/blob/main/misc/lshw-aws" target="_blank" rel="noopener"
 &gt;same configuration as the one used for the official tests&lt;/a&gt;. So, I fixed the platform of the image, spun up an EC2 with the same configuration, and finally ran the tests. The results were a bit disappointing compared to the TOP 10.&lt;/p&gt;
&lt;h3 id="how-did-i-approach-the-problem"&gt;How did I approach the problem?
&lt;/h3&gt;&lt;p&gt;I had two constraints in mind while designing and building:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It has to work – each endpoint has to do what it is supposed to do.&lt;/li&gt;
&lt;li&gt;Keep things as simple as possible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of these points brought a few tradeoffs that ended up limiting the overall performance of the solution.&lt;/p&gt;
&lt;p&gt;You can check my solution on &lt;a class="link" href="https://github.com/flavio1110/rinha-de-backend" target="_blank" rel="noopener"
 &gt;flavio1110/rinha-de-backend&lt;/a&gt;. It&amp;rsquo;s built with Go and PostgreSQL.&lt;/p&gt;
&lt;p&gt;Given many APIs were performing super well, a few days before the last day of the challenge, &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/commit/be637c944434bdddfe4a92a31bd17cd528f68a38" target="_blank" rel="noopener"
 &gt;the load for the stress test was doubled&lt;/a&gt;, and it had a massive impact on the performance of my API. You can check the comparison below:&lt;/p&gt;
&lt;p&gt;Before&amp;hellip;&lt;/p&gt;
&lt;figure class="aligncenter"&gt;
 &lt;a href="https://fsilva.me/rinha-de-backend/rinhabackendsimulation-20230821183911665/" target="_blank"&gt;&lt;img src="https://fsilva.me/images/gh_results_before.png" alt="rooster fight" /&gt;&lt;/a&gt;
 &lt;figcaption&gt;Print of part of the report generated by Gatling of my solution run on Github BEFORE the load was doubled.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After&amp;hellip;&lt;/p&gt;
&lt;figure class="aligncenter"&gt;
 &lt;a href="https://fsilva.me/rinha-de-backend/rinhabackendsimulation-20230823091057409/" target="_blank"&gt;&lt;img src="https://fsilva.me/images/gh_results_after.png" alt="rooster fight" /&gt;&lt;/a&gt;
 &lt;figcaption&gt;Print of part of the report generated by Gatling of my solution run on Github AFTER the load was doubled.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="what-were-the-bottlenecks"&gt;What were the bottlenecks?
&lt;/h3&gt;&lt;p&gt;The inserts were the bottleneck. Because I didn&amp;rsquo;t tolerate the risk of losing writes and wanted to keep it simple, I missed the opportunity to introduce a distributed mechanism to validate the data before trying to insert it into the database and perform the inserts in batch.&lt;/p&gt;
&lt;h4 id="validating-the-uniqueness-of-a-field-apelido-nickname"&gt;Validating the uniqueness of a field &lt;em&gt;Apelido&lt;/em&gt; (Nickname)
&lt;/h4&gt;&lt;p&gt;On my designed solution, without a distributed cache, it was not possible to perform without the DB in 100% of the cases.&lt;/p&gt;
&lt;p&gt;I had an in-memory cache, but if the nickname wasn&amp;rsquo;t there because it wasn&amp;rsquo;t there because the entry was inserted via the other instance, that request would reach the DB, occupy a connection, take resources, etc.&lt;/p&gt;
&lt;p&gt;Introducing a distributed cache like &lt;a class="link" href="https://redis.uptrace.dev/" target="_blank" rel="noopener"
 &gt;Redis&lt;/a&gt; would enable me to add and check the entry in a single place. As a result, we could decrease the memory necessary to run the APIs and move to the cache.&lt;/p&gt;
&lt;h4 id="batching-inserts"&gt;Batching inserts
&lt;/h4&gt;&lt;p&gt;I inserted each entry per a valid request because I didn&amp;rsquo;t want to risk losing valid writes in case there was any error between the construction of the batch and its execution.&lt;/p&gt;
&lt;p&gt;This was a huge problem because it used too many DB connections and resources, and slowed down the creation request.&lt;/p&gt;
&lt;h3 id="did-i-like-it"&gt;Did I like it?
&lt;/h3&gt;&lt;p&gt;Despite my results, I loved the challenge! I learned quite some tricks with nginx config and some insteresting stuff like using &lt;a class="link" href="https://redis.io/docs/interact/pubsub/" target="_blank" rel="noopener"
 &gt;Redis&lt;/a&gt; or even &lt;a class="link" href="https://www.postgresql.org/docs/current/sql-notify.html" target="_blank" rel="noopener"
 &gt;PostgreSQL&lt;/a&gt; as a &amp;ldquo;PubSub&amp;rdquo;.
The interaction with the community via &lt;a class="link" href="https://twitter.com/flavio1110" target="_blank" rel="noopener"
 &gt;Twitter&lt;/a&gt; and Github was super nice!
I also liked the fact of working with something challenging and closer to reality. Loved it!&lt;/p&gt;
&lt;h3 id="what-could-i-have-done-differently"&gt;What could I have done differently?
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Build the image using the correct platform.&lt;/li&gt;
&lt;li&gt;A challenge is a challenge. I should have been more flexible with the writing.&lt;/li&gt;
&lt;li&gt;Tweak the Nginx configuration to load balance based on fewer connections and disable logs.&lt;/li&gt;
&lt;li&gt;Use a distributed cache and drop the read table.&lt;/li&gt;
&lt;li&gt;Batch inserts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?
&lt;/h2&gt;&lt;p&gt;Check the &lt;a class="link" href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3#resultados" target="_blank" rel="noopener"
 &gt;official results&lt;/a&gt; and look into the repositories of the participants. I guarantee I&amp;rsquo;ll learn something new.&lt;/p&gt;
&lt;p&gt;Stay tuned on &lt;a class="link" href="https://twitter.com/rinhadebackend" target="_blank" rel="noopener"
 &gt;@rinhadebackend&lt;/a&gt; for the next challenges, I can&amp;rsquo;t wait for the next ones.&lt;/p&gt;
&lt;p&gt;Other than that, I&amp;rsquo;ll apply the lessons learned to my solution and hopefully get better results. Watch &lt;a class="link" href="https://github.com/flavio1110/" target="_blank" rel="noopener"
 &gt;flavio1110/rinha-de-backend&lt;/a&gt; and see how it will evolve.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This challenge is officially over, but you can still do it. Challenge accepted?&lt;/em&gt;&lt;/p&gt;</description></item><item><title>🐉 got: Embracing my lazyness with my custom git CLI</title><link>https://fsilva.me/posts/embracing-my-lazyness-with-a-custom-git-cli/</link><pubDate>Thu, 30 Mar 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/embracing-my-lazyness-with-a-custom-git-cli/</guid><description>&lt;img src="https://fsilva.me/posts/embracing-my-lazyness-with-a-custom-git-cli/got.png" alt="Featured image of post 🐉 got: Embracing my lazyness with my custom git CLI" /&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/flavio1110/got" target="_blank" rel="noopener"
 &gt;got&lt;/a&gt; is a CLI written in Go, created on top of the git CLI, to make my life easier by shortening some commands I use daily.&lt;/p&gt;
&lt;p&gt;Because it&amp;rsquo;s built on top of git, all git commands will also work just fine and I don&amp;rsquo;t need to keep switching between &lt;code&gt;got&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;If I want to clean up my local dead branches I can &lt;code&gt;got rmb&lt;/code&gt; instead of &lt;code&gt;git branch | grep -v &amp;quot;main&amp;quot; | xargs git branch -D&lt;/code&gt; (you can call me lazy. I accept that 😅).&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://xkcd.com/1319/" target="_blank" rel="noopener"
 &gt;&lt;img src="https://imgs.xkcd.com/comics/automation_2x.png" alt="xckd 1319" /&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;a class="link" href="https://xkcd.com/1319/" target="_blank" rel="noopener"
 &gt;xkcd&lt;/a&gt; Automation.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="but-why"&gt;But&amp;hellip; Why?
&lt;/h3&gt;&lt;p&gt;My first experience with a Distributed version control system (DVCS), was many years ago with &lt;a class="link" href="https://en.wikipedia.org/wiki/Mercurial" target="_blank" rel="noopener"
 &gt;mercurial hg&lt;/a&gt;, and despite taking some time to get used to the new way of working as compared to a centralized version control system, I got very used to &lt;a class="link" href="https://gist.github.com/cortesben/016cd401faae5a8dae59" target="_blank" rel="noopener"
 &gt;its short commands&lt;/a&gt;, aliases, and simplicity.
We could do something like &lt;code&gt;hg pull&lt;/code&gt; or &lt;code&gt;hg pul&lt;/code&gt; (that&amp;rsquo;s not a typo. This is an actual alias for &lt;code&gt;hg pull&lt;/code&gt;), we were able to close a branch and commit a message in a single command like &lt;code&gt;hg commit --close-branch -m 'closing this branch'&lt;/code&gt;. It was super nice and handy!
Then, eventually, I started working with &lt;a class="link" href="https://git-scm.com/" target="_blank" rel="noopener"
 &gt;git&lt;/a&gt; and I was amazed by its differences, possibilities, and features. However, it felt more verbose for day-to-day tasks. So I ended up creating a bunch of aliases for the commands that I use more often, or commands that are longer and I always have to google it to remember.
Sneak peek of my &lt;code&gt;~/.zshrc&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;git push origin head&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;stat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;git status -s&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;gbr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;git branch | grep -v &amp;#34;&lt;/span&gt;main&lt;span class="s2"&gt;&amp;#34; | xargs git branch -D&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;git cherry-pick&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and a few more...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Speaking on &lt;a class="link" href="https://ohmyz.sh/" target="_blank" rel="noopener"
 &gt;zsh&lt;/a&gt;, if you use it with the &lt;code&gt;go&lt;/code&gt; plugin, you will have a conflict with the alias &lt;code&gt;got&lt;/code&gt; for &lt;code&gt;go test&lt;/code&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;The aliases work so well and are easy to maintain&amp;hellip; But I wanted something fancier and all have the opportunity to go through the process of writing a CLI in Go.&lt;/p&gt;
&lt;h3 id="how"&gt;How
&lt;/h3&gt;&lt;p&gt;Got is built using &lt;a class="link" href="https://github.com/spf13/cobra" target="_blank" rel="noopener"
 &gt;cobra&lt;/a&gt;, which makes the work so much easier. Cobra has its own &lt;a class="link" href="https://github.com/spf13/cobra-cli/blob/main/README.md" target="_blank" rel="noopener"
 &gt;CLI called Cobra Generator&lt;/a&gt; that helps bootstrap your CLI project and add commands. You can check the full documentation &lt;a class="link" href="https://github.com/spf13/cobra-cli/blob/main/README.md" target="_blank" rel="noopener"
 &gt;here&lt;/a&gt;, but here goes the basic usage:&lt;/p&gt;
&lt;h4 id="install"&gt;Install
&lt;/h4&gt;&lt;p&gt;With go installed, open the terminal and execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/spf13/cobra-cli@latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="booststraping-your-cli"&gt;Booststraping your CLI
&lt;/h4&gt;&lt;p&gt;Navigate to a folder that you want to have your project, init a go module, then execute the cobra init. e.g&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go mod init my-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cobra-cli init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Voilá! You have your custom CLI, ad you can run it with &lt;code&gt;go run .&lt;/code&gt;! You will see a result like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A longer description that spans multiple lines and likely contains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;examples and usage of using your application. For example:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cobra is a CLI library &lt;span class="k"&gt;for&lt;/span&gt; Go that empowers applications.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This application is a tool to generate the needed files
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;to quickly create a Cobra application.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can now add a command with executing &lt;code&gt;cobra-cli add ping&lt;/code&gt;, then execute the command again. You will see a result like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A longer description that spans multiple lines and likely contains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;examples and usage of using your application. For example:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cobra is a CLI library &lt;span class="k"&gt;for&lt;/span&gt; Go that empowers applications.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This application is a tool to generate the needed files
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;to quickly create a Cobra application.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-cli &lt;span class="o"&gt;[&lt;/span&gt;command&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Available Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; completion Generate the autocompletion script &lt;span class="k"&gt;for&lt;/span&gt; the specified shell
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;help&lt;/span&gt; Help about any &lt;span class="nb"&gt;command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ping A brief description of your &lt;span class="nb"&gt;command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Flags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -h, --help &lt;span class="nb"&gt;help&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; my-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -t, --toggle Help message &lt;span class="k"&gt;for&lt;/span&gt; toggle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Use &lt;span class="s2"&gt;&amp;#34;my-cli [command] --help&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information about a command.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The foundation for your CLI is in place, you &lt;em&gt;only&lt;/em&gt; need to worry about the actual logic of the commands because &lt;a class="link" href="https://github.com/spf13/cobra" target="_blank" rel="noopener"
 &gt;cobra&lt;/a&gt; will take care of all the pumbling for you.&lt;/p&gt;
&lt;p&gt;You can check several examples in the &lt;a class="link" href="https://git,hub.com/spf13/cobra/blob/main/user_guide.md" target="_blank" rel="noopener"
 &gt;cobra&lt;/a&gt;&lt;a class="link" href="https://git,hub.com/spf13/cobra/blob/main/user_guide.md" target="_blank" rel="noopener"
 &gt; documentation&lt;/a&gt;, and use it as a base to create your shine CLI.&lt;/p&gt;
&lt;h3 id="ok-what-about-the-logic-in-got"&gt;Ok, what about the logic in got?
&lt;/h3&gt;&lt;p&gt;In got I have two ways of executing some logic. 1) using &lt;a class="link" href="https://github.com/go-git/go-git" target="_blank" rel="noopener"
 &gt;go-git&lt;/a&gt; to perform some actions like iterate in all local branches and delete all except main, and 2) executing a git cli command from my Go application directly.&lt;/p&gt;
&lt;h4 id="go-git"&gt;go-git
&lt;/h4&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;a class="link" href="https://github.com/go-git/go-git" target="_blank" rel="noopener"
 &gt;go-git&lt;/a&gt; is a highly extensible git implementation library written in pure Go. It can be used to manipulate git repositories at low level (plumbing) or high level (porcelain), through an idiomatic Go API. - (Description from its repo).&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;It is such a well-documented, flexible, and powerful lib. I highly recommend looking into it if you even thought to do something with git, or if you intend to create your own lib. 10/10!&lt;/p&gt;
&lt;p&gt;The example below stages all files (including untracked) and commit them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// get the current path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;exitIfError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PlainOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// open the repository related to the path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;exitIfError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Worktree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// get the current worktree&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;exitIfError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Stage all files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;exitIfError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;commit yay!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CommitOptions&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// commit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;exitIfError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I have to say that for basic operations like this one, we could be better served by executing the git cli directly like in the example below. However, &lt;em&gt;go-git&lt;/em&gt; opens several possibilities like in the support of several type of storage, such as in-memory, file system, or anything you can think of as long as you implement the &lt;a class="link" href="https://pkg.go.dev/github.com/go-git/go-git/v5/plumbing/storer" target="_blank" rel="noopener"
 &gt;&lt;code&gt;Storer&lt;/code&gt;&lt;/a&gt; interface.
At this moment I&amp;rsquo;m not using such features yet, but I&amp;rsquo;m planning to use them for some commands like &lt;code&gt;got squash&lt;/code&gt; that will squash all commits of the current branch.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;git&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;add&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;-A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;git&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;commit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;commit yay!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="fallback-to-git"&gt;Fallback to git
&lt;/h4&gt;&lt;p&gt;It is important because I wanted to &amp;ldquo;proxy&amp;rdquo; all unknown &lt;em&gt;got&lt;/em&gt; commands to &lt;em&gt;git&lt;/em&gt;. In this way, I can use &lt;em&gt;got&lt;/em&gt; for my custom commands and the standard &lt;em&gt;git&lt;/em&gt; commands without worrying about what is available where.
The other benefit is I don&amp;rsquo;t need to implement things that are good enough or I don&amp;rsquo;t use so often.&lt;/p&gt;
&lt;p&gt;Implementing it was fairly simple.
&lt;em&gt;The snippet below is part of the &lt;a class="link" href="https://github.com/flavio1110/got/blob/main/cmd/root.go" target="_blank" rel="noopener"
 &gt;&lt;code&gt;root.go&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rootCmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;fallbackToGit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fallbackToGit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;git&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Skipping error here, because git already sends it to stdout, and I don&amp;#39;t have anything else to do with it.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="conclusion-and-whats-next"&gt;Conclusion and what&amp;rsquo;s next?
&lt;/h3&gt;&lt;p&gt;This small project started as an experiment for playing with writing a custom CLI, but I ended up creating something that is very useful for me. Win-win!&lt;/p&gt;
&lt;p&gt;I still have a few commands I want to introduce in &lt;code&gt;got&lt;/code&gt;, but my next step is to create a reasonable test suit, so I can have confidence that things work as they suppose to. This is especially important given it is responsible for my interaction with &lt;code&gt;git&lt;/code&gt;. That&amp;rsquo;s a big deal!&lt;/p&gt;
&lt;p&gt;Anyways, I created it to attend to my laziness, but if &lt;code&gt;got&lt;/code&gt; looks interesting to you, feel free to use, fork, and contribute. 💪&lt;/p&gt;
&lt;p&gt;I hope this post can help you to see how easy is to write a CLI using Go, and maybe can inspire you to identify things that you do daily and can be somehow optimized.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Till next time o/&lt;/p&gt;</description></item><item><title>📂 Go: Importig a CSV to PostgreSQL</title><link>https://fsilva.me/posts/csv-import-go-postgresql/</link><pubDate>Sun, 26 Mar 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/csv-import-go-postgresql/</guid><description>&lt;img src="https://fsilva.me/posts/csv-import-go-postgresql/csv-rainbow.webp" alt="Featured image of post 📂 Go: Importig a CSV to PostgreSQL" /&gt;&lt;p&gt;If you are using Go ad PostgreSQL, and need to performa a bulk import a CSV,
it&amp;rsquo;s most likely you will find the &lt;code&gt;COPY&lt;/code&gt; protocol is the &lt;a class="link" href="https://www.postgresql.org/docs/current/sql-copy.html" target="_blank" rel="noopener"
 &gt;feature&lt;/a&gt; that suits you better.
In that direction, you will find examples using &lt;a class="link" href="https://github.com/jackc/pgx" target="_blank" rel="noopener"
 &gt;pgx&lt;/a&gt; &lt;a class="link" href="https://pkg.go.dev/github.com/jackc/pgx/v4#Conn.CopyFrom" target="_blank" rel="noopener"
 &gt;CopyFrom&lt;/a&gt; that relies on the native protocol, and it&amp;rsquo;s fairly easy to use.
&lt;strong&gt;However, depending how it&amp;rsquo;s used you can have an exponencial increase of memory consumption of your application making it unreliable and more expensive to run.&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;TL;DR;: &lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt; load the file in memory and use &lt;code&gt;pgx.CopyFromRows&lt;/code&gt;. Instead, use the &lt;code&gt;io.Reader&lt;/code&gt; of the file and implement a custom &lt;code&gt;pgx.CopyFromSource&lt;/code&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;a class="link" href="https://github.com/flavio1110/large-csv-to-pgsql" target="_blank" rel="noopener"
 &gt;Checkout the repository with examples and details presented here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="context"&gt;Context
&lt;/h3&gt;&lt;p&gt;Most of the examples out there, are either using &lt;a class="link" href="https://pkg.go.dev/github.com/jackc/pgx/v4#CopyFromRows" target="_blank" rel="noopener"
 &gt;CopyFromRows&lt;/a&gt; or &lt;a class="link" href="https://pkg.go.dev/github.com/jackc/pgx/v4#CopyFromSlice" target="_blank" rel="noopener"
 &gt;CopyFromSlice&lt;/a&gt;. However, the big problem is these two options require you to have the entire content in memory to use.
This is not a big deal when dealing with small files, there won&amp;rsquo;t be concurrent usage, or you have infinite memory 😅.&lt;/p&gt;
&lt;h3 id="how-big-is-the-problem"&gt;How big is the problem?
&lt;/h3&gt;&lt;p&gt;Comparing the memory consumption for the two distinct approaches importing a file with ~16MB (1M rows).&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Approach/metric&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;TotalAlloc&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Sys&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Stream file&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;61 MiB&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12 Mib&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Read entire file&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;84 MiB&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;58 Mib&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;strong&gt;+37.70%&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;strong&gt;+346.15%&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Yes,&lt;/strong&gt; you read it right! using &lt;code&gt;CopyFromRows&lt;/code&gt; obtained +346.15% of memory from the OS! 58 MiB instead of 12 MiB.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You can read more about the meaning of each metric on &lt;a class="link" href="https://golang.org/pkg/runtime/#MemStats" target="_blank" rel="noopener"
 &gt;https://golang.org/pkg/runtime/#MemStats&lt;/a&gt; and find the source code and details of the comparisson on &lt;a class="link" href="https://github.com/flavio1110/large-csv-to-pgsql" target="_blank" rel="noopener"
 &gt;this repository&lt;/a&gt;..&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="whats-the-most-efficient-way-for-using-the-copy-protocol-with-pgx"&gt;What&amp;rsquo;s the most efficient way for using the COPY protocol with pgx?
&lt;/h3&gt;&lt;p&gt;Instead of reading the entire in memory, the idea is to stream each line of the file directly to PostgreSQL. In this way, we only need to keep the current line in-memory as opposed to the entire file.&lt;/p&gt;
&lt;h4 id="how-can-we-do-it"&gt;How can we do it?
&lt;/h4&gt;&lt;p&gt;The &lt;a class="link" href="https://github.com/jackc/pgx/blob/master/copy_from.go#LL238C21-L238C21" target="_blank" rel="noopener"
 &gt;CopyFrom&lt;/a&gt; method receives an implementation of the interface &lt;a class="link" href="https://github.com/jackc/pgx/blob/master/copy_from.go#L68" target="_blank" rel="noopener"
 &gt;CopyFromSource&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s implement this interface using a CSV file with the followng three columns: first_name, last_name, and city.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;span class="lnt"&gt;59
&lt;/span&gt;&lt;span class="lnt"&gt;60
&lt;/span&gt;&lt;span class="lnt"&gt;61
&lt;/span&gt;&lt;span class="lnt"&gt;62
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newPeopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReuseRecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// reuse slice to return the record line by line&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FieldsPerRecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csvReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isBOF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// first line is header&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;peopleColumns&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Reader&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isEOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isBOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the order of the elements of the record array, must match with&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the order of the columns in passed into the copy method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCsvRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// when get to the end of the file return false and clean the error.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If it&amp;#39;s io.EOF we can&amp;#39;t return an error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;peopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pfs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can now use this implementation in the &lt;code&gt;CopyFrom&lt;/code&gt; method. e.g.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pgxConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pgx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;people&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;peopleColumns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newPeopleCopyFromSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvStream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="conclusion"&gt;Conclusion
&lt;/h3&gt;&lt;p&gt;Using the &lt;code&gt;CopyFrom&lt;/code&gt; with &lt;code&gt;CopyFromRows&lt;/code&gt; or &lt;code&gt;CopyFrom&lt;/code&gt; will significantly increase the memory comsultion of your application. The high memory usage can bring several problems like OOM errors, increase of costs, unavailability, etc.&lt;/p&gt;
&lt;p&gt;By using a custom implementation of &lt;a class="link" href="https://github.com/jackc/pgx/blob/master/copy_from.go#L68" target="_blank" rel="noopener"
 &gt;CopyFromSource&lt;/a&gt; will make your application much more efficient, reliable, and cheaper to ru.&lt;/p&gt;
&lt;p&gt;You can find the entire source code of the examples above on &lt;a class="link" href="https://github.com/flavio1110/large-csv-to-pgsql" target="_blank" rel="noopener"
 &gt;this repository&lt;/a&gt;. There you will also find more deatils about the comparisson and the not-so-great implementation.&lt;/p&gt;</description></item><item><title>🔢 Go: Where is the decimal type?</title><link>https://fsilva.me/posts/go-decimal-type/</link><pubDate>Tue, 21 Mar 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/go-decimal-type/</guid><description>&lt;img src="https://fsilva.me/posts/go-decimal-type/gopher-side-eye.webp" alt="Featured image of post 🔢 Go: Where is the decimal type?" /&gt;&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt; doesn&amp;rsquo;t have a primitive &lt;code&gt;decimal&lt;/code&gt; type for arbitrary-precision fixed-point decimal numbers. Yes, you read it right. Therefore, if you need to deal with fixed-point precision there are two main options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use an external package like &lt;a class="link" href="https://github.com/shopspring/decimal" target="_blank" rel="noopener"
 &gt;decimal&lt;/a&gt;, which introduces the &lt;code&gt;decimal&lt;/code&gt; type. However, the current version (1.3.1), can &amp;ldquo;only&amp;rdquo; represent numbers with a maximum of 2^31 digits after the decimal point.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;int64&lt;/code&gt; to store and deal with these numbers. For e.g. given you need 6 precision digits, therefore &lt;code&gt;79.23&lt;/code&gt;, &lt;code&gt;23.00&lt;/code&gt;, and &lt;code&gt;54.123456&lt;/code&gt;, become respectively &lt;code&gt;79230000&lt;/code&gt;, &lt;code&gt;23000000&lt;/code&gt;, and &lt;code&gt;54123456&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is an &lt;a class="link" href="https://github.com/golang/go/issues/19787#issue-218228389" target="_blank" rel="noopener"
 &gt;open proposal&lt;/a&gt; to add decimal float types (IEEE 754-2008) in the std lib. However, for now, it&amp;rsquo;s just a proposal being discussed, without guarantee it will be ever added.&lt;/p&gt;</description></item><item><title>🧠 Go resources for beginners</title><link>https://fsilva.me/posts/go-resources/</link><pubDate>Sun, 19 Mar 2023 14:00:00 +0100</pubDate><author>flavio1110@gmail.com (Flavio Silva)</author><guid>https://fsilva.me/posts/go-resources/</guid><description>&lt;img src="https://fsilva.me/posts/go-resources/gophers.webp" alt="Featured image of post 🧠 Go resources for beginners" /&gt;&lt;p&gt;Below a living list of compiled links for whoever is learning &lt;strong&gt;Go&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="official-docs"&gt;Official docs
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Effective Go&lt;/strong&gt; - &lt;a class="link" href="https://go.dev/doc/effective_go" target="_blank" rel="noopener"
 &gt;https://go.dev/doc/effective_go&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A document that gives tips for writing clear, idiomatic Go code. A must-read for any new Go programmer. It augments the tour and the language specification, both of which should be read first.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Go Code Review Comments&lt;/strong&gt; &lt;a class="link" href="https://github.com/golang/go/wiki/CodeReviewComment" target="_blank" rel="noopener"
 &gt;https://github.com/golang/go/wiki/CodeReviewComment&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This page collects common comments made during reviews of Go code, so that a single detailed explanation can be referred to by shorthands. This is a laundry list of common mistakes, not a comprehensive style guide. You can view this as a supplement to Effective Go.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go Docs&lt;/strong&gt; - &lt;a class="link" href="https://go.dev/doc/" target="_blank" rel="noopener"
 &gt;https://go.dev/doc/&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Root for many useful documentations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FAQ&lt;/strong&gt; - &lt;a class="link" href="https://go.dev/doc/faq" target="_blank" rel="noopener"
 &gt;https://go.dev/doc/faq&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Answers to common questions about Go.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="blogs"&gt;Blogs
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dave Cheney&lt;/strong&gt; - &lt;a class="link" href="https://dave.cheney.net/" target="_blank" rel="noopener"
 &gt;https://dave.cheney.net/&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Specially the &lt;a class="link" href="https://dave.cheney.net/practical-go" target="_blank" rel="noopener"
 &gt;practical go section&lt;/a&gt;, it has TONS of good advice and real-world examples of how to deal with daily challenges. I highly recommend it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="courses"&gt;Courses
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The way to go&lt;/strong&gt; - &lt;a class="link" href="https://www.educative.io/courses/the-way-to-go" target="_blank" rel="noopener"
 &gt;https://www.educative.io/courses/the-way-to-go&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s a course from educative.io, it goes from the basics concepts of the language to more advanced ones. It also brings very interesting insights into the differences between the approaches of Java/C# to Go..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How to code in go&lt;/strong&gt; - &lt;a class="link" href="https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go" target="_blank" rel="noopener"
 &gt;https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A great collection of tutorials that cover basic Go concepts, ideal for beginers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="videos"&gt;Videos
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go in 100 seconds&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=446E-r0rXHI&amp;amp;t=38s" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=446E-r0rXHI&amp;t=38s&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Short introduction about Go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity is complicated&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=rFejpH_tAHM" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=rFejpH_tAHM&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Rob Pike talks about how Go is often described as a simple language. It is not, it just seems that way. Rob explains how Go&amp;rsquo;s simplicity hides a great deal of complexity, and that both the simplicity and complexity are part of the design.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency is not parallelism&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=qmg1CF3gZQ0&amp;amp;t=1582s" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=qmg1CF3gZQ0&amp;t=1582s&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Rob Pike talks about concurrency and how Go implements it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Understanding channels&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=KBZlN0izeiY&amp;amp;t=1011s" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=KBZlN0izeiY&amp;t=1011s&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Channels provide a simple mechanism for goroutines to communicate, and a powerful construct to build sophisticated concurrency patterns. We will delve into the inner workings of channels and channel operations, including how they&amp;rsquo;re supported by the runtime scheduler and memory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency in Go&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=%5c_uQgGS_VIXM&amp;amp;list=PLsc-VaxfZl4do3Etp_xQ0aQBoC-x5BIgJ" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=\_uQgGS_VIXM&amp;list=PLsc-VaxfZl4do3Etp_xQ0aQBoC-x5BIgJ&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Playlist with a few short videos about different components of concurrency in Go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Just for func&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=H_4eRD8aegk&amp;amp;list=PL64wiCrrxh4Jisi7OcCJIUpguV_f5jGnZ" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=H_4eRD8aegk&amp;list=PL64wiCrrxh4Jisi7OcCJIUpguV_f5jGnZ&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;A very complete playlist of tutorials given by Francesc Campoy, a past Developer Advocate for the Go team at Google, that cover simple to advanced topics in Go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Golang crash course&lt;/strong&gt; - &lt;a class="link" href="https://www.youtube.com/watch?v=SqrbIlUwR0U" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=SqrbIlUwR0U&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;A 90 minutes video that covers most of Go features with cristal clear live coding examples, excelent for beginers to get a fast gist of Go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="podcasts"&gt;Podcasts
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go Time&lt;/strong&gt; - &lt;a class="link" href="https://open.spotify.com/show/2cKdcxETn7jDp7uJCwqmSE" target="_blank" rel="noopener"
 &gt;Spotify&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Diverse discussions from around the Go and its community&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>