How to embed SQLX results in HTML using GO (golang) - html

I am trying to embed results from a Sql Query into an html table using GO as the back end. To iterate row results in Go, the Rows.Next() function is used. This works for printing to the console window, but not for a html table.
Here is my Go Code:
package main
// Database connection Code for http://play.golang.org/p/njPBsg0JjD
import (
"net/http"
"html/template"
"fmt"
"github.com/LukeMauldin/lodbc"
"github.com/jmoiron/sqlx"
"database/sql"
)
//declare database class
var db *sqlx.DB
type webdata struct {
Title string
Heading string
GridTitle string
ColumnHeading [9]string
RowData [9]string
NumOfRows *sql.Rows
}
//this is the function handler to handle '/mbconsole'
func ConsoleHandler(w http.ResponseWriter, r *http.Request) {
//declare an instance of webdata
var wdata webdata
//connect to database
//Set ODBC driver level
lodbc.SetODBCVersion(lodbc.ODBCVersion_3)
var err error
//connect to a Microsoft SQL Server
db, err = sqlx.Open("lodbc", "[connectionstring]")
if err == nil {
fmt.Println("Connection successful")
}else{
fmt.Println("SQL Connection error", err)
}
// Execute the queries
rows, err := db.Query("[Select ...]")
if err != nil {
panic(err.Error())
}
// Get column names
columns, err := rows.Columns()
if err != nil {
panic(err.Error())
}
// Make a slice for the values
values := make([]interface{}, len(columns))
// rows.Scan wants '[]interface{}' as an argument, so we must copy the
// references into such a slice
// See http://code.google.com/p/go-wiki/wiki/InterfaceSlice for details
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}
//fill table headings, the table returns 9 columns so I just hard coded it
for i:=0;i<9;i++ {
wdata.ColumnHeading[i] = columns[i]
}
wdata.NumOfRows = rows
// Fetch rows
for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
panic(err.Error())
}
// Print data
for i, value := range values {
switch value.(type) {
case nil:
wdata.RowData[i] = "NULL"
case []byte:
wdata.RowData[i] = string(value.([]byte))
default:
wdata.RowData[i] = fmt.Sprint(value)
}
}
}
wdata.Title = "Page Title"
wdata.Heading = "My View"
wdata.GridTitle = "My Grid Title"
//get the template the data will be loaded into
t1, err := template.ParseFiles("template.html")
if t1 == nil {
fmt.Println("File Not Found: ", err)
}
//load the template with data and display
terr := t1.Execute(w, &wdata)
if terr != nil {
fmt.Println("terr: ", terr)
}
db = db.Unsafe()
defer db.Close()
}
func main() {
http.HandleFunc("/",ConsoleHandler)
}
Here is my template.html
<html>
<head><title>{{.Title}}</title></head><body>
...
<h1>{{.Heading}}</h1>
<div id="gridviewcontainer">
<br />
<div id="gridtitle">
{{.GridTitle}}
</div>
<table id="gridtable">
<tr>{{range $ColumnIdx, $colheading := .ColumnHeading}}
<th>{{$colheading}}</th>{{end}}</tr>
<<!---This is what is causing the issue, .NumOfRows is not a valid field, must be array, channel, pipeline, or map -->
{{range $index, $rowval := .NumOfRows}}
<tr>
{{range $rowidx, $rowdat := .RowData}}<td>{{$rowdat}}</td>{{end}}
</tr>
{{endfor}}
</table>
...
</body>
</html>
I connect to the database correctly and using the "fmt" package I can print correctly. But I can't figure out how to loop through for number of rows retured in the html page. Is there a way to cast sql.Rows to a correct type or loop for a set integer number of times in html.
ps.
I tried using {{ $index := 3}}...{end}} in the html, but that didn't work
Any input would be greatly appreciated

At the start of a new row, insert a "NewRow" string to serve as a flag in the html. Then in the {{range $rowidx, $rowdat := .RowData}} loop add an if statement that ends and starts a new row if $rowdat == "NewRow"

