2017-01-14 07:13:32 +01:00
|
|
|
// Package boilingcore has types and methods useful for generating code that
|
2016-06-12 03:25:00 +02:00
|
|
|
// acts as a fully dynamic ORM might.
|
2017-01-14 04:38:40 +01:00
|
|
|
package boilingcore
|
2016-06-12 03:25:00 +02:00
|
|
|
|
|
|
|
import (
|
2016-09-04 12:27:19 +02:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2016-08-24 06:50:14 +02:00
|
|
|
"go/build"
|
2016-06-12 03:25:00 +02:00
|
|
|
"os"
|
2016-08-24 06:50:14 +02:00
|
|
|
"path/filepath"
|
2016-09-04 15:44:54 +02:00
|
|
|
"regexp"
|
2016-06-12 03:25:00 +02:00
|
|
|
"strings"
|
2016-06-12 08:50:38 +02:00
|
|
|
"text/template"
|
2016-06-12 03:25:00 +02:00
|
|
|
|
2016-08-13 20:36:03 +02:00
|
|
|
"github.com/pkg/errors"
|
2016-08-09 09:59:30 +02:00
|
|
|
"github.com/vattle/sqlboiler/bdb"
|
|
|
|
"github.com/vattle/sqlboiler/bdb/drivers"
|
2016-09-15 05:45:09 +02:00
|
|
|
"github.com/vattle/sqlboiler/queries"
|
2016-09-12 07:30:25 +02:00
|
|
|
"github.com/vattle/sqlboiler/strmangle"
|
2016-06-12 03:25:00 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2016-06-12 21:08:34 +02:00
|
|
|
templatesDirectory = "templates"
|
|
|
|
templatesSingletonDirectory = "templates/singleton"
|
2016-06-12 03:25:00 +02:00
|
|
|
|
2016-06-12 21:08:34 +02:00
|
|
|
templatesTestDirectory = "templates_test"
|
|
|
|
templatesSingletonTestDirectory = "templates_test/singleton"
|
|
|
|
|
|
|
|
templatesTestMainDirectory = "templates_test/main_test"
|
2016-06-12 03:25:00 +02:00
|
|
|
)
|
|
|
|
|
2016-06-12 08:27:39 +02:00
|
|
|
// State holds the global data needed by most pieces to run
|
|
|
|
type State struct {
|
|
|
|
Config *Config
|
|
|
|
|
2016-09-09 19:14:18 +02:00
|
|
|
Driver bdb.Interface
|
|
|
|
Tables []bdb.Table
|
2016-09-15 05:45:09 +02:00
|
|
|
Dialect queries.Dialect
|
2016-06-12 08:27:39 +02:00
|
|
|
|
2016-07-17 08:55:15 +02:00
|
|
|
Templates *templateList
|
|
|
|
TestTemplates *templateList
|
|
|
|
SingletonTemplates *templateList
|
|
|
|
SingletonTestTemplates *templateList
|
2016-06-12 08:27:39 +02:00
|
|
|
|
|
|
|
TestMainTemplate *template.Template
|
|
|
|
}
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
// New creates a new state based off of the config
|
|
|
|
func New(config *Config) (*State, error) {
|
2016-06-12 21:08:34 +02:00
|
|
|
s := &State{
|
|
|
|
Config: config,
|
|
|
|
}
|
2016-06-12 03:25:00 +02:00
|
|
|
|
|
|
|
err := s.initDriver(config.DriverName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to the driver database
|
|
|
|
if err = s.Driver.Open(); err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return nil, errors.Wrap(err, "unable to connect to the database")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
2016-09-09 07:41:57 +02:00
|
|
|
err = s.initTables(config.Schema, config.WhitelistTables, config.BlacklistTables)
|
2016-06-12 03:25:00 +02:00
|
|
|
if err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return nil, errors.Wrap(err, "unable to initialize tables")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
2016-09-04 12:27:19 +02:00
|
|
|
if s.Config.Debug {
|
|
|
|
b, err := json.Marshal(s.Tables)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "unable to json marshal tables")
|
|
|
|
}
|
2016-09-11 18:22:20 +02:00
|
|
|
fmt.Printf("%s\n", b)
|
2016-09-04 12:27:19 +02:00
|
|
|
}
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
err = s.initOutFolder()
|
|
|
|
if err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return nil, errors.Wrap(err, "unable to initialize the output folder")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err = s.initTemplates()
|
|
|
|
if err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return nil, errors.Wrap(err, "unable to initialize templates")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
2016-09-04 15:44:54 +02:00
|
|
|
err = s.initTags(config.Tags)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "unable to initialize struct tags")
|
|
|
|
}
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run executes the sqlboiler templates and outputs them to files based on the
|
|
|
|
// state given.
|
|
|
|
func (s *State) Run(includeTests bool) error {
|
2016-06-12 09:32:46 +02:00
|
|
|
singletonData := &templateData{
|
2016-08-28 16:12:37 +02:00
|
|
|
Tables: s.Tables,
|
2016-09-08 23:23:10 +02:00
|
|
|
Schema: s.Config.Schema,
|
2016-08-28 16:12:37 +02:00
|
|
|
DriverName: s.Config.DriverName,
|
|
|
|
UseLastInsertID: s.Driver.UseLastInsertID(),
|
|
|
|
PkgName: s.Config.PkgName,
|
|
|
|
NoHooks: s.Config.NoHooks,
|
|
|
|
NoAutoTimestamps: s.Config.NoAutoTimestamps,
|
2016-09-09 19:14:18 +02:00
|
|
|
Dialect: s.Dialect,
|
2016-09-12 07:30:25 +02:00
|
|
|
LQ: strmangle.QuoteCharacter(s.Dialect.LQ),
|
|
|
|
RQ: strmangle.QuoteCharacter(s.Dialect.RQ),
|
2016-06-20 07:22:50 +02:00
|
|
|
|
|
|
|
StringFuncs: templateStringMappers,
|
2016-06-12 09:32:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := generateSingletonOutput(s, singletonData); err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.Wrap(err, "singleton template output")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
2016-09-02 03:22:56 +02:00
|
|
|
if !s.Config.NoTests && includeTests {
|
2016-06-12 09:32:46 +02:00
|
|
|
if err := generateTestMainOutput(s, singletonData); err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.Wrap(err, "unable to generate TestMain output")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
2016-06-12 09:32:46 +02:00
|
|
|
if err := generateSingletonTestOutput(s, singletonData); err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.Wrap(err, "unable to generate singleton test template output")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, table := range s.Tables {
|
|
|
|
if table.IsJoinTable {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
data := &templateData{
|
2016-08-28 16:12:37 +02:00
|
|
|
Tables: s.Tables,
|
|
|
|
Table: table,
|
2016-09-08 23:23:10 +02:00
|
|
|
Schema: s.Config.Schema,
|
2016-08-28 16:12:37 +02:00
|
|
|
DriverName: s.Config.DriverName,
|
|
|
|
UseLastInsertID: s.Driver.UseLastInsertID(),
|
|
|
|
PkgName: s.Config.PkgName,
|
|
|
|
NoHooks: s.Config.NoHooks,
|
|
|
|
NoAutoTimestamps: s.Config.NoAutoTimestamps,
|
2016-09-04 15:44:54 +02:00
|
|
|
Tags: s.Config.Tags,
|
2016-09-09 19:14:18 +02:00
|
|
|
Dialect: s.Dialect,
|
2016-09-12 07:30:25 +02:00
|
|
|
LQ: strmangle.QuoteCharacter(s.Dialect.LQ),
|
|
|
|
RQ: strmangle.QuoteCharacter(s.Dialect.RQ),
|
2016-06-20 07:22:50 +02:00
|
|
|
|
|
|
|
StringFuncs: templateStringMappers,
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the regular templates
|
|
|
|
if err := generateOutput(s, data); err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.Wrap(err, "unable to generate output")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the test templates
|
2016-09-02 03:22:56 +02:00
|
|
|
if !s.Config.NoTests && includeTests {
|
2016-06-12 03:25:00 +02:00
|
|
|
if err := generateTestOutput(s, data); err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.Wrap(err, "unable to generate test output")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup closes any resources that must be closed
|
|
|
|
func (s *State) Cleanup() error {
|
|
|
|
s.Driver.Close()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// initTemplates loads all template folders into the state object.
|
|
|
|
func (s *State) initTemplates() error {
|
|
|
|
var err error
|
|
|
|
|
2016-08-24 06:50:14 +02:00
|
|
|
basePath, err := getBasePath(s.Config.BaseDir)
|
2016-06-12 03:25:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-08-24 06:50:14 +02:00
|
|
|
s.Templates, err = loadTemplates(filepath.Join(basePath, templatesDirectory))
|
2016-06-12 03:25:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-08-24 06:50:14 +02:00
|
|
|
s.SingletonTemplates, err = loadTemplates(filepath.Join(basePath, templatesSingletonDirectory))
|
2016-06-12 03:25:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-02 03:22:56 +02:00
|
|
|
if !s.Config.NoTests {
|
|
|
|
s.TestTemplates, err = loadTemplates(filepath.Join(basePath, templatesTestDirectory))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-06-12 03:25:00 +02:00
|
|
|
|
2016-09-02 03:22:56 +02:00
|
|
|
s.SingletonTestTemplates, err = loadTemplates(filepath.Join(basePath, templatesSingletonTestDirectory))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-08-24 06:50:14 +02:00
|
|
|
|
2016-09-02 03:22:56 +02:00
|
|
|
s.TestMainTemplate, err = loadTemplate(filepath.Join(basePath, templatesTestMainDirectory), s.Config.DriverName+"_main.tpl")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-06-12 21:08:34 +02:00
|
|
|
}
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-24 06:50:14 +02:00
|
|
|
var basePackage = "github.com/vattle/sqlboiler"
|
|
|
|
|
|
|
|
func getBasePath(baseDirConfig string) (string, error) {
|
|
|
|
if len(baseDirConfig) > 0 {
|
|
|
|
return baseDirConfig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
p, _ := build.Default.Import(basePackage, "", build.FindOnly)
|
|
|
|
if p != nil && len(p.Dir) > 0 {
|
|
|
|
return p.Dir, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return os.Getwd()
|
|
|
|
}
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
// initDriver attempts to set the state Interface based off the passed in
|
|
|
|
// driver flag value. If an invalid flag string is provided an error is returned.
|
|
|
|
func (s *State) initDriver(driverName string) error {
|
|
|
|
// Create a driver based off driver flag
|
|
|
|
switch driverName {
|
|
|
|
case "postgres":
|
2016-06-23 08:48:49 +02:00
|
|
|
s.Driver = drivers.NewPostgresDriver(
|
2016-06-12 03:25:00 +02:00
|
|
|
s.Config.Postgres.User,
|
|
|
|
s.Config.Postgres.Pass,
|
|
|
|
s.Config.Postgres.DBName,
|
|
|
|
s.Config.Postgres.Host,
|
|
|
|
s.Config.Postgres.Port,
|
2016-07-12 00:17:49 +02:00
|
|
|
s.Config.Postgres.SSLMode,
|
2016-06-12 03:25:00 +02:00
|
|
|
)
|
2016-09-09 08:04:58 +02:00
|
|
|
case "mysql":
|
|
|
|
s.Driver = drivers.NewMySQLDriver(
|
|
|
|
s.Config.MySQL.User,
|
|
|
|
s.Config.MySQL.Pass,
|
|
|
|
s.Config.MySQL.DBName,
|
|
|
|
s.Config.MySQL.Host,
|
|
|
|
s.Config.MySQL.Port,
|
|
|
|
s.Config.MySQL.SSLMode,
|
|
|
|
)
|
2016-08-18 14:11:02 +02:00
|
|
|
case "mock":
|
|
|
|
s.Driver = &drivers.MockDriver{}
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if s.Driver == nil {
|
|
|
|
return errors.New("An invalid driver name was provided")
|
|
|
|
}
|
|
|
|
|
2016-09-09 19:14:18 +02:00
|
|
|
s.Dialect.LQ = s.Driver.LeftQuote()
|
|
|
|
s.Dialect.RQ = s.Driver.RightQuote()
|
|
|
|
s.Dialect.IndexPlaceholders = s.Driver.IndexPlaceholders()
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-15 14:42:40 +02:00
|
|
|
// initTables retrieves all "public" schema table names from the database.
|
2016-09-09 07:41:57 +02:00
|
|
|
func (s *State) initTables(schema string, whitelist, blacklist []string) error {
|
2016-06-12 03:25:00 +02:00
|
|
|
var err error
|
2016-09-09 07:41:57 +02:00
|
|
|
s.Tables, err = bdb.Tables(s.Driver, schema, whitelist, blacklist)
|
2016-06-12 03:25:00 +02:00
|
|
|
if err != nil {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.Wrap(err, "unable to fetch table data")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(s.Tables) == 0 {
|
2016-06-13 00:34:57 +02:00
|
|
|
return errors.New("no tables found in database")
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := checkPKeys(s.Tables); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-04 15:44:54 +02:00
|
|
|
// Tags must be in a format like: json, xml, etc.
|
|
|
|
var rgxValidTag = regexp.MustCompile(`[a-zA-Z_\.]+`)
|
|
|
|
|
|
|
|
// initTags removes duplicate tags and validates the format
|
|
|
|
// of all user tags are simple strings without quotes: [a-zA-Z_\.]+
|
|
|
|
func (s *State) initTags(tags []string) error {
|
|
|
|
s.Config.Tags = removeDuplicates(s.Config.Tags)
|
|
|
|
for _, v := range s.Config.Tags {
|
|
|
|
if !rgxValidTag.MatchString(v) {
|
|
|
|
return errors.New("Invalid tag format %q supplied, only specify name, eg: xml")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-06-12 03:25:00 +02:00
|
|
|
// initOutFolder creates the folder that will hold the generated output.
|
|
|
|
func (s *State) initOutFolder() error {
|
2016-06-13 00:34:57 +02:00
|
|
|
return os.MkdirAll(s.Config.OutFolder, os.ModePerm)
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// checkPKeys ensures every table has a primary key column
|
2016-06-23 08:09:56 +02:00
|
|
|
func checkPKeys(tables []bdb.Table) error {
|
2016-06-12 03:25:00 +02:00
|
|
|
var missingPkey []string
|
|
|
|
for _, t := range tables {
|
|
|
|
if t.PKey == nil {
|
|
|
|
missingPkey = append(missingPkey, t.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(missingPkey) != 0 {
|
2016-08-13 20:36:03 +02:00
|
|
|
return errors.Errorf("primary key missing in tables (%s)", strings.Join(missingPkey, ", "))
|
2016-06-12 03:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|