sqlboiler/bdb/drivers/mysql.go
2016-09-08 22:41:57 -07:00

320 lines
8 KiB
Go

package drivers
import (
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/go-sql-driver/mysql"
"github.com/pkg/errors"
"github.com/vattle/sqlboiler/bdb"
)
// MySQLDriver holds the database connection string and a handle
// to the database connection.
type MySQLDriver struct {
connStr string
dbConn *sql.DB
}
// NewMySQLDriver takes the database connection details as parameters and
// returns a pointer to a MySQLDriver object. Note that it is required to
// call MySQLDriver.Open() and MySQLDriver.Close() to open and close
// the database connection once an object has been obtained.
func NewMySQLDriver(user, pass, dbname, host string, port int, sslmode string) *MySQLDriver {
driver := MySQLDriver{
connStr: MySQLBuildQueryString(user, pass, dbname, host, port, sslmode),
}
return &driver
}
// MySQLBuildQueryString builds a query string for MySQL.
func MySQLBuildQueryString(user, pass, dbname, host string, port int, sslmode string) string {
var config mysql.Config
config.User = user
if len(pass) != 0 {
config.Passwd = pass
}
config.DBName = dbname
config.Net = "tcp"
config.Addr = host
if port == 0 {
port = 3306
}
config.Addr += ":" + strconv.Itoa(port)
config.TLSConfig = sslmode
return config.FormatDSN()
}
// Open opens the database connection using the connection string
func (m *MySQLDriver) Open() error {
var err error
m.dbConn, err = sql.Open("mysql", m.connStr)
if err != nil {
return err
}
return nil
}
// Close closes the database connection
func (m *MySQLDriver) Close() {
m.dbConn.Close()
}
// UseLastInsertID returns false for postgres
func (m *MySQLDriver) UseLastInsertID() bool {
return true
}
// TableNames connects to the postgres database and
// retrieves all table names from the information_schema where the
// table schema is public.
func (m *MySQLDriver) TableNames(schema string, whitelist, blacklist []string) ([]string, error) {
var names []string
query := fmt.Sprintf(`select table_name from information_schema.tables where table_schema = ?`)
args := []interface{}{schema}
if len(whitelist) > 0 {
query += fmt.Sprintf("and table_name in (%s);", strings.Repeat(",?", len(whitelist))[1:])
for _, w := range whitelist {
args = append(args, w)
}
} else if len(blacklist) > 0 {
query += fmt.Sprintf("and table_name not in (%s);", strings.Repeat(",?", len(blacklist))[1:])
for _, b := range blacklist {
args = append(args, b)
}
}
rows, err := m.dbConn.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
names = append(names, name)
}
return names, nil
}
// Columns takes a table name and attempts to retrieve the table information
// from the database information_schema.columns. It retrieves the column names
// and column types and returns those as a []Column after TranslateColumnType()
// converts the SQL types to Go types, for example: "varchar" to "string"
func (m *MySQLDriver) Columns(schema, tableName string) ([]bdb.Column, error) {
var columns []bdb.Column
rows, err := m.dbConn.Query(`
select column_name, data_type, column_default, is_nullable,
exists (
select c.column_name
from information_schema.table_constraints tc
inner join information_schema.key_column_usage kcu
on tc.constraint_name = kcu.constraint_name and tc.table_name = kcu.table_name and tc.table_schema = kcu.table_schema
where c.column_name = kcu.column_name and tc.table_name = c.table_name and
(tc.constraint_type = 'PRIMARY KEY' or tc.constraint_type = 'UNIQUE')
) as is_unique
from information_schema.columns as c
where table_name = ? and table_schema = ?;
`, tableName, schema)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var colName, colType, colDefault, nullable string
var unique bool
var defaultPtr *string
if err := rows.Scan(&colName, &colType, &defaultPtr, &nullable, &unique); err != nil {
return nil, errors.Wrapf(err, "unable to scan for table %s", tableName)
}
if defaultPtr != nil && *defaultPtr != "NULL" {
colDefault = *defaultPtr
}
column := bdb.Column{
Name: colName,
DBType: colType,
Default: colDefault,
Nullable: nullable == "YES",
Unique: unique,
Validated: psqlIsValidated(colType),
}
columns = append(columns, column)
}
return columns, nil
}
// PrimaryKeyInfo looks up the primary key for a table.
func (m *MySQLDriver) PrimaryKeyInfo(schema, tableName string) (*bdb.PrimaryKey, error) {
pkey := &bdb.PrimaryKey{}
var err error
query := `
select tc.constraint_name
from information_schema.table_constraints as tc
where tc.table_name = ? and tc.constraint_type = 'PRIMARY KEY' and tc.table_schema = ?;`
row := m.dbConn.QueryRow(query, tableName, schema)
if err = row.Scan(&pkey.Name); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
queryColumns := `
select kcu.column_name
from information_schema.key_column_usage as kcu
where table_name = ? and constraint_name = ? and table_schema = ?;`
var rows *sql.Rows
if rows, err = m.dbConn.Query(queryColumns, tableName, pkey.Name, schema); err != nil {
return nil, err
}
defer rows.Close()
var columns []string
for rows.Next() {
var column string
err = rows.Scan(&column)
if err != nil {
return nil, err
}
columns = append(columns, column)
}
if err = rows.Err(); err != nil {
return nil, err
}
pkey.Columns = columns
return pkey, nil
}
// ForeignKeyInfo retrieves the foreign keys for a given table name.
func (m *MySQLDriver) ForeignKeyInfo(schema, tableName string) ([]bdb.ForeignKey, error) {
var fkeys []bdb.ForeignKey
query := `
select constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
from information_schema.key_column_usage
where table_schema = ? and referenced_table_schema = ? and table_name = ?
`
var rows *sql.Rows
var err error
if rows, err = m.dbConn.Query(query, schema, schema, tableName); err != nil {
return nil, err
}
for rows.Next() {
var fkey bdb.ForeignKey
var sourceTable string
fkey.Table = tableName
err = rows.Scan(&fkey.Name, &sourceTable, &fkey.Column, &fkey.ForeignTable, &fkey.ForeignColumn)
if err != nil {
return nil, err
}
fkeys = append(fkeys, fkey)
}
if err = rows.Err(); err != nil {
return nil, err
}
return fkeys, nil
}
// TranslateColumnType converts postgres database types to Go types, for example
// "varchar" to "string" and "bigint" to "int64". It returns this parsed data
// as a Column object.
func (m *MySQLDriver) TranslateColumnType(c bdb.Column) bdb.Column {
if c.Nullable {
switch c.DBType {
case "tinyint":
c.Type = "null.Int8"
case "smallint":
c.Type = "null.Int16"
case "mediumint", "int", "integer":
c.Type = "null.Int"
case "bigint":
c.Type = "null.Int64"
case "float":
c.Type = "null.Float32"
case "double", "double precision", "real":
c.Type = "null.Float64"
case "boolean", "bool":
c.Type = "null.Bool"
case "date", "datetime", "timestamp", "time":
c.Type = "null.Time"
case "binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob":
c.Type = "null.Bytes"
case "json":
c.Type = "types.JSON"
default:
c.Type = "null.String"
}
} else {
switch c.DBType {
case "tinyint":
c.Type = "int8"
case "smallint":
c.Type = "int16"
case "mediumint", "int", "integer":
c.Type = "int"
case "bigint":
c.Type = "null.Int64"
case "float":
c.Type = "float32"
case "double", "double precision", "real":
c.Type = "float64"
case "boolean", "bool":
c.Type = "bool"
case "date", "datetime", "timestamp", "time":
c.Type = "time.Time"
case "binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob":
c.Type = "[]byte"
case "json":
c.Type = "types.JSON"
default:
c.Type = "string"
}
}
return c
}
var mySQLValidatedTypes = []string{}
// isValidated checks if the database type is in the validatedTypes list.
func mySQLIsValidated(typ string) bool {
for _, v := range mySQLValidatedTypes {
if v == typ {
return true
}
}
return false
}