Published 2014-08-09.
Last modified 2015-09-17.
Time to read: 11 minutes.
Common usage patterns of partially applied functions are introduced, including the Loan Pattern, various ways of using type hints, nested partially applied functions and generating partially applied functions from parametric methods. An extended review of partially applied functions will stretch your brain. The exercise walks you through using partially applied functions to make writing database code easy.
The sample code for this lecture can be found in
courseNotes/
.
We need a few methods and objects for this lecture, which are defined in the PartiallyAppliedStuff
object
in the sample code for this course.
The first2lines
helper method returns the first two lines of the given InputStream
as a String
.
def first2lines(msg: String, inputStream: InputStream): String = s"$msg:\n " + read(inputStream).split("\n").take(2).mkString("\n ")
first2lines
calls read
, which uses
Iterator.
.
We learned about Interator.
in the
Immutable Collections lecture of this course.
Given an InputStream
, read
returns the contents of the stream as a String
.
def read(inputStream: InputStream): String = Iterator.continually(inputStream.read) .takeWhile(_ != -1) .map(_.toChar) .mkString
We’ll also need a File
instance, which I’ve called file
, and a supply of
FileInputStream
instances, which are provided by the fileInputStream
method.
val file = new File("/etc/passwd") def fileInputStream = new FileInputStream(file)

