home search about rss feed twitter ko-fi

App 2: insert Database Function (Scala 3 Video)

As a quick reminder, in the following lessons where we’ll create database-related code, all of these functions will be placed inside a Database class that looks like this:

class Database(dbFilename: String):
    // def insert = ???
    // def selectAll = ???
    // def delete = ???
end Database

insert

The first Database function we’ll need is an insert function. Because this is just a flat-file database, this function will append a single new record to the database file. Therefore, I know that it takes a string as input, and I start to sketch the insert function like this:

def insert(record: String)

Next — and mostly because I’ve been through this process before — I know that I want this function to use another writeToFile function to do the actual file-writing. I know this because the delete and update functions I’ll write in the following lessons will require me to read in the entire file and then re-write it completely. So writeToFile will need to be able to both (a) append to a file and (b) completely overwrite the file (and I don’t want to have a boolean append parameter in the insert function).

Therefore, let’s forget about the insert function for a moment and first work on the writeToFile function.

writeToFile

writeToFile will be another function inside the Database class, and because nobody outside of Database will need to access it, I start by making it private:

private def writeToFile

Next, because I know that delete and update will require me to completely re-write the file, I define it to accept a list of strings, which in the most generic sense in Scala is a Seq[String]:

private def writeToFile(lines: Seq[String])
                        ------------------

I also know that I want to set a boolean append flag when calling this function. This lets me declare that I want to overwrite the file, or append to it:

private def writeToFile(lines: Seq[String], append: Boolean):
                                            ---------------

Now the final thing to think about is its return type. As I mentioned in our rules, any function that communicates with the outside world must return a Try value, so that gives me this:

private def writeToFile(lines: Seq[String], append: Boolean): Try[
                                                              ----

And now the only question is, “What should the success value be inside Try?”

Since I don’t need any data back when writing a file — I just need a Success or Failure to know if it worked or now — this type can be Unit. (As mentioned before, this means that I’m not interested in the return type.) So I complete the function signature like this:

private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
                                                              ---------

Implementing writeToFile

Next, I’m going to do something controversial and violate two of my rules: I’m going to use a var field and a null value. However, I’m only doing this because:

  • I suspect that many people reading this book are familiar with languages like Java and Kotlin, and therefore writing to files with Java classes and methods.
  • The more-correct solution requires a little more Scala knowledge that I don’t want to get into yet. (I share this correct solution in the “Using” lesson in the Appendix.)

Therefore, because this is Java/OOP code that uses the java.io.* classes, I write the body of the function as shown here, with comments describing the important parts:

import java.io.*
import scala.util.{Try, Success, Failure}

/**
 * Notice that `dbFilename` comes in through a side door.
 */
private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
    // i create this variable before the 'try' block because i need to
    // access it in the 'finally' block:
    var bw: BufferedWriter = null
    
    try
        // this is basically standard Java code that you use to
        // create a BufferedWriter:
        bw = BufferedWriter(FileWriter(File(dbFilename), append))
        // here i loop over every String in 'lines', and write each
        // String (one at a time) to the file. each String is
        // followed by a newline character:
        for line <- lines do bw.write(s"$line\n")
        // if the previous code does not throw an exception, flow
        // comes here, and we will return this Success value:
        Success(true)
    catch
        // if the previous code DOES throw an exception, control
        // comes here and we will return a Failure:
        case e: Throwable => Failure(e)
    finally
        // the 'finally' block always executes, so this is where
        // we release/close resources we opened. note that you need
        // to check if 'bw' is null first, because the previous code
        // may explode before 'bw' is bound to the BufferedWriter:
        if bw != null then bw.close

I added a lot of comments in there to explain how that code works. So you can read that function more easily, here it is again without those comments:

private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
    var bw: BufferedWriter = null    
    try
        bw = BufferedWriter(FileWriter(File(dbFilename), append))
        for line <- lines do bw.write(s"$line\n")
        Success(true)
    catch
        case e: Throwable => Failure(e)
    finally
        if bw != null then bw.close

Again, this is a very Java/OOP solution to this problem, and if you’d like to see the Scala solution, see the “Using” lesson in the Appendix.

Getting back to the insert function

Now that I have the writeToFile function, all I have to do is finish the insert function. I already sketched this portion of it:

def insert(record: String)

and now that I know I want to call writeToFile, I know that insert’s body should look like this:

def insert(record: String) ...
    writeToFile(List(record), true)

In that code, I wrap the String that’s received in a List, because writeToFile expects to receive a Seq[String]. And because I know that writeToFile returns a Try[Unit], and I also want to return a Try from insert, I use that as this function’s return type as well:

def insert(record: String): Try[Unit] =
    writeToFile(List(record), true)

That completes the insert function.

Update: All of my new videos are now on
LearnScala.dev