func (h *Handler) execQuery(c *conn, q string) (io.ReadCloser, bool, error) {
	tx, err := c.db.Begin()
	if err != nil {
		return nil, false, err
	}
	defer tx.Rollback()
	rows, err := tx.Query(q)
	if err != nil {
		return nil, false, err
	}
	defer rows.Close()
	return rows2ltsv(rows, c.maxRows)
}
execQuery executes a query in a transaction which will be rollbacked.
package db
import (
	"errors"
	"io"
	"net/url"
	"regexp"
	"strings"
	"time"
	"github.com/koron/nvgd/common_const"
	"github.com/koron/nvgd/protocol"
	"github.com/koron/nvgd/resource"
)
const assetPrefix = "assets/"
type Handler struct {
}
func init() {
	protocol.MustRegister("db", &Handler{})
}
func (h *Handler) Open(u *url.URL) (*resource.Resource, error) {
	query := path(u)
	if query == "" || strings.HasPrefix(query, assetPrefix) {
		return h.openAsset(query)
	}
	c, err := openDB(u)
	if err != nil {
		return nil, err
	}
	if err := h.checkSanity(query); err != nil {
		return nil, err
	}
	st := time.Now()
	rc, truncated, err := h.execQuery(c, query)
	dur := time.Since(st)
	if err != nil {
		return nil, err
	}
	rs := resource.New(rc)
	rs.Put(common_const.LTSV, true)
	rs.Put(common_const.SQLQuery, query)
	if truncated {
		rs.Put(common_const.SQLTruncatedBy, c.maxRows)
	}
	rs.Put(common_const.SQLExecTime, dur)
	return rs, nil
}
func (h *Handler) openAsset(s string) (*resource.Resource, error) {
	if s == "" {
		s = "index.html"
	}
	if strings.HasPrefix(s, assetPrefix) {
		s = s[len(assetPrefix):]
	}
	f, err := Assets.Open(s)
	if err != nil {
		return nil, err
	}
	rs := resource.New(f).GuessContentType(s)
	return rs, nil
}
var reBadQuery = regexp.MustCompile(`(?i:^\s*(?:insert|update|delete|create|drop|alter|truncate|prepare|execute))`)
func (h *Handler) checkSanity(q string) error {
	if reBadQuery.MatchString(q) {
		return errors.New("including invalid keywords")
	}
	return nil
}