The Loan Pattern
The Loan Pattern loans a resource to a higher-order function. Implementations of this pattern do the following:
- Create or access a resource
- Pass the resource to the the higher-order function
- Dispose of the resource reliably, efficiently and safely
The advantages of the loan pattern are:
- The loan pattern implementation is completely separate from the higher-order function
- Various higher-order functions can be provided
- Higher-order functions are not concerned about the creation or destruction of the resource because the loan pattern implementation handles that
Before we go any further, you might want to refresh your memory of how call-by-name / lazy evaluated parameters work by reviewing the More Fun With Functions lecture of the Introduction to Scala course.
The Loan Pattern is actually an extension of the withT
method that we encountered in the
Parametric Types
lecture of this course so that it safely handles the opening and closing of a resource.
withT
is parametric in T
and U
.
T
is the type of the object that withT
will operate on, and the operation transforms the instance
of T
into an instance of U
.
The body of withT
merely consists of invoking the operation
and passing it the value of the t
parameter.
def withT[T, U](t: T)(operation: T => U): U = operation(t)
... and here is how we could use withT
.
We create a new java.util.Date
, and pass it to the t
parameter of the withT
method.
The operation
parameter is bound to the block of code that simply consists of the println
method,
and that means that the body of withT
invokes the println
method and passes it the Date
instance.
withT(new java.util.Date) { println }
Here is the definition of an implementation of the Loan Pattern, called withCloseable
:
import java.io._ import scala.util.{Failure, Success, Try}
def withCloseable[C <: Closeable, T](factory: => C)(operation: C => T): Try[T] = { val closeable = factory try { val result: T = operation(closeable) closeable.close() Success(result) } catch { case throwable: Throwable => try { closeable.close() } catch { case _: Throwable => } Failure(throwable) } }
withCloseable
is parametric in types C
and T
, where C
is
constrained to be a subtype of java.io.Closeable
.
withCloseable
accepts two Function1
parameters and returns a Try
.
The first of withCloseable
’s Function1
parameters is a lazily evaluated parameter called
factory
of type C
.
The result of evaluating factory
is cached.
That is important because as we learned in the More Fun With Functions
lecture of the Introduction to Scala course,
lazily evaluated parameters are evaluated each time they are referenced.
The second Function1
is an operation
to be performed on the Closeable
instance.
The Closeable
is automatically closed after operation
completes or if it throws an exception.
The withCloseable
method returns a Try
, which wraps the result from running operation
.
Let’s invoke withCloseable
such that both parameters are bound with two arguments.
withCloseable(fileInputStream) { fis => first2lines("withCloseable example 1", fis) }
The above causes the contents of /etc/passwd
to be displayed.
The first parameter list constructs a FileInputStream
,
and the second parameter list receives the FileInputStream
then passes it to the first2lines
method we just defined.
As we learned in the Functions are First Class lecture of the
Introduction to Scala course,
you could also write this using shorthand like this:
withCloseable(fileInputStream) {
first2lines("withCloseable example 2", _)
}
Using the Loan Pattern
Let’s say we want to be able to write a family of File
processing routines that use
withCloseable
to take care of the housekeeping (that is, the opening and closing of the File
).
The file contents are prefixed by a message that provides a clue as to what the contents are.
Here is an example of one of the File
processing routines, which uses the first2lines
method we just defined:
scala> import java.io._ import java.io._
scala> import PartiallyAppliedStuff._ import PartiallyAppliedMethods._
scala> val tryContents1: Try[String] = withCloseable(fileInputStream) { first2lines("tryContents1", _) } tryContents1: Success(tryContents6: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin)
We can provide a different lambda function, which returns the first two lines of the file in upper case:
scala> val tryContents2: Try[String] = withCloseable(fileInputStream) { first2lines("tryContents2", _).toUpperCase } res13: scala.util.Try[String] = Success(ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH DAEMON:X:1:1:DAEMON:/USR/SBIN:/USR/SBIN/NOLOGIN)
Our task is to make a partially applied function of the above, which would allow us to pass in any Function
for arbitrary processing of the FileInputStream
bound to the first parameter list.
Let’s try the obvious thing and create openFileInputStream3
as a partially applied function which
accepts a FileInputstream
and returns a String
.
I called this partially applied function openFileInputStream3
and I invoked it by passing in a lambda
function that invokes the first2lines
method as before and then reverses the resulting String
.
scala> val openFileInputStream3 = withCloseable(fileInputStream) (_: FileInputStream => String) openFileInputStream3: (java.io.FileInputStream => String) => scala.util.Try[String] = <function1>
scala> val tryContents3: Try[String] = openFileInputStream3 { first2lines("tryContents3", _).reverse } tryContents3: scala.util.Try[String] = Success(nigolon/nibs/rsu/:nibs/rsu/:nomead:1:1:x:nomead hsab/nib/:toor/:toor:0:0:x:toor :3stnetnoCyrt)
Eta Expansion Type Hints
The underscore at the end of the expression is optional if a type hint to the left of the equals sign indicates currying. Eta expansion happens in this case even if there is no trailing underscore.
scala> val openFileInputStream4: (FileInputStream => String) => Try[String] = withCloseable(fileInputStream) openFileInputStream4: (java.io.FileInputStream => String) => scala.util.Try[String] = <function1>
scala> val tryContents4: Try[String] = openFileInputStream4 { first2lines("tryContents4", _).toUpperCase.reverse } tryContents4: scala.util.Try[String] = Success(NIGOLON/NIBS/RSU/:NIBS/RSU/:NOMEAD:1:1:X:NOMEAD HSAB/NIB/:TOOR/:TOOR:0:0:X:TOOR :4STNETNOCYRT)
As we saw in the previous lecture, if we partially apply the withCloseable
function by only
providing it the first parameter list, like this:
scala> val noGood = withCloseable(fileInputStream) _ noGood: (java.io.FileInputStream => Nothing) => scala.util.Try[Nothing] = <function1>
... then unfortunately the returned type is Nothing
.
Because withCloseable
is parametric, you must let the compiler know the type of the unbound method parameters.
One way to do this is to provide a type hint on the left of the equals sign, as we saw with openFileInputStream4
.
The other way is to specify the parametric types for the method, like this.
scala> val openFileInputStream5 = withCloseable[FileInputStream, String](fileInputStream) _ opened: (java.io.FileInputStream => String) => scala.util.Try[String] = <function1>
The Nothing
type is now replaced with String
.
Note that the type of the new openFileInputStream5
Function
is
(java.io.FileInputStream => String) => scala.util.Try[String]
.
Generating a Partially Applied Function From a Parametric Method
While it is good practice to assign Function
s to N
val
s,
sometimes we can simplify the creation of a partially applied functions by using def
instead of
val
, because def
s can be parametric, while val
s cannot.
Eta Expansion Example
scala> def openFileInputStream6[T] = withCloseable[FileInputStream, T](fileInputStream) _ openFileInputStream5: [T]=> (java.io.FileInputStream => T) => scala.util.Try[T]
The openFileInputStream6
method is parametric in T
.
The body consists of an invocation of withCloseable
, passing in two type parameters
(FileInputStream
as before and T
, which will vary between invocations of this partially applied function generator).
The other parameters are fileInputStream
, and it ends in an underbar,
which means that we want to leave the remaining parameters unbound,
or in other words we want to create a partially applied function.
Notice that the return type is Try[T]
.
Now we can invoke openFileInputStream6
.
I’ve provided a type hint on the left of the equals sign,
which indicates that the return type must be Try[String]
,
and the Scala compiler understands that means T
must be a String
for this partially applied function instance.
The lambda passed to openFileInputStream6
consists of an invocation of first2lines
,
and shorthand notation is again used to indicate that the FileInputStream
parameter passed to the lambda should be bound to the underscore.
The results of first2lines
are then further processed by the invocation of replace
.
scala> val tryContents6: Try[String] = openFileInputStream6 { first2lines("tryContents6", _).replace(":", "#") } tryContents6: scala.util.Try[String] = Success(tryContents6# root#x#0#0#root#/root#/bin/bash daemon#x#1#1#daemon#/usr/sbin#/usr/sbin/nologin)
Placeholder Syntax Example
Here is another example, where a new partially applied function is returned from a method.
withBufferedInputStream1
is parametric in T
and it accepts an input of type java.io.File
.
The method body consists of an invocation of withCloseable
, which as you know accepts two parameter lists,
each of which consist of only one parameter.
The first parameter is an instance of a BufferedInputStream
,
and the second parameter is a lambda function that is declared to receive the BufferedInputStream
and
return an instance of a T
.
The second parameter has no implementation, it is just a type declaration using placeholder syntax,
which means that it is unbound and therefore this creates a partially applied function.
In other words, withBufferedInputStream1
is another partially applied function generator.
def withBufferedInputStream1[T](input: File) =
withCloseable(new BufferedInputStream(new FileInputStream(input)))
(_: BufferedInputStream => T)
This method returns an instance of a partially applied function with this signature:
(java.io.BufferedInputStream => T) => scala.util.Try[T]
.
It is good to define the return type of a method, so here is a better way to write the same method. Again note that the trailing underscore was not necessary because of the highlighted type hint on the left hand side of the equals sign. Note also that this a curried type.
def withBufferedInputStream[T](input: File): (BufferedInputStream => T) => Try[T] =
withCloseable(new BufferedInputStream(new FileInputStream(input)))
We can invoke the partially applied Function1
returned by the withBufferedInputStream
method like the following.
Notice that the results from the withBufferedInputStream
invocation are iterated through by foreach
, and each result is printed.
withBufferedInputStream(file) { first2lines("withBufferedInputStream", _) } foreach println
Output is:
withBufferedInputStream: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

Nesting Partially Applied Functions
Let’s define withBuffferedOutputStream
, which is the dual of withBufferedInputStream
.
withBufferedOutputStream
is a method that accepts a File
instance and returns a partially
applied FunctionN
that is parametric in T
,
accepts a BufferedOutputStream
instance and returns an instance of Try[T]
.
def withBufferedOutputStream[T](output: File): (BufferedOutputStream => T) => Try[T] = withCloseable(new BufferedOutputStream(new FileOutputStream(output)))
The following code copies a BufferedInputStream
instance to a BufferedOutputStream
instance.
Notice how the two partially applied functions generated from withBufferedInputStream
and
withBufferedOutputStream
provide the result of executing their bound parameters to their respective
lambda functions as inputStream
and outputStream
.
We again use the read
method shown earlier.
withBufferedInputStream(file) { inputStream => withBufferedOutputStream(new File("/tmp/blah")) { outputStream => read(inputStream).foreach(outputStream.write(_)) } }
You will frequently see this pattern in library code.
Review Example 1
Partially applied FunctionN
s can make any bound parameter available to a block of code.
Here is an example of a method whose second parameter list accepts a function.
scala> def stringMunger(a: String)(f: String => String) = f(a) repeat: (a: String)(f: String => String)String
stringMunger
’s last parameter list is a function that accepts a String
and returns a String
.
It is easy to make a partial function from a curried function – just leave off one or more of the trailing parameter lists and add an underscore.
scala> val abc = stringMunger("abc") _
abc: (String => String) => String

A partially applied Function1
called abc
was lifted from the stringMunger
method by the _
operator.
No argument was supplied for the second parameter, which is why this is a partially applied Function1
.
The body of the lifted stringMunger
method remains intact,
even though we don’t see it in the line where abc
is defined;
however, the body of the partially applied function is still defined as f(a)
,
just the same as stringMunger
’s body.
As you know, it is in the body where the stringMunger
method parameters
(and the abc
Function
parameters) come together to perform a computation.
The type of abc
is (String => String) => String
, which of course means it is a curried function.
The only parameter that is bound to the partially applied function abc
is the String
"abc"
,
and it is bound to the first parameter, a
.
The body of the method (and the Function1
lifted from the method) invokes f(a)
–
in other words, it applies f
to a
.
The second parameter list merely specifies what f
is.
We know from the stringMunger
method body that f
accepts a single String
parameter,
and this matches the type of f
, which is declared in the second parameter list as String => String
.
Thus when we wish to employ the partially bound function abc
we know that we will need to supply a function that accepts the String
bound to a
, and returns another String
.
We could invoke the partially applied function by supplying a FunctionN
with the proper method signature,
or a regular method and Scala will automatically lift it into a FunctionN
, but instead for now we will provide a lambda function.
This lambda function names the parameter that receives the value of a
as x
,
but we could have called it anything else.
scala> abc { x => x * 4 } res6: String = abcabcabcabc
Notice that curly braces are used instead of parentheses around the code block { x => x * 4 }
.
Scala allows {
curly braces }
to be used instead of (
round parentheses )
if a parameter list has only one parameter.
Using curly braces around a block of code makes it easier to read.
We can use the shorthand introduced in the Functions are First Class lecture of the Introduction to Scala course to rewrite the previous line as.
scala> abc { _ * 4 } res7: String = abcabcabcabc
Review Example 2
This example shows a partially applied function that has all its data parameters bound, and is invoked by passed in a lambda to bind to remaining the unbound parameter. This allows you to apply arbitarary transformations to data at will. Knowledge of this technique can be an invaluable addition to your software programming repetoire.
Let’s define a method, parametric in T
, that generates partially applied functions.
twoStringMunger
accepts three parameter lists, two of which are String
s
and the third is a Function
that accepts a Tuple2[String, String]
and returns an instance of T
.
The third parameter, f
, is applied to the first two parameters of type String
, and returns an instance of T
.
scala> def twoStringMunger[T](a: String)(b: String)(f: (String, String) => T) = f(a, b) twoStringMunger: (a: String)(b: String)(f: (String, String) => String)String
Now we can use twoStringMunger
as the basis for a new partially applied function called twoStringToIntPF
,
where T
is defined as Int
, a is bound to "abcdefghi"
,
and b
is bound to "def"
.
The underbar at the end of this expression indicates to the Scala compiler that there are no more parameters to bind,
so a partially applied function is created.
scala> val twoStringToIntPF = twoStringMunger[Int]("abcdefghi")("def") _ abcdef: ((String, String) => Int) => Int = <function1>
We can invoke twoStringToIntPF
by providing an expression for the unbound parameter f
.
The method signature for the original twoStringMunger
method defines the type of f
as
Tuple2[String, String] => T
, and the definition of twoStringMunger
declares T
to be Int
, so the Scala compiler will attempt to interpret the lambda provided to the partially applied
function as having the method signature Tuple2[String, String] => Int
.
That means that s1
and s2
must both be of type String
,
or there must be an implicit in scope that can convert them into String
s,
and the expression s1.indexOf(s2)
must either return an Int
or there must be an implicit in scope that can convert the result of that expression into an Int
.
scala> twoStringToIntPF { (s1, s2) => s1.indexOf(s2) } res8: Int = 3
Review Example 3
This example is just like the previous example, but it uses a method lifted to a function instead of a lambda to bind the remaining unbound parameter of the partially bound function.
First we define a method that references the bound parameters a
, b
and f
in the implementation.
concatStringsAndFnValue
is a curried method that accepts two String
s and a
Function2
that accepts a Tuple2[String, String]
, and returns a String
.
scala> def concatStringsAndFnValue(a: String) (b: String) (f: (String, String) => String) = a + b + f(a, b) another2StringMunger: (a: String)(b: String)(f: (String, String) => String)String
We can invoke the method by supplying arguments for every parameter and obtain a result.
scala> concatStringsAndFnValue("x")("y") { (u, v) => (u.length + v.length).toString } res9: String = xy2
We can also partially apply the method, binding parameters a
and b
,
but not providing a binding for f
, which means that parameter is unbound,
and we have a partially applied function called xyHuh
.
scala> val xyHuh = concatStringsAndFnValue("x")("y") _ xyHuh: ((String, String) => String) => String = <function1>
xyHuh
is a partially applied functioN that has all its data parameters bound, and can only have a function passed in.
Now we define a method called u2v3
that matches the signature for the f
parameter of concatStringsAndFnValue
.
scala> def u2v3(u: String, v: String): String = (u.length * 2 + v.length * 3).toString u2v3: (u: String, v: String)String
When we provide the u2v3
method as the value of the f
parameter,
the Scala compiler lifts v2v3
method to a Function2
.
scala> val bing = xyHuh { u2v3 } bing: String = xy5
For practice, I suggest you try writing additional methods and functions that match the required signature,
and invoke xyHuh
with those transformations.
Exercise – Wrapping Database Operations With the Loan Pattern
Let’s use the loan pattern to write a database wrapper.
JDBC connections implement AutoCloseable
, not Closeable
.
We can modify withCloseable
to work with JDBC connections either of the following two ways.
-
Change the signature to accept the type
AutoCloseable
instead ofCloseable
.Scala codedef withCloseable[C <: AutoCloseable, T](factory: => C) (operation: C => T): Try[T]
-
Change the signature to accept a structural type that references a method called
close
which does not accept parameters and returns nothing. Structural types are a little slower than regular types because they are implemented with introspection.Scala codedef withCloseable[C <: { def close(): Unit }, T](factory: => C) (operation: C => T): Try[T]
It is up to you to decide how you want to modify withCloseable
.
In either case, the body of the withCloseable
method would remain unchanged.
We’ll use the SQLite database, which just uses a single file to store the database and doesn’t have a concept of username or password. You will need to add the appropriate dependency in build.sbt. Here is the SQLite Git project on BitBucket.
You need to find the most recent version of that dependency by using
https://mvnrepository.com
and insert it into build.sbt
.
If you use an Scala-IDE, don’t forget to update the IDE project by typing sbt eclipse
.
Your program only needs this single import statement to make the SQLite functionality available.
import java.sql.{Connection, DriverManager}
You can get a connection to a SQLite database using the following.
Class.forName("org.sqlite.JDBC")
def connection(url: String, userName: String="", password: String=""): Connection = DriverManager.getConnection(url, userName, password)
The SQLite database driver is a JDBC4-compatible driver.
All JDBC 4+ drivers in the class path are automatically loaded.
However, you must manually load any drivers prior to JDBC 4 with the method Class.forName
.
There is no harm in calling Class.forName
, just to be sure.
The following will get a connection to a SQLite database in the current directory called person.db
(remember that userName
and password
are ignored by SQLite).
val conn = connection("jdbc:sqlite:person.db")
You can create a table with the following method:
def createTable(connection: Connection, creationStatement: String)(implicit connection:Connection): Connection = { val statement = connection.createStatement() statement.setQueryTimeout(30) statement.executeUpdate(s"drop table if exists $tableName") statement.executeUpdate(s"create table $tableName ($creationStatement)") connection }
Given a database connection called conn
, you can create a table called person
with columns id
,
name
and age
by writing:
createTable("person", "id integer, name string, age integer", conn)
Of course, if an implicit Connection
instance is in scope you could write:
createTable("person", "id integer, name string, age integer")
The following will insert a new record into the database. Yes, Fred is 40,001 years old.
val stmt = conn.prepareStatement("insert into person (id, name, age) values (?, ?, ?)") stmt.setInt(1, 1) stmt.setString(2, "Fred Flintstone") stmt.setInt(3, 400001) // Cromagnon man! stmt.executeUpdate()
Your task is to write a console application that uses the loan pattern to obtain a database connection and insert a few records, then select them and print them out.
Hints:
-
You can select all records in a database like this:
Scala code
val stmt2 = conn.prepareStatement("select * from person")
-
If you consider
stmt2.executeQuery
as a factory returning aresultSet
, you could pass it towithCloseable
and obtain each successive record by callingresultSet.next
.ResultSet
s are mutable objects, which means they are not threadsafe, but they are convenient to work with in a loop. - You might want to use a
do while
loop to walk through the entireresultSet
. - The number of columns in the
resultSet
can be obtained by callingresultSet.getMetaData.getColumnCount
. - The
i
th column in aresultSet
can be obtained by callingresultSet.getObject(i)
.
Solution
The solutions
project, a sibling of the courseNotes
project, contains a solution.
-
Start by cloning the
sbtTemplate
git project into a new directory calleddbOps
:Shell$ git clone https://github.com/mslinn/sbtTemplate dbOps $ cd dbOps
-
Figure out the dependencies for SqlLite and added them to
libraryDependencies
inbuild.sbt
. As of the date this solution was written, the most recent stable releases for the dependencies were:ShelllibraryDependencies ++= Seq( "org.xerial" % "sqlite-jdbc" % "3.14.2.1" withSources(), // "org.scalatest" %% "scalatest" % "3.0.0" % "test" withSources(), "junit" % "junit" % "4.12" % "test" // Scala IDE requires this; IntelliJ IDEA does not )
-
I opted to use the more flexible structural types approach for rewriting
withCloseable
, and defined a trait calledCloseable
to hold it:Scala codetrait Closeable { //type Closer = AutoCloseable // this works fine, but there is a more flexible approach: /** Alias for structural type that has a method called close which does not accept parameters and returns nothing */ type Closer = { def close(): Unit } /** Modified to handle Closeable or AutoCloseable by using a structural type */ def withCloseable[C <: Closer, T](factory: => C)(operation: C => T): Try[T] = { val closeable = factory try { val result: T = operation(closeable) closeable.close() Success(result) } catch { case throwable: Throwable => try { closeable.close() } catch { case _: Throwable => } println(throwable.toString) Failure(throwable) } } }
-
I created a trait called
DBOps
to contain the database operation methods:Scala codetrait DBOps extends Closeable { import java.sql.{Connection, DriverManager, ResultSet, Statement}
import scala.collection.immutable.Map
Class.forName("org.sqlite.JDBC")
def connectTable(url: String, userName: String="", password: String=""): Connection = { implicit val conn = connection(url, userName, password) createTable("person", "id integer, name string, age integer") }
def connection(url: String, userName: String="", password: String=""): Connection = DriverManager.getConnection(url, userName, password)
def createTable(tableName: String, creationStatement: String)(implicit connection: Connection): Connection = { val statement = connection.createStatement statement.setQueryTimeout(30) statement.executeUpdate(s"drop table if exists $tableName") statement.executeUpdate(s"create table $tableName ($creationStatement)") connection }
def insert(tableName: String, nameValueMap: Map[String, Any])(implicit conn: Connection) = { val keys = nameValueMap.keys.toList // nameValueMap.keys is an iterable, which can only be traversed once val names = keys.mkString(", ") val placeholders = keys.map { _ => "?" }.mkString(", ") val stmtString = s"insert into $tableName ($names) values ($placeholders)" val stmt = conn.prepareStatement(stmtString) nameValueMap.values.zipWithIndex foreach { case (value, i) => stmt.setObject(i+1, value) } stmt.executeUpdate() }
/** Required to support statement.cancel() */ private var maybeCurrentStatement: Option[Statement] = None
/** Must be run from another thread than execute */ def cancel(): Unit = { maybeCurrentStatement.foreach(_.cancel()) maybeCurrentStatement = None }
/** Executes simple queries, which can be cancelled */ def execute(statementString:String)(body: ResultSet => Any)(implicit conn: Connection) = { withCloseable(conn.createStatement) { statement => maybeCurrentStatement = Some(statement) val rs = statement.executeQuery(statementString) withCloseable(rs) { resultSet => body(resultSet) } maybeCurrentStatement = None } } } -
Now writing the app is easy:
Scala code
object LoanDB extends App with Closeable with DBOps { withCloseable(connectTable("jdbc:sqlite:person.db")) { implicit conn: Connection => insert("person", Map("id" -> 1, "name" -> "Fred Flintsone", "age" -> 400002)) insert("person", Map("id" -> 2, "name" -> "Wilma Flintsone", "age" -> 400001)) insert("person", Map("id" -> 3, "name" -> "Barney Rubble", "age" -> 400004)) insert("person", Map("id" -> 4, "name" -> "Betty Rubble", "age" -> 400003))
val stmt2 = conn.prepareStatement("select * from person") withCloseable(stmt2.executeQuery) { resultSet => val columnCount: Int = resultSet.getMetaData.getColumnCount do { val x = (1 to columnCount).map(resultSet.getObject).mkString(", ") println(x) } while (resultSet.next) } } }
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.