I use this variant:
Go
func MainPageHandler(w http.ResponseWriter, r *http.Request) {
type User struct {
Name1 string
Name2 string
}
rows, err := database.Query("select .......;")
if err != nil {
log.Println(err)
}
defer rows.Close()
user_current := []User{}
for rows.Next() {
p := User{}
err := rows.Scan(&p.Name1, &p.Name2 )
if err != nil {
fmt.Println(err)
continue
}
user_current = append(user_current, p)
}
tmpl, _ := template.ParseFiles("main_page.html")
tmpl.Execute(w, user_current)
}
html
<table>
<thead><th>name1/th><th>name2</th></thead>
{{range . }}
<tr>
<td>{{.Name1}}</td>
<td>{{.Name2}}</td>
</tr>
{{end}}
</table>

Related

How to print all the values of a variable outside a loop?

In this code, I have a function in which I find the users' names. It finds them and prints all the names inside the loop but when I return the s variable outside the loop, it prints only the first name. It does not print all the names.
Although I append s variable in another variable also but still does not work.
type Signup struct {
Names string
Emails string
}
func FindNames() string {
db := Connect()
var s string
rows, err := db.Query("select names from Signup")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var signup Signup
err := rows.Scan(&signup.Names)
if err != nil {
log.Fatal(err)
}
// s = signup.Names
// fmt.Println(s)
s = fmt.Sprintf("%s", signup.Names)
}
return s
}
When looping over stuff it'd make sense to put the results in a list, then do some actions on the results. Or to use a stringbuilder (as it's faster).
Example with list
type Signup struct {
Names string
Emails string
}
func FindNames() string {
db := Connect()
var s string
rows, err := db.Query("select names from Signup")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var names []string
for rows.Next() {
var signup Signup
err := rows.Scan(&signup.Names)
if err != nil {
log.Fatal(err)
}
names = append(names, signup.Names)
}
return strings.Join(names, "\n") // newline (\n) character to print multiple lines.
}
Stringbuilder
func FindNames() string {
db := Connect()
var s string
rows, err := db.Query("select names from Signup")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var sb strings.Builder
for rows.Next() {
var signup Signup
err := rows.Scan(&signup.Names)
if err != nil {
log.Fatal(err)
}
sb.WriteString(signup.Names)
sb.WriteRune('\n')
}
return sb.String()
}

Saving Read/Write/Save to json from Struct of Map

I've been trying to have a " working " file to which i save certain basic state of my application instead of having them in Ram since they would need to be saved everyday, i've decided on creating file per day, this part is working but i've stripped it from the code for more clarity.
Now i'm able to initialise my file with false value for the informations struct and then unmarshalling and reading from it.
The problem arise when i'm trying to update the "file" after it's been unmarshalled before i save it back to the text file.
The isImportStarted does work (when removing the erronous line obv ) but i can't seem to update the file properly i get this error :
./test.go:62:34: cannot assign to struct field
TheList[symbol].ImportStarted in map
./test.go:71:3: cannot take the address of
TheList[symbol].ImportStarted
./test.go:71:34: cannot assign to &TheList[symbol].ImportStarted
My code :
package main
import (
"encoding/json"
"fmt"
"os"
"io/ioutil"
"log"
)
type Informations struct {
ImportStarted bool
ImportDone bool
}
var MyList = map[string]*Informations{
"test": &Informations{ImportStarted: false,ImportDone:false},
"test2": &Informations{ImportStarted: false,ImportDone:false},
}
func ReadFile(filename string) []byte{
data, err := ioutil.ReadFile(filename)
if err != nil {
log.Panicf("failed reading data from file: %s", err)
}
return data
}
func writeFile(json string,filename string){
file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
defer file.Close()
if err != nil {
fmt.Println(err)
}
_,err2 := file.WriteString(json)
fmt.Println(err2)
}
func main() {
isImportStarted("test")
ImportStart("test")
}
func ImportStart(symbol string){
filename := "test.txt"
_, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("File does not exist creating it...")
file, err := os.Create(filename)
jsonString, _ := json.Marshal(MyList)
writeFile(string(jsonString),filename)
if err != nil {
fmt.Println(err)
}
fmt.Println("reading from file"+filename )
x := ReadFile(filename)
var TheList = map[string]Informations{}
json.Unmarshal(x,&TheList )
TheList[symbol].ImportStarted = true
defer file.Close()
//wanting to save afterwards...
}
} else {
fmt.Println("reading from file "+ filename)
x := ReadFile(filename)
var TheList = map[string]Informations{}
json.Unmarshal(x,&TheList )
&TheList[symbol].ImportStarted = true
}
}
func isImportStarted(symbol string) bool{
filename := "test.txt"
x := ReadFile(filename)
var TheList = map[string]Informations{}
json.Unmarshal(x,&TheList )
return TheList[symbol].ImportStarted
}
I've tried the Why do I get a "cannot assign" error when setting value to a struct as a value in a map? question but it doesn't fit my use case at all as it would effectivly initialize all my structs with nil instead of {false,false}
Any ideas?
Try var TheList = map[string]*Informations{}, why you cannot assign a value in a map please refer to why-do-i-get-a-cannot-assing-error or access-struct-in-map-without-copying

