<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stephanie Bergamo</title>
    <description>The latest articles on DEV Community by Stephanie Bergamo (@steph_baltus).</description>
    <link>https://dev.to/steph_baltus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F57014%2F1978e8a8-6ed4-46af-9955-8707a87677fa.jpeg</url>
      <title>DEV Community: Stephanie Bergamo</title>
      <link>https://dev.to/steph_baltus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/steph_baltus"/>
    <language>en</language>
    <item>
      <title>Building a B-tree in Go: The gap between theory and implementation</title>
      <dc:creator>Stephanie Bergamo</dc:creator>
      <pubDate>Wed, 14 Jan 2026 10:33:08 +0000</pubDate>
      <link>https://dev.to/steph_baltus/building-a-b-tree-in-go-the-gap-between-theory-and-implementation-55h0</link>
      <guid>https://dev.to/steph_baltus/building-a-b-tree-in-go-the-gap-between-theory-and-implementation-55h0</guid>
      <description>&lt;p&gt;I've been obsessed with database internals lately 🤓. It started with &lt;a href="https://www.databass.dev/" rel="noopener noreferrer"&gt;Alex Petrov's "Database Internals"&lt;/a&gt;: one of those books where every page makes you think "wait, THAT'S how it works?!" &lt;/p&gt;

&lt;p&gt;But there's a gap between understanding the theory and really &lt;em&gt;getting&lt;/em&gt; it. I could explain what a B-tree does to a 6yo, draw the diagrams and the likes. But could I actually build one? That itch needed scratching.&lt;/p&gt;

&lt;p&gt;So I dedicated a bit of my time off coding a B-tree from scratch in Go. About 15 hours total, spread across a few days between coffee, sport, rest and debugging sessions. I definitely didn't need one for production (PostgreSQL does this infinitely better), but I wanted to really understand what happens when I type &lt;code&gt;CREATE INDEX&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And honestly? It was one of the most fun coding challenges I've done in a while.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a B-tree, anyway?
&lt;/h2&gt;

&lt;p&gt;If you've used a database, you've used a B-tree: they're the data structure behind most database indexes. The key insight: &lt;strong&gt;unlike binary trees where each node has 2 children, B-tree nodes can have many children&lt;/strong&gt; (determined by the "order"). This makes them perfect for disk storage where reading a block is expensive.&lt;/p&gt;

&lt;p&gt;I won't explain B-trees from scratch here: Alex Petrov's book does that brilliantly. If you're not familiar with the basics, start with chapter 2 of Database Internals. This article assumes you know what a B-tree is and focuses on what I learned building one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Implementation: Easy, Interesting, and "Wait, What?"
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Easy Part: Search
&lt;/h3&gt;

&lt;p&gt;Binary search within nodes, recurse to children. With good tests, this was maybe 2 hours including edge cases. The satisfying kind of easy where you know exactly what to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Interesting Part: Split
&lt;/h3&gt;

&lt;p&gt;Understanding &lt;em&gt;why&lt;/em&gt; nodes split (keep tree balanced) vs &lt;em&gt;how&lt;/em&gt; to split them (median, two halves, promote) took some head-scratching. Drawing it out on paper helped.&lt;/p&gt;

&lt;p&gt;The surprise: root splits are special. You create a brand new root with just the promoted key. The tree grows taller, not wider.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Wait, What?" Part: Cascade
&lt;/h3&gt;

&lt;p&gt;Oh wow. This is where I learned the difference between "I understand B-trees" and "I can build a B-tree."&lt;/p&gt;

&lt;p&gt;When a split cascades up, you're not just inserting a key: you're managing a whole graph of parent-child relationships. Split an internal node? Better redistribute its children too. And update every child's parent pointer. Miss one? Silent corruption. The debugging session was ...&lt;em&gt;fun&lt;/em&gt;...&lt;/p&gt;

&lt;p&gt;The full implementation is on GitLab, but the key insight was: when you split an internal node, you're not just moving one or two keys: you're &lt;strong&gt;rewiring an entire graph&lt;/strong&gt;. Get one pointer wrong and your tree &lt;strong&gt;silently&lt;/strong&gt; corrupts. Tests caught this. A lot. And when they didn't and I had to discover that by myself, I wrote new tests to confirm and made sure I fixed the bug.  &lt;/p&gt;

