Saturday, 19 September 2015

Making connections to the Oracle Database from Golang

I posted the other day about getting going with Golang[1], and as mentioned am planning to do a series of posts on various aspects of the language - as a means to help with the learning process.

Being an Oracle developer, it seemed logical I would want to be able to make connections to the Oracle database. Built into the language core is an SQL interface (for issuing queries and statements) which is through the module "database/sql". The other side of it is that you need a valid driver. A list of available drivers is on the go wiki[2]. You will see that there are 2 listed for Oracle. How do I judge which project to use? First I see that one has considerably more stars and for another, the online commentary I see also seems to suggest that same package. And that is https://github.com/mattn/go-oci8.

Driver setup

Before we get to the driver, you need to make sure you have an Oracle client and the SDK installed on your system. For, this I followed the steps as per the Ubuntu wiki[3] - where you would at minimum want to install the instant client and the SDK. Once the installation is complete, you should end up with an $ORACLE_HOME that points to /usr/lib/oracle/11.2/client64 or something similar depending on your systems architecture and the version of Oracle you installed.

Within $ORACLE_HOME, you should have 3 folders:
  1. bin
  2. include
  3. lib
At this point, if you try to install the aforementioned driver, you will get an error:

$ go get github.com/mattn/go-oci8
# pkg-config --cflags oci8
Package oci8 was not found in the pkg-config search path.
Perhaps you should add the directory containing `oci8.pc'
to the PKG_CONFIG_PATH environment variable
No package 'oci8' found
exit status 1

So, before you install the driver, you need to do the pkg-config set up. This application should be already installed on your system, but if not you can do so with sudo apt-get install pkg-config. This program is just a way to provide the necessary details for compiling and linking a program to a library[4].

The project itself does provide an example of a package config file for oci8, however it's example is for Windows, so the config file I came up with was:

prefixdir=/usr/lib/oracle/11.2/client64
libdir=${prefixdir}/lib
includedir=${prefixdir}/include

Name: OCI
Description: Oracle database driver
Version: 11.2
Libs: -L${libdir} -lclntsh
Cflags: -I${includedir}

I just grabbed the path to $ORACLE_HOME and placed it in the variable prefixdir, since the config file doesn't know about environment variables.

Before installing the driver, you need to make this file available in a place that pkg-config knows about. So there are two options here. First, you can place it in the system wide pkg-config directory: /usr/lib/pkgconfig and the system will automatically find it. The other option if you have it some obscure location is to export that location into the environment variable PKG_CONFIG_PATH. Once that is set up, you should be able to successfully install the driver with: go get github.com/mattn/go-oci8

You will now find that both the compiled version and source for that package in your GOPATH (in pkg and src folders respectively).

Connecting and querying database

Now that we have the driver available, we can begin with our program. Most of the aspects I describe here are also documented on the go wiki[5].

First, we want to open our connection. This is done with the sql.Open function[6], where you will pass in the name of the driver, and the connection string.

db, err := sql.Open("oci8", "hr/hr2@example.com:1521/xe")
if err != nil {
    fmt.Println(err)
    return
}
defer db.Close()

Opening the connection is actually deferred until you start issuing statements - so for example, if you enter incorrect password, only when you attempt to query the database will you find out. So in this case, the Ping function[7] might be useful to test the connection is OK.

err = db.Ping()
if err != nil {
    fmt.Printf("Error connecting to the database: %s\n", err)
    return
}

Then, to the actual querying. Here we have the functions Query[8] and QueryRow[9] depending on if you want return a row set or a single row respectively. You will more than likely want to use bind variables in your queries - the documentation suggests the use of `?` as placeholders. And I've also seen examples of `$1`. However, using either of those methods seemed to return the error:

sql: statement expects 0 inputs; got 1

What I've found works is what you would be used to use an Oracle developer - a number prefixed with a colon (:1). I'm assuming that's just the specific implementation of the driver. note: If you repeat the same bind number in your program, don't expect it to be re-used - it's just the position of the bind in the query, and then the respective parameter index. In the result set, you'll then want to declare variables of suitable data types to store the data in, which is fetched with the Scan function.

rows,err := db.Query("select employee_id, first_name from employees where employee_id < :1", 105)
if err != nil {
    fmt.Println("Error fetching employees")
    fmt.Println(err)
    return
}
defer rows.Close()

for rows.Next() {

    var emp_id int
    var first_name string
    rows.Scan(&emp_id, &first_name)
    println(emp_id, first_name)

}

var last_name string
err = db.QueryRow("select last_name from employees where employee_id = :1", 101).Scan(&last_name)
if err != nil {
    fmt.Println("Error fetching row")
    fmt.Println(err)
}

fmt.Printf("Last name is %s\n", last_name)


If you are running a query that doesn't return anything (such as insert or create statements), you would typically use the Exec[10] function. And further, if you are repeating a statement, the Prepare[11] function.

Full program (main.go):

package main

import (
    "fmt"
    "database/sql"
    _ "github.com/mattn/go-oci8"
)

func main(){


    db, err := sql.Open("oci8", "hr/hr@example.com:1521/xe")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer db.Close()
    
    
    if err = db.Ping(); err != nil {
        fmt.Printf("Error connecting to the database: %s\n", err)
        return
    }

    rows,err := db.Query("select employee_id, first_name from employees where employee_id < :1", 105)
    if err != nil {
        fmt.Println("Error fetching employees")
        fmt.Println(err)
        return
    }
    defer rows.Close()

    for rows.Next() {

        var emp_id int
        var first_name string
        rows.Scan(&emp_id, &first_name)
        println(emp_id, first_name)

    }
    
    var last_name string
    err = db.QueryRow("select last_name from employees where employee_id = :1", 101).Scan(&last_name)
    if err != nil {
        fmt.Println("Error fetching row")
        fmt.Println(err)
    }
    
    fmt.Printf("Last name is %s\n", last_name)

}

1: http://tschf.github.io/2015/09/18/getting-going-with-golang/
2: https://github.com/golang/go/wiki/SQLDrivers
3: https://help.ubuntu.com/community/Oracle%20Instant%20Client
4: http://people.freedesktop.org/~dbn/pkg-config-guide.html
5: https://github.com/golang/go/wiki/SQLInterface
6: http://golang.org/pkg/database/sql/#Open
7: http://golang.org/pkg/database/sql/#DB.Ping
8: http://golang.org/pkg/database/sql/#DB.Query
9: http://golang.org/pkg/database/sql/#DB.QueryRow
10: http://golang.org/pkg/database/sql/#DB.Exec
11: http://golang.org/pkg/database/sql/#DB.Prepare

5 comments:

  1. is it for windows or linux why using sudo

    ReplyDelete
  2. Nice article.
    Since I started searching for the best option for a Go Oracle driver I've been wondering about what happens when I'm finished with my application and have to distribute it to let's say a client. Do I have to do the whole Oracle client setup and pkg-config every time? Or once I build the project the library will be sstatically linked and compiled with the binary?

    ReplyDelete
  3. This worked perfectly on OS X. I used brew to install package config. After that I was up and running.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. If you are having problems installing Oracle and using it with golang on a Mac, You can also checkout my article at https://gocodecloud.com/blog/2016/08/09/accessing-an-oracle-db-in-go/

    ReplyDelete