How to get values from a variable of type *sql.Rows in the view?

In posts table, there are attributes like title, content.
I can get data of type *sql.Rows and pass them to the view by
posts, err := db.Query("SELECT id, title FROM posts WHERE id = 1")
and
err = tpl.ExecuteTemplate(w, "index.gohtml", posts)
but I couldn't display the title value in the view. Here is my code.
index.go
package main
import (
"net/http"
"fmt"
"log"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func index(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("mysql", "root:****#/database")
if err != nil {
panic(err.Error())
}
defer db.Close()
posts, err := db.Query("SELECT id, title FROM posts WHERE id = 1")
var id int
var title string
for posts.Next() {
err = posts.Scan(&id, &title)
if err != nil {
panic(err.Error())
}
}
fmt.Println(posts)
defer posts.Close()
err = tpl.ExecuteTemplate(w, "index.gohtml", posts)
if err != nil {
log.Println(err)
}
}
index.gohtml
<h1>Awesome Posts</h1>
{{$posts := .}}
<p>{{$posts}}</p>
There are a few mistakes in your code and I think you have misunderstood how to extract data using the sql package.
As Flimzy said in the comments, you should pass a proper context struct which contains your ID and Title values somewhere.
If you check the docs for sql.Rows you will see how to extract the data at each of the rows from your query...and in fact you already know how to get the rows and column values - by using the Next() and Scan() methods. But this should not be done by code in the HTML template, it should store the results in some variable which is passed to the template.
Quick Answer
A quick answer to your question would be to change how you pass the values into the template and amend the template. Seeing as you declare id and title variables you should pass them to the template:
err = tpl.ExecuteTemplate(w, "index.gohtml", map[string]interface{}{"ID": id,"Title": title})
if err != nil {
log.Println(err)
}
<h1>Awesome Posts</h1>
<p>{{.ID}} - {{.Title}}</p>
Having a Post model
A better solution would be to have a struct which holds all of the properties of the post, and use this to Scan into.
type Post struct{
ID int
Title string
}
...
var p Post
...
_ = rows.Scan(&p)
However, there is another problem with the way you are storing the results of the query. You are using db.Query to return a single row - This is an assumption as you have WHERE ID=1. If you expect only one post to be returned then use the QueryRow method: (N.B. you can chain on the Scan method for simplicity)
var p Post
// error handling removed for brevity
_ = db.QueryRow("SELECT id, title FROM posts WHERE id = 1").Scan(&p)
_ = tpl.ExecuteTemplate(w, "index.gohtml", p)
If, however, you are expecting to retrieve multiple posts (and you were just adding the where clause for simplicity), then you need to Scan into a Post struct, and append into a slice of Posts.
rows, _ := db.Query("SELECT id, title FROM posts")
defer rows.Close()
var posts []Post
for rows.Next() {
var p Post
_ = posts.Scan(&id, &p) // you should handle error here.
posts = append(posts, p)
}
if err = tpl.ExecuteTemplate(w, "index.gohtml", posts); err!=nil{
log.Println(err)
}
Other considerations
You should not be creating a connection to the DB within the HTTP handler. One way is to have a global variable which holds the connection. A struct with embedded connection can work and/or it is possible to abstract away the connection into a package as well.
/db/db.go
package db
import (
"database/sql"
// MYSQL driver
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
// Open handles the opening of the DB
func Open(connstr string) (err error) {
db, err = sql.Open("mysql", connstr)
if err != nil {
return err
}
return nil
}
// Close handles the closing of the DB
func Close() error {
return db.Close()
}
/db/posts.go
package db
// Post model
type Post struct {
ID uint
Title string
Body string
}
const (
getPosts = `SELECT id, title, body FROM posts`
getAPost = `SELECT id, title, body FROM posts WHERE id=?`
)
// GetPosts will return all posts from the DB
func GetPosts() ([]Post, error) {
rows, err := db.Query(getPosts)
if err != nil {
return nil, err
}
var posts []Post
for rows.Next() {
var p Post
if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil {
return nil, err
}
posts = append(posts, p)
}
return posts, nil
}
// GetPost will return single post identified by ID from the DB
func GetPost(id uint) (Post, error) {
var p Post
if err := db.QueryRow(getAPost, id).Scan(&p.ID, &p.Title, &p.Body); err != nil {
return p, err
}
return p, nil
}
main.go
import (
"chilledoj/sopost/db" // this is the absolute path to the db folder
"html/template"
"log"
"net/http"
"strconv"
"flag"
"github.com/gorilla/mux"
)
var dbconn string
func init() {
flag.StringVar(&dbconn, "dbconn", "", "MYSQL DB Connection string")
flag.Parse()
}
func main() {
if dbconn == "" {
log.Fatal("DB Connection string not set")
}
if err := db.Open(dbconn); err != nil {
log.Fatal(err)
}
defer db.Close()
r := mux.NewRouter()
r.HandleFunc("/", indexHandler())
r.HandleFunc("/posts", postsHandler())
r.HandleFunc("/posts/{id}", postHandler())
if err := http.ListenAndServe(":8080", r); err != nil {
log.Panic(err)
}
}
var indexHandler = func() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<h1>Welcome</h1>Posts`))
}
}
var postsHandler = func() http.HandlerFunc {
tmpl, err := template.New("posts").Parse(`<h1>Awesome Posts</h1>
<ul>{{range .}}
<li>{{.Title}}</li>
{{end}}</ul>
<hr/>
Home`)
if err != nil {
log.Panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
posts, err := db.GetPosts()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
err = tmpl.Execute(w, posts)
if err != nil {
log.Printf("There was a template Error.\n%v\n", err)
}
}
}
var postHandler = func() http.HandlerFunc {
tmpl, err := template.New("posts").Parse(`<h1>Awesome Posts</h1>
<h2>{{.Title}}</h2>
<p>{{.Body}}</p>
<hr/>
Home
Posts`)
if err != nil {
log.Panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.ParseInt(vars["id"], 10, 32)
if err != nil {
http.NotFound(w, r)
return
}
post, err := db.GetPost(uint(id))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
tmpl.Execute(w, post)
}
}
Run the above using
go run main.go -dbconn [dbuser]:[dbpass]#/[dbname]?parseTime=true
Another way is to use dependency injection and have a function take in a db connection but return an http.HandlerFunc. e.g.
var indexHandler = function (db *sql.DB) http.HandlerFunc{
return function(w http.ResponseWriter, r *http.Request){
// now have access to db
}
}
http.HandleFunc("/posts", indexHandler())

Else condition seems not working in go

I have a MySQL database with one value in it, a string: "192.168.0.1"
Here is my code:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func main() {
db, err := sql.Open("mysql", "be:me#tcp(127.0.0.1:3306)/ipdb?charset=utf8")
checkErr(err)
ip := "'192.168.0.1'"
rows, err := db.Query("SELECT * FROM Ip_ipdata WHERE ipHost=" + ip)
fmt.Println("insert")
if rows != nil {
for rows.Next() {
var id int
var ip string
err = rows.Scan(&id, &ip)
checkErr(err)
fmt.Println(id)
fmt.Println(ip)
}
} else {
fmt.Println("insert2")
stmt, err2 := db.Prepare("INSERT Ip_ipdata SET ipHost=2")
checkErr(err2)
_, err3 := stmt.Exec(ip)
checkErr(err3)
}
fmt.Println("end")
}
When I put "'192.168.0.1'" in ip it works and shows as expected.
But when I put "'192.168.0.2'" in ip the else statement isn't run and it just exits.
It didn't print "insert2"
screenshot 1
screenshot 2
You should get used to using '?' placeholders in your sql to allow for proper escaping and prevent any potential SQL injection attacks.
You should always check the error in Go before using the returned value.
ip := "192.168.0.1"
rows, err := db.Query("SELECT * FROM Ip_ipdata WHERE ipHost=?", ip)
if err != nil {
// handle error
}
// this will ensure that the DB connection gets put back into the pool
defer rows.Close()
for rows.Next() {
// scan here
}
The Rows returned by Query will not be nil in the case of no results, it will be empty. Try something like this:
func main() {
...
fmt.Println("insert")
checkErr(err)
defer rows.Close()
var found bool
for rows.Next() {
found = true
...
}
if !found {
fmt.Println("insert2")
...
}
fmt.Println("end")
}
Note that like #jmaloney said, more robust error handling is a must as is closing your Rows pointer.

Go SQL driver get interface{} column values

I am trying to use go sql driver to read from database tables and I am converting the values to []map[string]interface{}. The column name is the key of the map and the values are of interface{}. I am adding all the columns into an array. I am using the code sample for "RawBytes" at https://github.com/go-sql-driver/mysql/wiki/Examples as an example to start with.
However, in the example -all the column values are converted to string as follows,
// Fetch rows
for rows.Next() {
// get RawBytes from data
err = rows.Scan(scanArgs...)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
// Now do something with the data.
// Here we just print each column as a string.
var value string
for i, col := range values {
// Here we can check if the value is nil (NULL value)
if col == nil {
value = "NULL"
} else {
value = string(col) //ATTN : converted to string here
}
fmt.Println(columns[i], ": ", value)
}
fmt.Println("-----------------------------------")
}
Is there a way to retain it as interface{} so I can do the necessary type casting while using the columns from []map[string]interface{}
See this https://stackoverflow.com/questions/20271123/go-lang-sql-in-parameters answer which my answer is based on. Using that you can do something like this:
var myMap = make(map[string]interface{})
rows, err := db.Query("SELECT * FROM myTable")
defer rows.Close()
if err != nil {
log.Fatal(err)
}
colNames, err := rows.Columns()
if err != nil {
log.Fatal(err)
}
cols := make([]interface{}, len(colNames))
colPtrs := make([]interface{}, len(colNames))
for i := 0; i < len(colNames); i++ {
colPtrs[i] = &cols[i]
}
for rows.Next() {
err = rows.Scan(colPtrs...)
if err != nil {
log.Fatal(err)
}
for i, col := range cols {
myMap[colNames[i]] = col
}
// Do something with the map
for key, val := range myMap {
fmt.Println("Key:", key, "Value Type:", reflect.TypeOf(val))
}
}
Using the reflect package you can then get the Type for each column as needed as demonstrated with the loop at the end.
This is generic and will work with any table, number of columns etc.
AFter a long struggle i found out the solution. Check belowfunction that converts sql.RawBytes to Int64. This can be easily altered to fit any data type
func GetInt64ColumnValue(payload sql.RawBytes) (int64, error) {
content := reflect.ValueOf(payload).Interface().(sql.RawBytes) // convert to bytes
data := string(content) //convert to string
i, err := strconv.ParseInt(data,10,64) // convert to int or your preferred data type
if err != nil {
log.Printf("got error converting %s to int error %s ",data,err.Error())
return 0, err
}
return i, nil
}