&lt;h2&gt;
  
  
  What Building It Taught Me (That Reading Didn't)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  B-tree vs B+tree: Not Just Academic
&lt;/h3&gt;

&lt;p&gt;I knew the theory: B-trees store data everywhere, B+trees only in leaves. But implementing it made me &lt;em&gt;feel&lt;/em&gt; the difference.&lt;/p&gt;

&lt;p&gt;In my B-tree, search can stop at &lt;em&gt;any&lt;/em&gt; level when it finds the key. Elegant! But range scans would be a nightmare (you'd have to traverse the entire tree structure). B+trees sacrifice that early-stop elegance for better range queries (linked leaves). &lt;/p&gt;

&lt;p&gt;Now when I see PostgreSQL using B+trees for indexes, I get &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequential inserts are... weird 🤔
&lt;/h3&gt;

&lt;p&gt;Testing with &lt;code&gt;INSERT 1, 2, 3, 4...&lt;/code&gt; created a hilariously unbalanced tree: 7 levels deep with just 50 keys, all growing to the right.&lt;/p&gt;

&lt;p&gt;Random inserts? Beautiful 3-level balanced tree.&lt;/p&gt;

&lt;p&gt;This is why databases have bulk loading strategies and why &lt;code&gt;REINDEX&lt;/code&gt; exists. Seeing my skewed tree made me appreciate those optimizations viscerally.&lt;/p&gt;

&lt;h3&gt;
  
  
  TDD Isn't Optional (or at least not for me anymore)
&lt;/h3&gt;

&lt;p&gt;I've been building software with TDD for so long now that coding without tests feels... naked? Uncomfortable? Like I'm missing something fundamental.&lt;/p&gt;

&lt;p&gt;Could I have built this B-tree without tests? Sure. Would I have shipped a dozen subtle bugs? Absolutely.&lt;/p&gt;

&lt;p&gt;Writing tests first isn't about following best practices anymore: it's how I think through implementation. The test tells me what "done" looks like before I start coding.&lt;/p&gt;

&lt;p&gt;For this project, that discipline caught:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate key insertion (oops, forgot to check)&lt;/li&gt;
&lt;li&gt;Index out of bounds on edge cases (classic off-by-one)&lt;/li&gt;
&lt;li&gt;Children count mismatch after splits (n keys → n+1 children, remembered that in the test)&lt;/li&gt;
&lt;li&gt;Parent pointers pointing to the wrong parent (caught by checking parent relationships in tests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When cascade finally worked and all 20+ tests turned green simultaneously? That's the confidence TDD gives you. I wasn't hoping it worked: I knew it did. That's the payoff: shipping with certainty, not crossed fingers. (by now I guess you might have understand that I'm a strong believer in the increased productivity and overall quality TDD is bringing).&lt;/p&gt;

&lt;p&gt;And yes, 100% coverage throughout. Some call it obsessive. I call it professional 😇.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "It Just Clicked" Moment
&lt;/h3&gt;

&lt;p&gt;There was this moment, probably 10 hours in, debugging why searches failed after cascade. I was staring at my tree visualization (can't help but thanks Copilot for that helper function!), and suddenly I &lt;em&gt;saw&lt;/em&gt; it: the children weren't following their parent after the split.&lt;/p&gt;

&lt;p&gt;That moment when the entire structure suddenly made sense (not just conceptually, but in memory, pointers, the whole graph of relationships) that's what hands-on gets you. No amount of reading gives you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~15 hours across 4 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; ~200 lines of implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests:&lt;/strong&gt; 20+ unit tests, 100% coverage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bugs found:&lt;/strong&gt; Too many to count (but TDD caught them all)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffee consumed:&lt;/strong&gt; Significant&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This was way more fun than I expected. There's something deeply satisfying about implementing a data structure you've used for years without really understanding it.&lt;/p&gt;

&lt;p&gt;Next up: LSM-trees (Log-Structured Merge trees). They represent the opposite tradeoff: B-trees optimize for reads (in-place updates), LSM-trees optimize for writes (append-only). Understanding both will show me why Cassandra chose LSM while PostgreSQL chose B+trees.&lt;/p&gt;

&lt;p&gt;Reading about compaction strategies is one thing. Implementing them will be another level entirely 🤩.&lt;/p&gt;

&lt;p&gt;If you're curious about database internals, start with "Database Internals" by Alex Petrov. Then pick something and build it. The gap between reading and implementing is where the real ~magic~ learning happens.&lt;/p&gt;

&lt;p&gt;You can see the full implementation on my &lt;a href="https://gitlab.ipsyn.net/bergacorp/oakdb" rel="noopener noreferrer"&gt;self-hosted GitLab&lt;/a&gt; (yes, I run my own instance: infra nerd life). Pull requests welcome if you spot ways to improve it! 😊&lt;/p&gt;

</description>
      <category>database</category>
      <category>go</category>
      <category>btree</category>
      <category>internals</category>
    </item>
    <item>
      <title>Hugo.io - Multiline cells in a table</title>
      <dc:creator>Stephanie Bergamo</dc:creator>
      <pubDate>Fri, 10 Jun 2022 07:22:26 +0000</pubDate>
      <link>https://dev.to/steph_baltus/hugoio-multiline-cells-in-a-table-48o6</link>
      <guid>https://dev.to/steph_baltus/hugoio-multiline-cells-in-a-table-48o6</guid>
      <description>&lt;h2&gt;
  
  
  A problem
&lt;/h2&gt;

&lt;p&gt;Building table in &lt;code&gt;markdown&lt;/code&gt; is super easy and &lt;a href="https://sourceforge.net/p/hugo-generator/wiki/markdown_syntax/#md_ex_tables" rel="noopener noreferrer"&gt;well-documented&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
However, having a bit of formatting within it, like carriage return within a cell as below is not possible by default.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F914yisjzcxhivhqry9bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F914yisjzcxhivhqry9bn.png" alt="multiline-cell" width="398" height="105"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/gohugoio/hugo/issues/6581" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; uses &lt;a href="https://github.com/yuin/goldmark/" rel="noopener noreferrer"&gt;Goldmark&lt;/a&gt; (a &lt;a href="https://spec.commonmark.org/0.29/" rel="noopener noreferrer"&gt;CommonMark&lt;/a&gt; implementation in &lt;code&gt;Go&lt;/code&gt;) to process the markdown. Apparently it's extremely fast, with this implementation we choose to block any HTML processing d'HTML by default. &lt;/p&gt;
&lt;h2&gt;
  
  
  A solution
&lt;/h2&gt;

&lt;p&gt;Once you know this, solving this is pretty straightforward.&lt;/p&gt;

&lt;p&gt;You can customize plenty of Goldmark options in the configuration file (&lt;code&gt;config.toml/yaml/json&lt;/code&gt;). &lt;br&gt;
So what we want to do is autorising Goldmark to render &lt;code&gt;HTML&lt;/code&gt; content :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[markup.goldmark]&lt;/span&gt;
    &lt;span class="nn"&gt;[markup.goldmark.renderer]&lt;/span&gt;
    &lt;span class="py"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now, within the table, we just need to add &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; tag as below :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|Adresse | Parking de La Poste&amp;lt;br&amp;gt; Le Bourg &amp;lt;br&amp;gt;97122 Baie-Mahault |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it ! &lt;/p&gt;

&lt;h2&gt;
  
  
  Free tips
&lt;/h2&gt;

&lt;p&gt;If you don't want to bother with &lt;code&gt;|&lt;/code&gt; when building tables, someone very nice has built a &lt;a href="https://www.tablesgenerator.com/markdown_tables" rel="noopener noreferrer"&gt;table generator&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
We thank this person, from the bottom of our heart 💜. &lt;/p&gt;

</description>
      <category>hugo</category>
      <category>markdown</category>
      <category>formatting</category>
      <category>blogging</category>
    </item>
    <item>
      <title>Testing with Kotlin and JUnit5</title>
      <dc:creator>Stephanie Bergamo</dc:creator>
      <pubDate>Fri, 10 Jun 2022 07:10:53 +0000</pubDate>
      <link>https://dev.to/steph_baltus/testing-with-kotlin-and-junit5-3gjo</link>
      <guid>https://dev.to/steph_baltus/testing-with-kotlin-and-junit5-3gjo</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I've recently started coding backend and mobile stuff in &lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;kotlin&lt;/a&gt;, following a book, then starting a pet project, ...&lt;br&gt;&lt;br&gt;
In the meantime, I've also started to gain a lot of interest on &lt;strong&gt;&lt;a href="https://www.agilest.org/devops/test-driven-development/" rel="noopener noreferrer"&gt;Test Driven Development&lt;/a&gt;&lt;/strong&gt; and found myself wondering &lt;strong&gt;&lt;em&gt;"wow, how do I test in Kotlin ? which framework should I use?"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To learn further, I've deciced to start a serie of articles on this topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basic testing&lt;/strong&gt; in Kotlin with &lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests" rel="noopener noreferrer"&gt;JUnit5&lt;/a&gt; from the Java ecosystem (this article)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic testing&lt;/strong&gt; in Kotlin with &lt;a href="https://kotest.io/" rel="noopener noreferrer"&gt;Kotest&lt;/a&gt; built for kotlin specificatlly (upcoming)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dzone.com/articles/how-to-test-software-part-i-mocking-stubbing-and-c" rel="noopener noreferrer"&gt;Mocking, stubbing and contract testing&lt;/a&gt;&lt;/strong&gt; in Kotlin with JUnit5 and Kotest (upcoming)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;comparision of these two testing frameworks&lt;/strong&gt; (mostly in term of features, usability, readability), this one might be a bit opinionated. (upcoming)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm a java-ist so my go-to test framework is JUnit, this is the sole reason why I'm starting by this one.&lt;/p&gt;
&lt;h3&gt;
  
  
  Agenda
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Getting started&lt;/li&gt;
&lt;li&gt;Execution and lifecycle&lt;/li&gt;
&lt;li&gt;A very simple test&lt;/li&gt;
&lt;li&gt;Checking exceptions&lt;/li&gt;
&lt;li&gt;The power of parameterized tests&lt;/li&gt;
&lt;li&gt;Conditional tests&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you just want to jump in the code, &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/tree/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin" rel="noopener noreferrer"&gt;please yourself&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Basics on testing with JUnit5
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;For the Gradle users, here's the setup. &lt;/p&gt;

&lt;p&gt;To be able to use the JUnit5 features, we must first add the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="nf"&gt;testImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.junit.jupiter:junit-jupiter-api:5.8.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;testImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.junit.jupiter:junit-jupiter-engine:5.8.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we must specify we want to use JUnit to run the tests :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For Gradle + Groovy &lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;useJUnitPlatform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// For Gradle + Kotlin &lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;useJUnitPlatform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire necessary configuration can be found &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/3f3b9ebc3cd3b7627dc023fca7a2bf4ff733dc45/money/money/build.gradle.kts" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note: I'm using &lt;code&gt;gradle 7.3.3&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests execution and orchestration
&lt;/h3&gt;

&lt;p&gt;Junit will consider as a test any function annotated with &lt;code&gt;@Test&lt;/code&gt;, &lt;code&gt;@RepeatedTest&lt;/code&gt;, &lt;code&gt;@ParameterizedTest&lt;/code&gt;, &lt;code&gt;@TestFactory&lt;/code&gt;, or &lt;code&gt;@TestTemplate&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
We also have annotation to help us wrap test execution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@BeforeAll&lt;/code&gt; executed first, before the whole test suite. useful for instiantiating external dependencies for instance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@BeforeEach&lt;/code&gt; executed after &lt;code&gt;BeforeAll&lt;/code&gt; and before any test, useful when we need to ensure the state is clean before launching for example.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@AfterEach&lt;/code&gt; not surprisingly, executed after any test.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@AfterAll&lt;/code&gt; executed at the end of the test suite, for housekeeping purpose, pushing stats or whatever.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  A very simple test
&lt;/h3&gt;

&lt;p&gt;Now we're all set, time to code and test!&lt;br&gt;&lt;br&gt;
Let's say we have a useless class like this one :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously we want to test this class has the expected behavior, with a very basic test:&lt;/p&gt;

&lt;p&gt;We'll need to first import the JUnit5 helpers :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Assertions&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we can create our test as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`should&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt; &lt;span class="nf"&gt;correctly`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;five&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&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="nc"&gt;Assertions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now execute this, using our favorite IDE or the simple gradle command :&lt;br&gt;&lt;br&gt;
&lt;code&gt;./gradlew test&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  A quick word on naming
&lt;/h4&gt;

&lt;p&gt;Well, no strong rule here, but a test method should :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test one thing, and one thing only&lt;/li&gt;
&lt;li&gt;have an explicit name so the expectation is clear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may have notice that I'm using backtick here.&lt;br&gt;&lt;br&gt;
This is a Kotlin capability, identifier for variable and method can use them, and although there's nothing mandatory here, I find it clearer.&lt;/p&gt;
&lt;h3&gt;
  
  
  What about checking exceptions ?
&lt;/h3&gt;

&lt;p&gt;Ok, now we don't want to multiply our dollar by zero, so we change our code a bit, to raise an exception if this occurs.  &lt;/p&gt;

&lt;p&gt;Let's write the test first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`should&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;multiplying&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;one&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dollar&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="n"&gt;assertThrows&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NoMoneyException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yep, it's just that easy, okay, this does not even compile, since the NoMoneyException class does not exists. Let's create it !&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NoMoneyException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then update our &lt;code&gt;times&lt;/code&gt; operator :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Dollar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;NoMoneyException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Can't multiply by zero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run since and see the green test :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's execute the same test with different inputs !
&lt;/h3&gt;

&lt;p&gt;I think you'll agree, if we add more test cases, we'll be loosing readability and we'll duplicate the same test again and again.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Grouped assertions
&lt;/h4&gt;

&lt;p&gt;Well there's a few great &lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions-kotlin" rel="noopener noreferrer"&gt;Kotlin assertions&lt;/a&gt; that comes with Junit, let's play with &lt;code&gt;assertAll&lt;/code&gt; and a collection with a multiplier and the expected result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`should&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="nf"&gt;stream`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;five&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;arrayListOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;arrayListOf&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;arrayListOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;arrayListOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"should provide the expected result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Remove this line and use the collection directly&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&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="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&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="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Parameterized tests
&lt;/h4&gt;

&lt;p&gt;Well, I've used a lot of  &lt;strong&gt;&lt;a href="https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go" rel="noopener noreferrer"&gt;Table Driven Tests&lt;/a&gt;&lt;/strong&gt; in &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;golang&lt;/a&gt;, this is super helpful to write compact and repeatable tests.&lt;br&gt;&lt;br&gt;
With JUnit, we can achieve the same with &lt;strong&gt;Parameterized tests&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests" rel="noopener noreferrer"&gt;Parameterized tests&lt;/a&gt; makes tests more readable and avoid duplicates, but don't take my word for it, let's code !  &lt;/p&gt;

&lt;p&gt;First, we'll need to add a new dependency :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;   &lt;span class="nf"&gt;testImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.junit.jupiter:junit-jupiter-params:5.8.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's replace our previous example and use a &lt;code&gt;CsvSource&lt;/code&gt; with the &lt;code&gt;multiplier&lt;/code&gt;  and the &lt;code&gt;expected&lt;/code&gt; value :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ParameterizedTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"multiply {0} by 5 should return {1}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@CsvSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"2, 10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"3, 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"10, 50"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`should&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt; &lt;span class="nf"&gt;correctly`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;five&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Assertions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dollar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So basically, we :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add the &lt;code&gt;@ParameterizedTest&lt;/code&gt; annotation and optionnally define a name,&lt;/li&gt;
&lt;li&gt;add the annotation for the the type of argument source we want to provide (&lt;code&gt;@CsvSource&lt;/code&gt; here) with the related test cases&lt;/li&gt;
&lt;li&gt;enjoy ! &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may have noticed &lt;strong&gt;the name of the test&lt;/strong&gt;? Well, this little trick makes our test super explicit and easier to debug, see for yourself :&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtcngepcu33k6yk0bcis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtcngepcu33k6yk0bcis.png" alt="test label" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, you can directly see which testcase fails, see for yourself: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqf23p8utoych7f160to.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqf23p8utoych7f160to.png" alt="Parameterized tests output" width="654" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't know about you, but I personnally find it &lt;strong&gt;more readable than the group assertions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For more info about &lt;strong&gt;customizing the display name&lt;/strong&gt;, see &lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-display-names" rel="noopener noreferrer"&gt;this part&lt;/a&gt; of the JUnit documentation.&lt;/p&gt;

&lt;p&gt;Another nice thing, is that we can use several types of arguments as inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ValueSource&lt;/code&gt; allows to pass a list of arguments of primitive types, String and Class: useful for a testing a single argument. &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L30" rel="noopener noreferrer"&gt;See an example here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CsvSource&lt;/code&gt; as show-cased here, we can pass an unlimited list of arguments as a string representing a CSV input&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CsvFileSource&lt;/code&gt; same as the previous file, except we use a CSV file. &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L47" rel="noopener noreferrer"&gt;See an example here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EnumSource&lt;/code&gt; lets you run the same test for each constant (or a selected set) of a given ENUM. &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L54" rel="noopener noreferrer"&gt;See an example here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MethodSource&lt;/code&gt; this one is super powerful, we can basically have anything we want as input source (say a JSON or a Parquet file), process it with the &lt;code&gt;MethodSource&lt;/code&gt; and use it to execute our tests. &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L75" rel="noopener noreferrer"&gt;See an example here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ArgumentSource&lt;/code&gt; this one goes even further than &lt;code&gt;MethodSource&lt;/code&gt;. With a new class, implementing the &lt;code&gt;ArgumentProvider&lt;/code&gt; interface, you can generate input data. &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L84" rel="noopener noreferrer"&gt;See an example here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also have a bit of syntaxic sugar with &lt;code&gt;@NullSource&lt;/code&gt;, &lt;code&gt;@EmptySource&lt;/code&gt; and &lt;code&gt;@NullAndEmptySource&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Be careful when using BeforeEach and "parameterized" or "repeated" tests
&lt;/h4&gt;

&lt;p&gt;Each item of a parameterized or repeated test suite is considered as a single test.&lt;br&gt;&lt;br&gt;
Therefore, whatever is defined in &lt;code&gt;BeforeEach&lt;/code&gt; will be executed before each occurence of the test source.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Say we define a &lt;code&gt;BeforeEach&lt;/code&gt; as follows :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@BeforeEach&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initEach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello I'm a test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing &lt;code&gt;should multiply correctly&lt;/code&gt; which have 3 rows in its &lt;code&gt;CsvSource&lt;/code&gt;, we'll have the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello I'm a test
hello I'm a test
hello I'm a test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Do we really want to execute this test ?
&lt;/h3&gt;

&lt;p&gt;JUnit comes with multiple way to decide whether a test sould be executed or not, depending of the context.&lt;br&gt;&lt;br&gt;
First we can totally disable a test or a class, by adding the &lt;code&gt;@Disabled&lt;/code&gt; annotation.  &lt;/p&gt;

&lt;p&gt;In addition, we can programmatically define condition execution depending on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;Operating System&lt;/strong&gt;, using &lt;code&gt;@EnabledOnOs&lt;/code&gt;, &lt;code&gt;@DisabledOnOs&lt;/code&gt; and the &lt;code&gt;OS&lt;/code&gt; Enum:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EnabledOnOs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MAC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@EnabledOnOs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MAC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LINUX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;JRE&lt;/strong&gt;, with &lt;code&gt;@EnabledOnJre&lt;/code&gt;, &lt;code&gt;@EnabledForJreRange&lt;/code&gt;, &lt;code&gt;@DisabledOnJre&lt;/code&gt;, &lt;code&gt;@DisabledForJreRange&lt;/code&gt; and the  &lt;code&gt;JRE&lt;/code&gt; Enum:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EnabledOnJre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JAVA_8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@EnabledForJreRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JRE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JAVA_11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JRE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JAVA_16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;system properties&lt;/strong&gt;, with &lt;code&gt;@EnabledIfSystemProperty&lt;/code&gt; and &lt;code&gt;@DisabledIfSystemProperty&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EnabledIfSystemProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"os.arch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".*64.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;one or multiple &lt;strong&gt;environment variable(s)&lt;/strong&gt;, &lt;code&gt;@EnabledIfEnvironmentVariable&lt;/code&gt;, &lt;code&gt;@EnabledIfEnvironmentVariables&lt;/code&gt;, &lt;code&gt;@DisabledIfEnvironmentVariable&lt;/code&gt; and &lt;code&gt;@DisabledIfEnvironmentVariable&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EnabledIfEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"EXEC_ENV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".*ci.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@EnabledIfEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DEBUG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the two singular annotations are repeatable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;custom condition&lt;/strong&gt;*, using &lt;code&gt;@EnabledIf&lt;/code&gt; and &lt;code&gt;@DisabledIf&lt;/code&gt; with a method name or its FQN (if the method is not in the same class) as string:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EnabledIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execAlways"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`something&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nf"&gt;test`&lt;/span&gt;&lt;span class="p"&gt;(){}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;execAlways&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That's it for this first part, covering the basics on testing Kotlin code with JUnit, we can already check a lot of things with :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grouped assertions&lt;/li&gt;
&lt;li&gt;exception check&lt;/li&gt;
&lt;li&gt;parameterized tests&lt;/li&gt;
&lt;li&gt;conditional tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you'll find this useful, you can find the full test implementation &lt;a href="https://gitlab.ipsyn.net/bikette/learn-tdd/-/tree/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>bestpractices</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Ingesting an S3 file into an RDS PostgreSQL table</title>
      <dc:creator>Stephanie Bergamo</dc:creator>
      <pubDate>Fri, 10 Jun 2022 07:02:30 +0000</pubDate>
      <link>https://dev.to/steph_baltus/ingesting-an-s3-file-into-an-rds-postgresql-table-4n3b</link>
      <guid>https://dev.to/steph_baltus/ingesting-an-s3-file-into-an-rds-postgresql-table-4n3b</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I'm using &lt;a href="https://aws.amazon.com/redshift/" rel="noopener noreferrer"&gt;Redshift&lt;/a&gt; for a while now, and one feature I find particularly useful, is the ability to &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/r_COPY.html" rel="noopener noreferrer"&gt;load a table from the content of an S3 file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'s3://bucket-name/path/file.ext'&lt;/span&gt;
  &lt;span class="n"&gt;iam_role&lt;/span&gt; &lt;span class="s1"&gt;'arn:aws:iam::&amp;lt;aws-account-id&amp;gt;:role/&amp;lt;role-name&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="s1"&gt;'region-code'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lately, I needed to do the very same thing with a &lt;strong&gt;PostgreSQL database, hosted on RDS&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
The good news is : YES WE CAN !&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;AWS pre-installs (among other things) 2 extensions on our RDS :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aws_commons&lt;/li&gt;
&lt;li&gt;aws_s3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, since we need to interact with S3, we simply need to run the following command, assuming&lt;br&gt;
our &lt;code&gt;user&lt;/code&gt; is a superuser or has database owner privileges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;aws_s3&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command installs both &lt;code&gt;aws_commons&lt;/code&gt; and &lt;code&gt;aws_s3&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Okay, that was the easy part.&lt;/p&gt;

&lt;p&gt;Well, now we have to work on the necessary IAM role for our RDS instance...and that's where we'll find the first pitfall...&lt;br&gt;&lt;br&gt;
Under the hood, installing the &lt;code&gt;aws_s3&lt;/code&gt; extension also adds an IAM role, managed by AWS (and therefore, read only for us)&lt;br&gt;
 to interact with plenty of AWS services.&lt;br&gt;
As we can see below, the role is linked to the &lt;code&gt;S3Import&lt;/code&gt; function :&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqs1rvkufesu695npupy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqs1rvkufesu695npupy0.png" alt="AWS Role" width="800" height="142"&gt;&lt;/a&gt;&lt;br&gt;
The problem is, &lt;strong&gt;we can only link a single role to a function&lt;/strong&gt; and this one does not have any access to our S3 bucket, therefore this cannot work.&lt;/p&gt;

&lt;p&gt;In the end, even if the policy is super short in &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL.Procedural.Importing.html#USER_PostgreSQL.S3Import.ARNRole" rel="noopener noreferrer"&gt;the doc&lt;/a&gt;, here is what we actually need :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&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="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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="s2"&gt;"arn:aws:s3:::bucket-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::bucket-name/*"&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="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="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"rds:CrossRegionCommunication"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:AllocateAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:AssociateAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:AuthorizeSecurityGroupIngress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateNetworkInterface"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateSecurityGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DeleteNetworkInterface"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DeleteSecurityGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeAddresses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeAvailabilityZones"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeCoipPools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeInternetGateways"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeLocalGatewayRouteTables"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeLocalGatewayRouteTableVpcAssociations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeLocalGateways"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeSecurityGroups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeSubnets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeVpcAttribute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeVpcs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DisassociateAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:ModifyNetworkInterfaceAttribute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:ModifyVpcEndpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:ReleaseAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:RevokeSecurityGroupIngress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:CreateVpcEndpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeVpcEndpoints"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DeleteVpcEndpoints"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:AssignPrivateIpAddresses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:UnassignPrivateIpAddresses"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"sns:Publish"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogGroup"&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="nl"&gt;"Resource"&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="s2"&gt;"arn:aws:logs:*:*:log-group:/aws/rds/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:log-group:/aws/docdb/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:log-group:/aws/neptune/*"&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="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="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:DescribeLogStreams"&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="nl"&gt;"Resource"&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="s2"&gt;"arn:aws:logs:*:*:log-group:/aws/rds/*:log-stream:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:log-group:/aws/docdb/*:log-stream:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:log-group:/aws/neptune/*:log-stream:*"&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="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="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:CreateStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:PutRecord"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:PutRecords"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:DescribeStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:SplitShard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:MergeShards"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:DeleteStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"kinesis:UpdateShardCount"&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="nl"&gt;"Resource"&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="s2"&gt;"arn:aws:kinesis:*:*:stream/aws-rds-das-*"&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="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="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&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="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudwatch:PutMetricData"&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="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&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="nl"&gt;"StringEquals"&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="nl"&gt;"cloudwatch:namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS/RDS"&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="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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once created, we we'll need to :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unlike the managed role from the &lt;code&gt;s3Import&lt;/code&gt; function;&lt;/li&gt;
&lt;li&gt;link the new role to the &lt;code&gt;s3Import&lt;/code&gt; function;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Careful now, &lt;strong&gt;swapping and applying the role may takes a few minutes&lt;/strong&gt;, don't believe what the AWS console tells you.&lt;/p&gt;

&lt;p&gt;That's it now, we're all set !&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;To load the content of a CSV file from S3,  we'll just need to :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;aws_s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_import_from_s3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="s1"&gt;'target-table'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'columns list, separated by a comma, OPTIONAL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'DELIMITER &lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt; CSV HEADER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;aws_commons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_s3_uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="s1"&gt;'bucket-name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s1"&gt;'file-prefix'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s1"&gt;'region-code'&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When providing an empty string as column list, the command will try to map the file columns to the table columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pitfalls
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Constraints
&lt;/h4&gt;

&lt;p&gt;Here are some constraints to keep in mind, not necessarily documented :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the bucket name &lt;strong&gt;must not contain a period &lt;code&gt;.&lt;/code&gt;&lt;/strong&gt; (I got this error message : &lt;code&gt;S3 bucket names with a period (.) are not supported&lt;/code&gt;). Yeah I know we should have &lt;code&gt;path-style&lt;/code&gt; S3 bucket, but hey, one has to deal with legacy right ?!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;;&lt;/code&gt; &lt;a href="https://stackoverflow.com/questions/62302191/importing-csv-file-with-semicolon-delimiters-from-s3-to-rds-postgres-database" rel="noopener noreferrer"&gt;cannot be used as a delimiter&lt;/a&gt;, but just so you know, this is not a PostgreSQL limitation as it accepts any character as a delimiter, as long as it's single one-byte character; &lt;/li&gt;
&lt;li&gt;just like the &lt;a href="https://www.postgresql.org/docs/9.2/sql-copy.html" rel="noopener noreferrer"&gt;&lt;code&gt;COPY&lt;/code&gt;command from PostgreSQL&lt;/a&gt; &lt;strong&gt;only text, CSV, or binary format are supported&lt;/strong&gt;; &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Error handling
&lt;/h4&gt;

&lt;p&gt;When an error occurs, the PG client doesn't get a real, explicit error message. I mean, either for an unsupported bucket&lt;br&gt;
name or a missing access right, we get the very same error message :&lt;br&gt;
&lt;code&gt;Unable to generate pre-signed url, look at engine log for details&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We must go dig in the PG logs, from the RDS console to get an explicit error message and debug efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Once we get through the installation and understand the limits and pitfalls, this feature is super useful and easy to use.&lt;/p&gt;

&lt;p&gt;Unfortunately (well, this is not really surprising) &lt;strong&gt;we can't install the two extensions cannot on simple EC2&lt;/strong&gt;.&lt;br&gt;
Life would be so nice if we could use AWS feature as we wish, where we wish !&lt;/p&gt;

&lt;p&gt;So we get to choose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;either we go for RDS, but we stick to the AWS handpicked extensions (exit &lt;a href="https://www.timescale.com/" rel="noopener noreferrer"&gt;timescale&lt;/a&gt;, &lt;a href="https://github.com/citusdata/citus" rel="noopener noreferrer"&gt;citus&lt;/a&gt; or their &lt;a href="https://github.com/citusdata/cstore_fdw" rel="noopener noreferrer"&gt;columnar storage&lt;/a&gt;, ... ),&lt;/li&gt;
&lt;li&gt;or we use other solutions, and we deprive ourselves of all the benefits of hosted / managed services. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://g33k.life/en/tech/load-rds-via-s3/" rel="noopener noreferrer"&gt;https://g33k.life/en/tech/load-rds-via-s3/&lt;/a&gt; on December 16, 2020.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>data</category>
      <category>engineering</category>
      <category>tips</category>
    </item>
  </channel>
</rss>
