Merge branch 'dev'
This commit is contained in:
commit
ca748b070d
25 changed files with 701 additions and 93 deletions
README.md
bdb
circle.ymlimports.goimports_test.gomain.goqueries
randomize
strmangle
templates.gotemplates
07_relationship_to_one_eager.tpl08_relationship_one_to_one_eager.tpl09_relationship_to_many_eager.tpl
singleton
testdata
types
47
README.md
47
README.md
|
@ -70,10 +70,11 @@ Table of Contents
|
||||||
* [Upsert](#upsert)
|
* [Upsert](#upsert)
|
||||||
* [Reload](#reload)
|
* [Reload](#reload)
|
||||||
* [Exists](#exists)
|
* [Exists](#exists)
|
||||||
|
* [Enums](#enums)
|
||||||
* [FAQ](#faq)
|
* [FAQ](#faq)
|
||||||
* [Won't compiling models for a huge database be very slow?](#wont-compiling-models-for-a-huge-database-be-very-slow)
|
* [Won't compiling models for a huge database be very slow?](#wont-compiling-models-for-a-huge-database-be-very-slow)
|
||||||
* [Missing imports for generated package](#missing-imports-for-generated-package)
|
* [Missing imports for generated package](#missing-imports-for-generated-package)
|
||||||
* [Benchmarks](#benchmarks)
|
* [Benchmarks](#benchmarks)
|
||||||
|
|
||||||
## About SQL Boiler
|
## About SQL Boiler
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ Table of Contents
|
||||||
- Debug logging
|
- Debug logging
|
||||||
- Schemas support
|
- Schemas support
|
||||||
- 1d arrays, json, hstore & more
|
- 1d arrays, json, hstore & more
|
||||||
|
- Enum types
|
||||||
|
|
||||||
### Supported Databases
|
### Supported Databases
|
||||||
|
|
||||||
|
@ -121,8 +123,8 @@ if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If you don't want to pass in db to all generated methods
|
// If you don't want to pass in db to all generated methods
|
||||||
// you can use boil.SetDB to set it globally, and then use
|
// you can use boil.SetDB to set it globally, and then use
|
||||||
// the G variant methods like so:
|
// the G variant methods like so:
|
||||||
boil.SetDB(db)
|
boil.SetDB(db)
|
||||||
users, err := models.UsersG().All()
|
users, err := models.UsersG().All()
|
||||||
|
@ -178,7 +180,7 @@ fmt.Println(len(users.R.FavoriteMovies))
|
||||||
|
|
||||||
* Go 1.6 minimum, and Go 1.7 for compatibility tests.
|
* Go 1.6 minimum, and Go 1.7 for compatibility tests.
|
||||||
* Table names and column names should use `snake_case` format.
|
* Table names and column names should use `snake_case` format.
|
||||||
* We require `snake_case` table names and column names. This is a recommended default in Postgres,
|
* We require `snake_case` table names and column names. This is a recommended default in Postgres,
|
||||||
and we agree that it's good form, so we're enforcing this format for all drivers for the time being.
|
and we agree that it's good form, so we're enforcing this format for all drivers for the time being.
|
||||||
* Join tables should use a *composite primary key*.
|
* Join tables should use a *composite primary key*.
|
||||||
* For join tables to be used transparently for relationships your join table must have
|
* For join tables to be used transparently for relationships your join table must have
|
||||||
|
@ -1048,6 +1050,43 @@ exists, err := jet.Pilot(db).Exists()
|
||||||
exists, err := models.Pilots(db, Where("id=?", 5)).Exists()
|
exists, err := models.Pilots(db, Where("id=?", 5)).Exists()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Enums
|
||||||
|
|
||||||
|
If your MySQL or Postgres tables use enums we will generate constants that hold their values
|
||||||
|
that you can use in your queries. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TYPE workday AS ENUM('monday', 'tuesday', 'wednesday', 'thursday', 'friday');
|
||||||
|
|
||||||
|
CREATE TABLE event_one (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
day workday NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
An enum type defined like the above, being used by a table, will generate the following enums:
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
WorkdayMonday = "monday"
|
||||||
|
WorkdayTuesday = "tuesday"
|
||||||
|
WorkdayWednesday = "wednesday"
|
||||||
|
WorkdayThursday = "thursday"
|
||||||
|
WorkdayFriday = "friday"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
For Postgres we use `enum type name + title cased` value to generate the const variable name.
|
||||||
|
For MySQL we use `table name + column name + title cased value` to generate the const variable name.
|
||||||
|
|
||||||
|
Note: If your enum holds a value we cannot parse correctly due, to non-alphabet characters for example,
|
||||||
|
it may not be generated. In this event, you will receive errors in your generated tests because
|
||||||
|
the value randomizer in the test suite does not know how to generate valid enum values. You will
|
||||||
|
still be able to use your generated library, and it will still work as expected, but the only way
|
||||||
|
to get the tests to pass in this event is to either use a parsable enum value or use a regular column
|
||||||
|
instead of an enum.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
#### Won't compiling models for a huge database be very slow?
|
#### Won't compiling models for a huge database be very slow?
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package bdb
|
package bdb
|
||||||
|
|
||||||
import "github.com/vattle/sqlboiler/strmangle"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vattle/sqlboiler/strmangle"
|
||||||
|
)
|
||||||
|
|
||||||
// Column holds information about a database column.
|
// Column holds information about a database column.
|
||||||
// Types are Go types, converted by TranslateColumnType.
|
// Types are Go types, converted by TranslateColumnType.
|
||||||
|
@ -54,3 +58,16 @@ func FilterColumnsByDefault(defaults bool, columns []Column) []Column {
|
||||||
|
|
||||||
return cols
|
return cols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterColumnsByEnum generates the list of columns that are enum values.
|
||||||
|
func FilterColumnsByEnum(columns []Column) []Column {
|
||||||
|
var cols []Column
|
||||||
|
|
||||||
|
for _, c := range columns {
|
||||||
|
if strings.HasPrefix(c.DBType, "enum") {
|
||||||
|
cols = append(cols, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols
|
||||||
|
}
|
||||||
|
|
|
@ -66,3 +66,23 @@ func TestFilterColumnsByDefault(t *testing.T) {
|
||||||
t.Errorf("Invalid result: %#v", res)
|
t.Errorf("Invalid result: %#v", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilterColumnsByEnum(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cols := []Column{
|
||||||
|
{Name: "col1", DBType: "enum('hello')"},
|
||||||
|
{Name: "col2", DBType: "enum('hello','there')"},
|
||||||
|
{Name: "col3", DBType: "enum"},
|
||||||
|
{Name: "col4", DBType: ""},
|
||||||
|
{Name: "col5", DBType: "int"},
|
||||||
|
}
|
||||||
|
|
||||||
|
res := FilterColumnsByEnum(cols)
|
||||||
|
if res[0].Name != `col1` {
|
||||||
|
t.Errorf("Invalid result: %#v", res)
|
||||||
|
}
|
||||||
|
if res[1].Name != `col2` {
|
||||||
|
t.Errorf("Invalid result: %#v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -121,7 +121,11 @@ func (m *MySQLDriver) Columns(schema, tableName string) ([]bdb.Column, error) {
|
||||||
var columns []bdb.Column
|
var columns []bdb.Column
|
||||||
|
|
||||||
rows, err := m.dbConn.Query(`
|
rows, err := m.dbConn.Query(`
|
||||||
select column_name, data_type, if(extra = 'auto_increment','auto_increment', column_default), is_nullable,
|
select
|
||||||
|
c.column_name,
|
||||||
|
if(c.data_type = 'enum', c.column_type, c.data_type),
|
||||||
|
if(extra = 'auto_increment','auto_increment', c.column_default),
|
||||||
|
c.is_nullable = 'YES',
|
||||||
exists (
|
exists (
|
||||||
select c.column_name
|
select c.column_name
|
||||||
from information_schema.table_constraints tc
|
from information_schema.table_constraints tc
|
||||||
|
@ -140,24 +144,23 @@ func (m *MySQLDriver) Columns(schema, tableName string) ([]bdb.Column, error) {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var colName, colType, colDefault, nullable string
|
var colName, colType string
|
||||||
var unique bool
|
var nullable, unique bool
|
||||||
var defaultPtr *string
|
var defaultValue *string
|
||||||
if err := rows.Scan(&colName, &colType, &defaultPtr, &nullable, &unique); err != nil {
|
if err := rows.Scan(&colName, &colType, &defaultValue, &nullable, &unique); err != nil {
|
||||||
return nil, errors.Wrapf(err, "unable to scan for table %s", tableName)
|
return nil, errors.Wrapf(err, "unable to scan for table %s", tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if defaultPtr != nil && *defaultPtr != "NULL" {
|
|
||||||
colDefault = *defaultPtr
|
|
||||||
}
|
|
||||||
|
|
||||||
column := bdb.Column{
|
column := bdb.Column{
|
||||||
Name: colName,
|
Name: colName,
|
||||||
DBType: colType,
|
DBType: colType,
|
||||||
Default: colDefault,
|
Nullable: nullable,
|
||||||
Nullable: nullable == "YES",
|
|
||||||
Unique: unique,
|
Unique: unique,
|
||||||
}
|
}
|
||||||
|
if defaultValue != nil && *defaultValue != "NULL" {
|
||||||
|
column.Default = *defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
columns = append(columns, column)
|
columns = append(columns, column)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// Side-effect import sql driver
|
// Side-effect import sql driver
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/vattle/sqlboiler/bdb"
|
"github.com/vattle/sqlboiler/bdb"
|
||||||
|
@ -123,25 +124,55 @@ func (p *PostgresDriver) Columns(schema, tableName string) ([]bdb.Column, error)
|
||||||
var columns []bdb.Column
|
var columns []bdb.Column
|
||||||
|
|
||||||
rows, err := p.dbConn.Query(`
|
rows, err := p.dbConn.Query(`
|
||||||
select column_name, c.data_type, e.data_type, column_default, c.udt_name, is_nullable,
|
select
|
||||||
(select exists(
|
c.column_name,
|
||||||
select 1
|
(
|
||||||
|
case when c.data_type = 'USER-DEFINED' and c.udt_name <> 'hstore'
|
||||||
|
then
|
||||||
|
(
|
||||||
|
select 'enum.' || c.udt_name || '(''' || string_agg(labels.label, ''',''') || ''')'
|
||||||
|
from (
|
||||||
|
select pg_enum.enumlabel as label
|
||||||
|
from pg_enum
|
||||||
|
where pg_enum.enumtypid =
|
||||||
|
(
|
||||||
|
select typelem
|
||||||
|
from pg_type
|
||||||
|
where pg_type.typtype = 'b' and pg_type.typname = ('_' || c.udt_name)
|
||||||
|
limit 1
|
||||||
|
)
|
||||||
|
order by pg_enum.enumsortorder
|
||||||
|
) as labels
|
||||||
|
)
|
||||||
|
else c.data_type
|
||||||
|
end
|
||||||
|
) as column_type,
|
||||||
|
|
||||||
|
c.udt_name,
|
||||||
|
e.data_type as array_type,
|
||||||
|
c.column_default,
|
||||||
|
|
||||||
|
c.is_nullable = 'YES' as is_nullable,
|
||||||
|
(select exists(
|
||||||
|
select 1
|
||||||
from information_schema.constraint_column_usage as ccu
|
from information_schema.constraint_column_usage as ccu
|
||||||
inner join information_schema.table_constraints tc on ccu.constraint_name = tc.constraint_name
|
inner join information_schema.table_constraints tc on ccu.constraint_name = tc.constraint_name
|
||||||
where ccu.table_name = c.table_name and ccu.column_name = c.column_name and tc.constraint_type = 'UNIQUE'
|
where ccu.table_name = c.table_name and ccu.column_name = c.column_name and tc.constraint_type = 'UNIQUE'
|
||||||
)) OR (select exists(
|
)) OR (select exists(
|
||||||
select 1
|
select 1
|
||||||
from
|
from
|
||||||
pg_indexes pgix
|
pg_indexes pgix
|
||||||
inner join pg_class pgc on pgix.indexname = pgc.relname and pgc.relkind = 'i'
|
inner join pg_class pgc on pgix.indexname = pgc.relname and pgc.relkind = 'i'
|
||||||
inner join pg_index pgi on pgi.indexrelid = pgc.oid
|
inner join pg_index pgi on pgi.indexrelid = pgc.oid
|
||||||
inner join pg_attribute pga on pga.attrelid = pgi.indrelid and pga.attnum = ANY(pgi.indkey)
|
inner join pg_attribute pga on pga.attrelid = pgi.indrelid and pga.attnum = ANY(pgi.indkey)
|
||||||
where
|
where
|
||||||
pgix.schemaname = $1 and pgix.tablename = c.table_name and pga.attname = c.column_name and pgi.indisunique = true
|
pgix.schemaname = $1 and pgix.tablename = c.table_name and pga.attname = c.column_name and pgi.indisunique = true
|
||||||
)) as is_unique
|
)) as is_unique
|
||||||
from information_schema.columns as c LEFT JOIN information_schema.element_types e
|
|
||||||
ON ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier)
|
from information_schema.columns as c
|
||||||
= (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))
|
left join information_schema.element_types e
|
||||||
|
on ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier)
|
||||||
|
= (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))
|
||||||
where c.table_name=$2 and c.table_schema = $1;
|
where c.table_name=$2 and c.table_schema = $1;
|
||||||
`, schema, tableName)
|
`, schema, tableName)
|
||||||
|
|
||||||
|
@ -151,29 +182,25 @@ func (p *PostgresDriver) Columns(schema, tableName string) ([]bdb.Column, error)
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var colName, udtName, colType, colDefault, nullable string
|
var colName, colType, udtName string
|
||||||
var elementType *string
|
var defaultValue, arrayType *string
|
||||||
var unique bool
|
var nullable, unique bool
|
||||||
var defaultPtr *string
|
if err := rows.Scan(&colName, &colType, &udtName, &arrayType, &defaultValue, &nullable, &unique); err != nil {
|
||||||
if err := rows.Scan(&colName, &colType, &elementType, &defaultPtr, &udtName, &nullable, &unique); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "unable to scan for table %s", tableName)
|
return nil, errors.Wrapf(err, "unable to scan for table %s", tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if defaultPtr == nil {
|
|
||||||
colDefault = ""
|
|
||||||
} else {
|
|
||||||
colDefault = *defaultPtr
|
|
||||||
}
|
|
||||||
|
|
||||||
column := bdb.Column{
|
column := bdb.Column{
|
||||||
Name: colName,
|
Name: colName,
|
||||||
DBType: colType,
|
DBType: colType,
|
||||||
ArrType: elementType,
|
ArrType: arrayType,
|
||||||
UDTName: udtName,
|
UDTName: udtName,
|
||||||
Default: colDefault,
|
Nullable: nullable,
|
||||||
Nullable: nullable == "YES",
|
|
||||||
Unique: unique,
|
Unique: unique,
|
||||||
}
|
}
|
||||||
|
if defaultValue != nil {
|
||||||
|
column.Default = *defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
columns = append(columns, column)
|
columns = append(columns, column)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,6 +317,8 @@ func (p *PostgresDriver) TranslateColumnType(c bdb.Column) bdb.Column {
|
||||||
c.Type = "null.Float32"
|
c.Type = "null.Float32"
|
||||||
case "bit", "interval", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
|
case "bit", "interval", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
|
||||||
c.Type = "null.String"
|
c.Type = "null.String"
|
||||||
|
case `"char"`:
|
||||||
|
c.Type = "null.Byte"
|
||||||
case "bytea":
|
case "bytea":
|
||||||
c.Type = "null.Bytes"
|
c.Type = "null.Bytes"
|
||||||
case "json", "jsonb":
|
case "json", "jsonb":
|
||||||
|
@ -330,6 +359,8 @@ func (p *PostgresDriver) TranslateColumnType(c bdb.Column) bdb.Column {
|
||||||
c.Type = "float32"
|
c.Type = "float32"
|
||||||
case "bit", "interval", "uuint", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
|
case "bit", "interval", "uuint", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
|
||||||
c.Type = "string"
|
c.Type = "string"
|
||||||
|
case `"char"`:
|
||||||
|
c.Type = "types.Byte"
|
||||||
case "json", "jsonb":
|
case "json", "jsonb":
|
||||||
c.Type = "types.JSON"
|
c.Type = "types.JSON"
|
||||||
case "bytea":
|
case "bytea":
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
test:
|
test:
|
||||||
pre:
|
pre:
|
||||||
- mkdir -p /home/ubuntu/.go_workspace/src/github.com/jstemmer
|
- mkdir -p /home/ubuntu/.go_workspace/src/github.com/jstemmer
|
||||||
- git clone git@github.com:nullbio/go-junit-report.git /home/ubuntu/.go_workspace/src/github.com/jstemmer/go-junit-report
|
- go get -u github.com/jstemmer/go-junit-report
|
||||||
- go install github.com/jstemmer/go-junit-report
|
|
||||||
- echo -e "[postgres]\nhost=\"localhost\"\nport=5432\nuser=\"ubuntu\"\ndbname=\"sqlboiler\"\n[mysql]\nhost=\"localhost\"\nport=3306\nuser=\"ubuntu\"\ndbname=\"sqlboiler\"\nsslmode=\"false\"" > sqlboiler.toml
|
- echo -e "[postgres]\nhost=\"localhost\"\nport=5432\nuser=\"ubuntu\"\ndbname=\"sqlboiler\"\n[mysql]\nhost=\"localhost\"\nport=3306\nuser=\"ubuntu\"\ndbname=\"sqlboiler\"\nsslmode=\"false\"" > sqlboiler.toml
|
||||||
- createdb -U ubuntu sqlboiler
|
- createdb -U ubuntu sqlboiler
|
||||||
- psql -U ubuntu sqlboiler < ./testdata/postgres_test_schema.sql
|
- psql -U ubuntu sqlboiler < ./testdata/postgres_test_schema.sql
|
||||||
|
|
34
imports.go
34
imports.go
|
@ -274,55 +274,55 @@ var defaultTestMainImports = map[string]imports{
|
||||||
// TranslateColumnType to see the type assignments.
|
// TranslateColumnType to see the type assignments.
|
||||||
var importsBasedOnType = map[string]imports{
|
var importsBasedOnType = map[string]imports{
|
||||||
"null.Float32": {
|
"null.Float32": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Float64": {
|
"null.Float64": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Int": {
|
"null.Int": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Int8": {
|
"null.Int8": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Int16": {
|
"null.Int16": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Int32": {
|
"null.Int32": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Int64": {
|
"null.Int64": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Uint": {
|
"null.Uint": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Uint8": {
|
"null.Uint8": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Uint16": {
|
"null.Uint16": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Uint32": {
|
"null.Uint32": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Uint64": {
|
"null.Uint64": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.String": {
|
"null.String": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Bool": {
|
"null.Bool": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Time": {
|
"null.Time": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.JSON": {
|
"null.JSON": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"null.Bytes": {
|
"null.Bytes": {
|
||||||
thirdParty: importList{`"gopkg.in/nullbio/null.v5"`},
|
thirdParty: importList{`"gopkg.in/nullbio/null.v6"`},
|
||||||
},
|
},
|
||||||
"time.Time": {
|
"time.Time": {
|
||||||
standard: importList{`"time"`},
|
standard: importList{`"time"`},
|
||||||
|
|
|
@ -75,7 +75,7 @@ func TestCombineTypeImports(t *testing.T) {
|
||||||
},
|
},
|
||||||
thirdParty: importList{
|
thirdParty: importList{
|
||||||
`"github.com/vattle/sqlboiler/boil"`,
|
`"github.com/vattle/sqlboiler/boil"`,
|
||||||
`"gopkg.in/nullbio/null.v5"`,
|
`"gopkg.in/nullbio/null.v6"`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ func TestCombineTypeImports(t *testing.T) {
|
||||||
},
|
},
|
||||||
thirdParty: importList{
|
thirdParty: importList{
|
||||||
`"github.com/vattle/sqlboiler/boil"`,
|
`"github.com/vattle/sqlboiler/boil"`,
|
||||||
`"gopkg.in/nullbio/null.v5"`,
|
`"gopkg.in/nullbio/null.v6"`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ func TestCombineImports(t *testing.T) {
|
||||||
|
|
||||||
a := imports{
|
a := imports{
|
||||||
standard: importList{"fmt"},
|
standard: importList{"fmt"},
|
||||||
thirdParty: importList{"github.com/vattle/sqlboiler", "gopkg.in/nullbio/null.v5"},
|
thirdParty: importList{"github.com/vattle/sqlboiler", "gopkg.in/nullbio/null.v6"},
|
||||||
}
|
}
|
||||||
b := imports{
|
b := imports{
|
||||||
standard: importList{"os"},
|
standard: importList{"os"},
|
||||||
|
@ -136,8 +136,8 @@ func TestCombineImports(t *testing.T) {
|
||||||
if c.standard[0] != "fmt" && c.standard[1] != "os" {
|
if c.standard[0] != "fmt" && c.standard[1] != "os" {
|
||||||
t.Errorf("Wanted: fmt, os got: %#v", c.standard)
|
t.Errorf("Wanted: fmt, os got: %#v", c.standard)
|
||||||
}
|
}
|
||||||
if c.thirdParty[0] != "github.com/vattle/sqlboiler" && c.thirdParty[1] != "gopkg.in/nullbio/null.v5" {
|
if c.thirdParty[0] != "github.com/vattle/sqlboiler" && c.thirdParty[1] != "gopkg.in/nullbio/null.v6" {
|
||||||
t.Errorf("Wanted: github.com/vattle/sqlboiler, gopkg.in/nullbio/null.v5 got: %#v", c.thirdParty)
|
t.Errorf("Wanted: github.com/vattle/sqlboiler, gopkg.in/nullbio/null.v6 got: %#v", c.thirdParty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
main.go
1
main.go
|
@ -125,6 +125,7 @@ func preRun(cmd *cobra.Command, args []string) error {
|
||||||
OutFolder: viper.GetString("output"),
|
OutFolder: viper.GetString("output"),
|
||||||
Schema: viper.GetString("schema"),
|
Schema: viper.GetString("schema"),
|
||||||
PkgName: viper.GetString("pkgname"),
|
PkgName: viper.GetString("pkgname"),
|
||||||
|
BaseDir: viper.GetString("basedir"),
|
||||||
Debug: viper.GetBool("debug"),
|
Debug: viper.GetBool("debug"),
|
||||||
NoTests: viper.GetBool("no-tests"),
|
NoTests: viper.GetBool("no-tests"),
|
||||||
NoHooks: viper.GetBool("no-hooks"),
|
NoHooks: viper.GetBool("no-hooks"),
|
||||||
|
|
|
@ -71,7 +71,7 @@ func eagerLoad(exec boil.Executor, toLoad []string, obj interface{}, bkind bindK
|
||||||
// - t is not considered here, and is always passed nil. The function exists on a loaded
|
// - t is not considered here, and is always passed nil. The function exists on a loaded
|
||||||
// struct to avoid a circular dependency with boil, and the receiver is ignored.
|
// struct to avoid a circular dependency with boil, and the receiver is ignored.
|
||||||
// - exec is used to perform additional queries that might be required for loading the relationships.
|
// - exec is used to perform additional queries that might be required for loading the relationships.
|
||||||
// - singular is passed in to identify whether or not this was a single object
|
// - bkind is passed in to identify whether or not this was a single object
|
||||||
// or a slice that must be loaded into.
|
// or a slice that must be loaded into.
|
||||||
// - obj is the object or slice of objects, always of the type *obj or *[]*obj as per bind.
|
// - obj is the object or slice of objects, always of the type *obj or *[]*obj as per bind.
|
||||||
//
|
//
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/nullbio/null.v5"
|
null "gopkg.in/nullbio/null.v6"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testObj struct {
|
type testObj struct {
|
||||||
|
|
|
@ -4,6 +4,7 @@ package randomize
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/nullbio/null.v5"
|
null "gopkg.in/nullbio/null.v6"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
|
@ -34,6 +35,7 @@ var (
|
||||||
typeNullUint32 = reflect.TypeOf(null.Uint32{})
|
typeNullUint32 = reflect.TypeOf(null.Uint32{})
|
||||||
typeNullUint64 = reflect.TypeOf(null.Uint64{})
|
typeNullUint64 = reflect.TypeOf(null.Uint64{})
|
||||||
typeNullString = reflect.TypeOf(null.String{})
|
typeNullString = reflect.TypeOf(null.String{})
|
||||||
|
typeNullByte = reflect.TypeOf(null.Byte{})
|
||||||
typeNullBool = reflect.TypeOf(null.Bool{})
|
typeNullBool = reflect.TypeOf(null.Bool{})
|
||||||
typeNullTime = reflect.TypeOf(null.Time{})
|
typeNullTime = reflect.TypeOf(null.Time{})
|
||||||
typeNullBytes = reflect.TypeOf(null.Bytes{})
|
typeNullBytes = reflect.TypeOf(null.Bytes{})
|
||||||
|
@ -156,9 +158,26 @@ func randDate(s *Seed) time.Time {
|
||||||
// If canBeNull is true:
|
// If canBeNull is true:
|
||||||
// The value has the possibility of being null or non-zero at random.
|
// The value has the possibility of being null or non-zero at random.
|
||||||
func randomizeField(s *Seed, field reflect.Value, fieldType string, canBeNull bool) error {
|
func randomizeField(s *Seed, field reflect.Value, fieldType string, canBeNull bool) error {
|
||||||
|
|
||||||
kind := field.Kind()
|
kind := field.Kind()
|
||||||
typ := field.Type()
|
typ := field.Type()
|
||||||
|
|
||||||
|
if strings.HasPrefix(fieldType, "enum") {
|
||||||
|
enum, err := randEnumValue(fieldType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == reflect.Struct {
|
||||||
|
val := null.NewString(enum, rand.Intn(1) == 0)
|
||||||
|
field.Set(reflect.ValueOf(val))
|
||||||
|
} else {
|
||||||
|
field.Set(reflect.ValueOf(enum))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var value interface{}
|
var value interface{}
|
||||||
var isNull bool
|
var isNull bool
|
||||||
|
|
||||||
|
@ -341,7 +360,7 @@ func randomizeField(s *Seed, field reflect.Value, fieldType string, canBeNull bo
|
||||||
// only get zero values for non byte slices
|
// only get zero values for non byte slices
|
||||||
// to stop mysql from being a jerk
|
// to stop mysql from being a jerk
|
||||||
if isNull && kind != reflect.Slice {
|
if isNull && kind != reflect.Slice {
|
||||||
value = getVariableZeroValue(s, kind)
|
value = getVariableZeroValue(s, kind, typ)
|
||||||
} else {
|
} else {
|
||||||
value = getVariableRandValue(s, kind, typ)
|
value = getVariableRandValue(s, kind, typ)
|
||||||
}
|
}
|
||||||
|
@ -457,6 +476,8 @@ func getStructNullValue(s *Seed, typ reflect.Type) interface{} {
|
||||||
return null.NewUint64(0, false)
|
return null.NewUint64(0, false)
|
||||||
case typeNullBytes:
|
case typeNullBytes:
|
||||||
return null.NewBytes(nil, false)
|
return null.NewBytes(nil, false)
|
||||||
|
case typeNullByte:
|
||||||
|
return null.NewByte(byte(0), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -501,13 +522,21 @@ func getStructRandValue(s *Seed, typ reflect.Type) interface{} {
|
||||||
return null.NewUint64(uint64(s.nextInt()), true)
|
return null.NewUint64(uint64(s.nextInt()), true)
|
||||||
case typeNullBytes:
|
case typeNullBytes:
|
||||||
return null.NewBytes(randByteSlice(s, 1), true)
|
return null.NewBytes(randByteSlice(s, 1), true)
|
||||||
|
case typeNullByte:
|
||||||
|
return null.NewByte(byte(rand.Intn(125-65)+65), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVariableZeroValue for the matching type.
|
// getVariableZeroValue for the matching type.
|
||||||
func getVariableZeroValue(s *Seed, kind reflect.Kind) interface{} {
|
func getVariableZeroValue(s *Seed, kind reflect.Kind, typ reflect.Type) interface{} {
|
||||||
|
switch typ.String() {
|
||||||
|
case "types.Byte":
|
||||||
|
// Decimal 65 is 'A'. 0 is not a valid UTF8, so cannot use a zero value here.
|
||||||
|
return types.Byte(65)
|
||||||
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Float32:
|
case reflect.Float32:
|
||||||
return float32(0)
|
return float32(0)
|
||||||
|
@ -548,6 +577,11 @@ func getVariableZeroValue(s *Seed, kind reflect.Kind) interface{} {
|
||||||
// The randomness is really an incrementation of the global seed,
|
// The randomness is really an incrementation of the global seed,
|
||||||
// this is done to avoid duplicate key violations.
|
// this is done to avoid duplicate key violations.
|
||||||
func getVariableRandValue(s *Seed, kind reflect.Kind, typ reflect.Type) interface{} {
|
func getVariableRandValue(s *Seed, kind reflect.Kind, typ reflect.Type) interface{} {
|
||||||
|
switch typ.String() {
|
||||||
|
case "types.Byte":
|
||||||
|
return types.Byte(rand.Intn(125-65) + 65)
|
||||||
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Float32:
|
case reflect.Float32:
|
||||||
return float32(float32(s.nextInt()%10)/10.0 + float32(s.nextInt()%10))
|
return float32(float32(s.nextInt()%10)/10.0 + float32(s.nextInt()%10))
|
||||||
|
@ -587,3 +621,12 @@ func getVariableRandValue(s *Seed, kind reflect.Kind, typ reflect.Type) interfac
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randEnumValue(enum string) (string, error) {
|
||||||
|
vals := strmangle.ParseEnumVals(enum)
|
||||||
|
if vals == nil || len(vals) == 0 {
|
||||||
|
return "", fmt.Errorf("unable to parse enum string: %s", enum)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vals[rand.Intn(len(vals)-1)], nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/nullbio/null.v5"
|
null "gopkg.in/nullbio/null.v6"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRandomizeStruct(t *testing.T) {
|
func TestRandomizeStruct(t *testing.T) {
|
||||||
|
@ -144,3 +144,28 @@ func TestRandomizeField(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRandEnumValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
enum1 := "enum.workday('monday','tuesday')"
|
||||||
|
enum2 := "enum('monday','tuesday')"
|
||||||
|
|
||||||
|
r1, err := randEnumValue(enum1)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 != "monday" && r1 != "tuesday" {
|
||||||
|
t.Errorf("Expected monday or tueday, got: %q", r1)
|
||||||
|
}
|
||||||
|
|
||||||
|
r2, err := randEnumValue(enum2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r2 != "monday" && r2 != "tuesday" {
|
||||||
|
t.Errorf("Expected monday or tueday, got: %q", r2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,15 +16,31 @@ import (
|
||||||
var (
|
var (
|
||||||
idAlphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
idAlphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||||
smartQuoteRgx = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(\."?[_a-z][_a-z0-9]*"?)*(\.\*)?$`)
|
smartQuoteRgx = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(\."?[_a-z][_a-z0-9]*"?)*(\.\*)?$`)
|
||||||
|
|
||||||
|
rgxEnum = regexp.MustCompile(`^enum(\.[a-z_]+)?\((,?'[^']+')+\)$`)
|
||||||
|
rgxEnumIsOK = regexp.MustCompile(`^(?i)[a-z][a-z0-9_]*$`)
|
||||||
|
rgxEnumShouldTitle = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
var uppercaseWords = map[string]struct{}{
|
var uppercaseWords = map[string]struct{}{
|
||||||
"guid": {},
|
"acl": {},
|
||||||
"id": {},
|
"api": {},
|
||||||
"ip": {},
|
"ascii": {},
|
||||||
"uid": {},
|
"cpu": {},
|
||||||
"uuid": {},
|
"eof": {},
|
||||||
"json": {},
|
"guid": {},
|
||||||
|
"id": {},
|
||||||
|
"ip": {},
|
||||||
|
"json": {},
|
||||||
|
"ram": {},
|
||||||
|
"sla": {},
|
||||||
|
"udp": {},
|
||||||
|
"ui": {},
|
||||||
|
"uid": {},
|
||||||
|
"uuid": {},
|
||||||
|
"uri": {},
|
||||||
|
"url": {},
|
||||||
|
"utf8": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -364,7 +380,7 @@ func MakeStringMap(types map[string]string) string {
|
||||||
c := 0
|
c := 0
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
v := types[k]
|
v := types[k]
|
||||||
buf.WriteString(fmt.Sprintf(`"%s": "%s"`, k, v))
|
buf.WriteString(fmt.Sprintf("`%s`: `%s`", k, v))
|
||||||
if c < len(types)-1 {
|
if c < len(types)-1 {
|
||||||
buf.WriteString(", ")
|
buf.WriteString(", ")
|
||||||
}
|
}
|
||||||
|
@ -562,3 +578,55 @@ func GenerateIgnoreTags(tags []string) string {
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseEnumVals returns the values from an enum string
|
||||||
|
//
|
||||||
|
// Postgres and MySQL drivers return different values
|
||||||
|
// psql: enum.enum_name('values'...)
|
||||||
|
// mysql: enum('values'...)
|
||||||
|
func ParseEnumVals(s string) []string {
|
||||||
|
if !rgxEnum.MatchString(s) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex := strings.IndexByte(s, '(')
|
||||||
|
s = s[startIndex+2 : len(s)-2]
|
||||||
|
return strings.Split(s, "','")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEnumName returns the name portion of an enum if it exists
|
||||||
|
//
|
||||||
|
// Postgres and MySQL drivers return different values
|
||||||
|
// psql: enum.enum_name('values'...)
|
||||||
|
// mysql: enum('values'...)
|
||||||
|
// In the case of mysql, the name will never return anything
|
||||||
|
func ParseEnumName(s string) string {
|
||||||
|
if !rgxEnum.MatchString(s) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
endIndex := strings.IndexByte(s, '(')
|
||||||
|
s = s[:endIndex]
|
||||||
|
startIndex := strings.IndexByte(s, '.')
|
||||||
|
if startIndex < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[startIndex+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnumNormal checks a set of eval values to see if they're "normal"
|
||||||
|
func IsEnumNormal(values []string) bool {
|
||||||
|
for _, v := range values {
|
||||||
|
if !rgxEnumIsOK.MatchString(v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldTitleCaseEnum checks a value to see if it's title-case-able
|
||||||
|
func ShouldTitleCaseEnum(value string) bool {
|
||||||
|
return rgxEnumShouldTitle.MatchString(value)
|
||||||
|
}
|
||||||
|
|
|
@ -291,8 +291,8 @@ func TestMakeStringMap(t *testing.T) {
|
||||||
|
|
||||||
r = MakeStringMap(m)
|
r = MakeStringMap(m)
|
||||||
|
|
||||||
e1 := `"TestOne": "interval", "TestTwo": "integer"`
|
e1 := "`TestOne`: `interval`, `TestTwo`: `integer`"
|
||||||
e2 := `"TestTwo": "integer", "TestOne": "interval"`
|
e2 := "`TestTwo`: `integer`, `TestOne`: `interval`"
|
||||||
|
|
||||||
if r != e1 && r != e2 {
|
if r != e1 && r != e2 {
|
||||||
t.Errorf("Got %s", r)
|
t.Errorf("Got %s", r)
|
||||||
|
@ -513,3 +513,70 @@ func TestGenerateIgnoreTags(t *testing.T) {
|
||||||
t.Errorf("expected %s, got %s", exp, tags)
|
t.Errorf("expected %s, got %s", exp, tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEnum(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Enum string
|
||||||
|
Name string
|
||||||
|
Vals []string
|
||||||
|
}{
|
||||||
|
{"enum('one')", "", []string{"one"}},
|
||||||
|
{"enum('one','two')", "", []string{"one", "two"}},
|
||||||
|
{"enum.working('one')", "working", []string{"one"}},
|
||||||
|
{"enum.wor_king('one','two')", "wor_king", []string{"one", "two"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
name := ParseEnumName(test.Enum)
|
||||||
|
vals := ParseEnumVals(test.Enum)
|
||||||
|
if name != test.Name {
|
||||||
|
t.Errorf("%d) name was wrong, want: %s got: %s (%s)", i, test.Name, name, test.Enum)
|
||||||
|
}
|
||||||
|
for j, v := range test.Vals {
|
||||||
|
if v != vals[j] {
|
||||||
|
t.Errorf("%d.%d) value was wrong, want: %s got: %s (%s)", i, j, v, vals[j], test.Enum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsEnumNormal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Vals []string
|
||||||
|
Ok bool
|
||||||
|
}{
|
||||||
|
{[]string{"o1ne", "two2"}, true},
|
||||||
|
{[]string{"one", "t#wo2"}, false},
|
||||||
|
{[]string{"1one", "two2"}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
if got := IsEnumNormal(test.Vals); got != test.Ok {
|
||||||
|
t.Errorf("%d) want: %t got: %t, %#v", i, test.Ok, got, test.Vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldTitleCaseEnum(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Val string
|
||||||
|
Ok bool
|
||||||
|
}{
|
||||||
|
{"hello_there0", true},
|
||||||
|
{"hEllo", false},
|
||||||
|
{"_hello", false},
|
||||||
|
{"0hello", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
if got := ShouldTitleCaseEnum(test.Val); got != test.Ok {
|
||||||
|
t.Errorf("%d) want: %t got: %t, %v", i, test.Ok, got, test.Val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
32
templates.go
32
templates.go
|
@ -121,6 +121,28 @@ func loadTemplate(dir string, filename string) (*template.Template, error) {
|
||||||
return tpl.Lookup(filename), err
|
return tpl.Lookup(filename), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set is to stop duplication from named enums, allowing a template loop
|
||||||
|
// to keep some state
|
||||||
|
type once map[string]struct{}
|
||||||
|
|
||||||
|
func newOnce() once {
|
||||||
|
return make(once)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o once) Has(s string) bool {
|
||||||
|
_, ok := o[s]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o once) Put(s string) bool {
|
||||||
|
if _, ok := o[s]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
o[s] = struct{}{}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// templateStringMappers are placed into the data to make it easy to use the
|
// templateStringMappers are placed into the data to make it easy to use the
|
||||||
// stringMap function.
|
// stringMap function.
|
||||||
var templateStringMappers = map[string]func(string) string{
|
var templateStringMappers = map[string]func(string) string{
|
||||||
|
@ -157,6 +179,15 @@ var templateFunctions = template.FuncMap{
|
||||||
"generateTags": strmangle.GenerateTags,
|
"generateTags": strmangle.GenerateTags,
|
||||||
"generateIgnoreTags": strmangle.GenerateIgnoreTags,
|
"generateIgnoreTags": strmangle.GenerateIgnoreTags,
|
||||||
|
|
||||||
|
// Enum ops
|
||||||
|
"parseEnumName": strmangle.ParseEnumName,
|
||||||
|
"parseEnumVals": strmangle.ParseEnumVals,
|
||||||
|
"isEnumNormal": strmangle.IsEnumNormal,
|
||||||
|
"shouldTitleCaseEnum": strmangle.ShouldTitleCaseEnum,
|
||||||
|
"onceNew": newOnce,
|
||||||
|
"oncePut": once.Put,
|
||||||
|
"onceHas": once.Has,
|
||||||
|
|
||||||
// String Map ops
|
// String Map ops
|
||||||
"makeStringMap": strmangle.MakeStringMap,
|
"makeStringMap": strmangle.MakeStringMap,
|
||||||
|
|
||||||
|
@ -173,6 +204,7 @@ var templateFunctions = template.FuncMap{
|
||||||
|
|
||||||
// dbdrivers ops
|
// dbdrivers ops
|
||||||
"filterColumnsByDefault": bdb.FilterColumnsByDefault,
|
"filterColumnsByDefault": bdb.FilterColumnsByDefault,
|
||||||
|
"filterColumnsByEnum": bdb.FilterColumnsByEnum,
|
||||||
"sqlColDefinitions": bdb.SQLColDefinitions,
|
"sqlColDefinitions": bdb.SQLColDefinitions,
|
||||||
"columnNames": bdb.ColumnNames,
|
"columnNames": bdb.ColumnNames,
|
||||||
"columnDBTypes": bdb.ColumnDBTypes,
|
"columnDBTypes": bdb.ColumnDBTypes,
|
||||||
|
|
|
@ -22,11 +22,15 @@ func ({{$varNameSingular}}L) Load{{$txt.Function.Name}}(e boil.Executor, singula
|
||||||
|
|
||||||
args := make([]interface{}, count)
|
args := make([]interface{}, count)
|
||||||
if singular {
|
if singular {
|
||||||
object.R = &{{$varNameSingular}}R{}
|
if object.R == nil {
|
||||||
|
object.R = &{{$varNameSingular}}R{}
|
||||||
|
}
|
||||||
args[0] = object.{{$txt.LocalTable.ColumnNameGo}}
|
args[0] = object.{{$txt.LocalTable.ColumnNameGo}}
|
||||||
} else {
|
} else {
|
||||||
for i, obj := range slice {
|
for i, obj := range slice {
|
||||||
obj.R = &{{$varNameSingular}}R{}
|
if obj.R == nil {
|
||||||
|
obj.R = &{{$varNameSingular}}R{}
|
||||||
|
}
|
||||||
args[i] = obj.{{$txt.LocalTable.ColumnNameGo}}
|
args[i] = obj.{{$txt.LocalTable.ColumnNameGo}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,15 @@ func ({{$varNameSingular}}L) Load{{$txt.Function.Name}}(e boil.Executor, singula
|
||||||
|
|
||||||
args := make([]interface{}, count)
|
args := make([]interface{}, count)
|
||||||
if singular {
|
if singular {
|
||||||
object.R = &{{$varNameSingular}}R{}
|
if object.R == nil {
|
||||||
|
object.R = &{{$varNameSingular}}R{}
|
||||||
|
}
|
||||||
args[0] = object.{{$txt.LocalTable.ColumnNameGo}}
|
args[0] = object.{{$txt.LocalTable.ColumnNameGo}}
|
||||||
} else {
|
} else {
|
||||||
for i, obj := range slice {
|
for i, obj := range slice {
|
||||||
obj.R = &{{$varNameSingular}}R{}
|
if obj.R == nil {
|
||||||
|
obj.R = &{{$varNameSingular}}R{}
|
||||||
|
}
|
||||||
args[i] = obj.{{$txt.LocalTable.ColumnNameGo}}
|
args[i] = obj.{{$txt.LocalTable.ColumnNameGo}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,15 @@ func ({{$varNameSingular}}L) Load{{$txt.Function.Name}}(e boil.Executor, singula
|
||||||
|
|
||||||
args := make([]interface{}, count)
|
args := make([]interface{}, count)
|
||||||
if singular {
|
if singular {
|
||||||
object.R = &{{$varNameSingular}}R{}
|
if object.R == nil {
|
||||||
|
object.R = &{{$varNameSingular}}R{}
|
||||||
|
}
|
||||||
args[0] = object.{{.Column | titleCase}}
|
args[0] = object.{{.Column | titleCase}}
|
||||||
} else {
|
} else {
|
||||||
for i, obj := range slice {
|
for i, obj := range slice {
|
||||||
obj.R = &{{$varNameSingular}}R{}
|
if obj.R == nil {
|
||||||
|
obj.R = &{{$varNameSingular}}R{}
|
||||||
|
}
|
||||||
args[i] = obj.{{.Column | titleCase}}
|
args[i] = obj.{{.Column | titleCase}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,3 +35,52 @@ func makeCacheKey(wl, nzDefaults []string) string {
|
||||||
strmangle.PutBuffer(buf)
|
strmangle.PutBuffer(buf)
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
The following is a little bit of black magic and deserves some explanation
|
||||||
|
|
||||||
|
Because postgres and mysql define enums completely differently (one at the
|
||||||
|
database level as a custom datatype, and one at the table column level as
|
||||||
|
a unique thing per table)... There's a chance the enum is named (postgres)
|
||||||
|
and not (mysql). So we can't do this per table so this code is here.
|
||||||
|
|
||||||
|
We loop through each table and column looking for enums. If it's named, we
|
||||||
|
then use some disgusting magic to write state during the template compile to
|
||||||
|
the "once" map. This lets named enums only be defined once if they're referenced
|
||||||
|
multiple times in many (or even the same) tables.
|
||||||
|
|
||||||
|
Then we check if all it's values are normal, if they are we create the enum
|
||||||
|
output, if not we output a friendly error message as a comment to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
Postgres output looks like: EnumNameEnumValue = "enumvalue"
|
||||||
|
MySQL output looks like: TableNameColNameEnumValue = "enumvalue"
|
||||||
|
|
||||||
|
It only titlecases the EnumValue portion if it's snake-cased.
|
||||||
|
*/}}
|
||||||
|
{{$dot := . -}}
|
||||||
|
{{$once := onceNew}}
|
||||||
|
{{- range $table := .Tables -}}
|
||||||
|
{{- range $col := $table.Columns | filterColumnsByEnum -}}
|
||||||
|
{{- $name := parseEnumName $col.DBType -}}
|
||||||
|
{{- $vals := parseEnumVals $col.DBType -}}
|
||||||
|
{{- $isNamed := ne (len $name) 0}}
|
||||||
|
{{- if and $isNamed (onceHas $once $name) -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- if $isNamed -}}
|
||||||
|
{{$_ := oncePut $once $name}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if and (gt (len $vals) 0) (isEnumNormal $vals)}}
|
||||||
|
// Enum values for {{if $isNamed}}{{$name}}{{else}}{{$table.Name}}.{{$col.Name}}{{end}}
|
||||||
|
const (
|
||||||
|
{{- range $val := $vals -}}
|
||||||
|
{{- if $isNamed}}{{titleCase $name}}{{else}}{{titleCase $table.Name}}{{titleCase $col.Name}}{{end -}}
|
||||||
|
{{if shouldTitleCaseEnum $val}}{{titleCase $val}}{{else}}{{$val}}{{end}} = "{{$val}}"
|
||||||
|
{{end -}}
|
||||||
|
)
|
||||||
|
{{- else}}
|
||||||
|
// Enum values for {{if $isNamed}}{{$name}}{{else}}{{$table.Name}}.{{$col.Name}}{{end}} are not proper Go identifiers, cannot emit constants
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
20
testdata/mysql_test_schema.sql
vendored
20
testdata/mysql_test_schema.sql
vendored
|
@ -1,3 +1,23 @@
|
||||||
|
CREATE TABLE event_one (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
day enum('monday','tuesday','wednesday')
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE event_two (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
face enum('happy','sad','bitter')
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE event_three (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
face enum('happy','sad','bitter'),
|
||||||
|
mood enum('happy','sad','bitter'),
|
||||||
|
day enum('monday','tuesday','wednesday')
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE magic (
|
CREATE TABLE magic (
|
||||||
id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||||
id_two int NOT NULL,
|
id_two int NOT NULL,
|
||||||
|
|
47
testdata/postgres_test_schema.sql
vendored
47
testdata/postgres_test_schema.sql
vendored
|
@ -1,3 +1,33 @@
|
||||||
|
CREATE TYPE workday AS ENUM('monday', 'tuesday', 'wednesday', 'thursday', 'friday');
|
||||||
|
CREATE TYPE faceyface AS ENUM('angry', 'hungry', 'bitter');
|
||||||
|
|
||||||
|
CREATE TABLE event_one (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
day workday NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE event_two (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
day workday NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE event_three (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
day workday NOT NULL,
|
||||||
|
face faceyface NOT NULL,
|
||||||
|
thing workday NULL,
|
||||||
|
stuff faceyface NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE facey (
|
||||||
|
id serial PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(255),
|
||||||
|
face faceyface NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE magic (
|
CREATE TABLE magic (
|
||||||
id serial PRIMARY KEY NOT NULL,
|
id serial PRIMARY KEY NOT NULL,
|
||||||
id_two serial NOT NULL,
|
id_two serial NOT NULL,
|
||||||
|
@ -24,6 +54,23 @@ CREATE TABLE magic (
|
||||||
string_ten VARCHAR(1000) NULL DEFAULT '',
|
string_ten VARCHAR(1000) NULL DEFAULT '',
|
||||||
string_eleven VARCHAR(1000) NOT NULL DEFAULT '',
|
string_eleven VARCHAR(1000) NOT NULL DEFAULT '',
|
||||||
|
|
||||||
|
nonbyte_zero CHAR(1),
|
||||||
|
nonbyte_one CHAR(1) NULL,
|
||||||
|
nonbyte_two CHAR(1) NOT NULL,
|
||||||
|
nonbyte_three CHAR(1) NULL DEFAULT 'a',
|
||||||
|
nonbyte_four CHAR(1) NOT NULL DEFAULT 'b',
|
||||||
|
nonbyte_five CHAR(1000),
|
||||||
|
nonbyte_six CHAR(1000) NULL,
|
||||||
|
nonbyte_seven CHAR(1000) NOT NULL,
|
||||||
|
nonbyte_eight CHAR(1000) NULL DEFAULT 'a',
|
||||||
|
nonbyte_nine CHAR(1000) NOT NULL DEFAULT 'b',
|
||||||
|
|
||||||
|
byte_zero "char",
|
||||||
|
byte_one "char" NULL,
|
||||||
|
byte_two "char" NULL DEFAULT 'a',
|
||||||
|
byte_three "char" NOT NULL,
|
||||||
|
byte_four "char" NOT NULL DEFAULT 'b',
|
||||||
|
|
||||||
big_int_zero bigint,
|
big_int_zero bigint,
|
||||||
big_int_one bigint NULL,
|
big_int_one bigint NULL,
|
||||||
big_int_two bigint NOT NULL,
|
big_int_two bigint NOT NULL,
|
||||||
|
|
61
types/byte.go
Normal file
61
types/byte.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Byte is an alias for byte.
|
||||||
|
// Byte implements Marshal and Unmarshal.
|
||||||
|
type Byte byte
|
||||||
|
|
||||||
|
// String output your byte.
|
||||||
|
func (b Byte) String() string {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON sets *b to a copy of data.
|
||||||
|
func (b *Byte) UnmarshalJSON(data []byte) error {
|
||||||
|
if b == nil {
|
||||||
|
return errors.New("json: unmarshal json on nil pointer to byte")
|
||||||
|
}
|
||||||
|
|
||||||
|
var x string
|
||||||
|
if err := json.Unmarshal(data, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(x) > 1 {
|
||||||
|
return errors.New("json: cannot convert to byte, text len is greater than one")
|
||||||
|
}
|
||||||
|
|
||||||
|
*b = Byte(x[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of b.
|
||||||
|
func (b Byte) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte{'"', byte(b), '"'}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns b as a driver.Value.
|
||||||
|
func (b Byte) Value() (driver.Value, error) {
|
||||||
|
return []byte{byte(b)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan stores the src in *b.
|
||||||
|
func (b *Byte) Scan(src interface{}) error {
|
||||||
|
switch src.(type) {
|
||||||
|
case uint8:
|
||||||
|
*b = Byte(src.(uint8))
|
||||||
|
case string:
|
||||||
|
*b = Byte(src.(string)[0])
|
||||||
|
case []byte:
|
||||||
|
*b = Byte(src.([]byte)[0])
|
||||||
|
default:
|
||||||
|
return errors.New("incompatible type for byte")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
74
types/byte_test.go
Normal file
74
types/byte_test.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestByteString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
b := Byte('b')
|
||||||
|
if b.String() != "b" {
|
||||||
|
t.Errorf("Expected %q, got %s", "b", b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteUnmarshal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b Byte
|
||||||
|
err := json.Unmarshal([]byte(`"b"`), &b)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b != 'b' {
|
||||||
|
t.Errorf("Expected %q, got %s", "b", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteMarshal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
b := Byte('b')
|
||||||
|
res, err := json.Marshal(&b)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(res, []byte(`"b"`)) {
|
||||||
|
t.Errorf("expected %s, got %s", `"b"`, b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
b := Byte('b')
|
||||||
|
v, err := b.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal([]byte{byte(b)}, v.([]byte)) {
|
||||||
|
t.Errorf("byte mismatch, %v %v", b, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b Byte
|
||||||
|
|
||||||
|
s := "b"
|
||||||
|
err := b.Scan(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal([]byte{byte(b)}, []byte{'b'}) {
|
||||||
|
t.Errorf("bad []byte: %#v ≠ %#v\n", b, "b")
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func (j *JSON) Marshal(obj interface{}) error {
|
||||||
// UnmarshalJSON sets *j to a copy of data.
|
// UnmarshalJSON sets *j to a copy of data.
|
||||||
func (j *JSON) UnmarshalJSON(data []byte) error {
|
func (j *JSON) UnmarshalJSON(data []byte) error {
|
||||||
if j == nil {
|
if j == nil {
|
||||||
return errors.New("JSON: UnmarshalJSON on nil pointer")
|
return errors.New("json: unmarshal json on nil pointer to json")
|
||||||
}
|
}
|
||||||
|
|
||||||
*j = append((*j)[0:0], data...)
|
*j = append((*j)[0:0], data...)
|
||||||
|
@ -68,7 +68,7 @@ func (j *JSON) Scan(src interface{}) error {
|
||||||
case []byte:
|
case []byte:
|
||||||
source = src.([]byte)
|
source = src.([]byte)
|
||||||
default:
|
default:
|
||||||
return errors.New("Incompatible type for JSON")
|
return errors.New("incompatible type for json")
|
||||||
}
|
}
|
||||||
|
|
||||||
*j = JSON(append((*j)[0:0], source...))
|
*j = JSON(append((*j)[0:0], source...))
|
||||||
|
|
Loading…
Add table
Reference in a new issue