Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
aarondl committed Nov 12, 2016
2 parents 7fc3d63 + 0cd1b61 commit ca748b0
Show file tree
Hide file tree
Showing 25 changed files with 701 additions and 93 deletions.
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ Table of Contents
* [Upsert](#upsert)
* [Reload](#reload)
* [Exists](#exists)
* [Enums](#enums)
* [FAQ](#faq)
* [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)
* [Benchmarks](#benchmarks)
* [Benchmarks](#benchmarks)

## About SQL Boiler

Expand All @@ -97,6 +98,7 @@ Table of Contents
- Debug logging
- Schemas support
- 1d arrays, json, hstore & more
- Enum types

### Supported Databases

Expand All @@ -121,8 +123,8 @@ if err != nil {
return err
}

// 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
// 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
// the G variant methods like so:
boil.SetDB(db)
users, err := models.UsersG().All()
Expand Down Expand Up @@ -178,7 +180,7 @@ fmt.Println(len(users.R.FavoriteMovies))

* Go 1.6 minimum, and Go 1.7 for compatibility tests.
* 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.
* Join tables should use a *composite primary key*.
* For join tables to be used transparently for relationships your join table must have
Expand Down Expand Up @@ -1048,6 +1050,43 @@ exists, err := jet.Pilot(db).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

#### Won't compiling models for a huge database be very slow?
Expand Down
19 changes: 18 additions & 1 deletion bdb/column.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package bdb

import "github.com/vattle/sqlboiler/strmangle"
import (
"strings"

"github.com/vattle/sqlboiler/strmangle"
)

// Column holds information about a database column.
// Types are Go types, converted by TranslateColumnType.
Expand Down Expand Up @@ -54,3 +58,16 @@ func FilterColumnsByDefault(defaults bool, columns []Column) []Column {

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
}
20 changes: 20 additions & 0 deletions bdb/column_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,23 @@ func TestFilterColumnsByDefault(t *testing.T) {
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)
}
}
25 changes: 14 additions & 11 deletions bdb/drivers/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ func (m *MySQLDriver) Columns(schema, tableName string) ([]bdb.Column, error) {
var columns []bdb.Column

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 (
select c.column_name
from information_schema.table_constraints tc
Expand All @@ -140,24 +144,23 @@ func (m *MySQLDriver) Columns(schema, tableName string) ([]bdb.Column, error) {
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 {
var colName, colType string
var nullable, unique bool
var defaultValue *string
if err := rows.Scan(&colName, &colType, &defaultValue, &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",
Nullable: nullable,
Unique: unique,
}
if defaultValue != nil && *defaultValue != "NULL" {
column.Default = *defaultValue
}

columns = append(columns, column)
}

Expand Down
91 changes: 61 additions & 30 deletions bdb/drivers/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

// Side-effect import sql driver

_ "github.com/lib/pq"
"github.com/pkg/errors"
"github.com/vattle/sqlboiler/bdb"
Expand Down Expand Up @@ -123,25 +124,55 @@ func (p *PostgresDriver) Columns(schema, tableName string) ([]bdb.Column, error)
var columns []bdb.Column

rows, err := p.dbConn.Query(`
select column_name, c.data_type, e.data_type, column_default, c.udt_name, is_nullable,
(select exists(
select 1
select
c.column_name,
(
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
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'
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'
)) OR (select exists(
select 1
from
pg_indexes pgix
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_attribute pga on pga.attrelid = pgi.indrelid and pga.attnum = ANY(pgi.indkey)
where
pgix.schemaname = $1 and pgix.tablename = c.table_name and pga.attname = c.column_name and pgi.indisunique = true
select 1
from
pg_indexes pgix
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_attribute pga on pga.attrelid = pgi.indrelid and pga.attnum = ANY(pgi.indkey)
where
pgix.schemaname = $1 and pgix.tablename = c.table_name and pga.attname = c.column_name and pgi.indisunique = true
)) 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)
= (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))
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)
= (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;
`, schema, tableName)

Expand All @@ -151,29 +182,25 @@ func (p *PostgresDriver) Columns(schema, tableName string) ([]bdb.Column, error)
defer rows.Close()

for rows.Next() {
var colName, udtName, colType, colDefault, nullable string
var elementType *string
var unique bool
var defaultPtr *string
if err := rows.Scan(&colName, &colType, &elementType, &defaultPtr, &udtName, &nullable, &unique); err != nil {
var colName, colType, udtName string
var defaultValue, arrayType *string
var nullable, unique bool
if err := rows.Scan(&colName, &colType, &udtName, &arrayType, &defaultValue, &nullable, &unique); err != nil {
return nil, errors.Wrapf(err, "unable to scan for table %s", tableName)
}

if defaultPtr == nil {
colDefault = ""
} else {
colDefault = *defaultPtr
}

column := bdb.Column{
Name: colName,
DBType: colType,
ArrType: elementType,
ArrType: arrayType,
UDTName: udtName,
Default: colDefault,
Nullable: nullable == "YES",
Nullable: nullable,
Unique: unique,
}
if defaultValue != nil {
column.Default = *defaultValue
}

columns = append(columns, column)
}

Expand Down Expand Up @@ -290,6 +317,8 @@ func (p *PostgresDriver) TranslateColumnType(c bdb.Column) bdb.Column {
c.Type = "null.Float32"
case "bit", "interval", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
c.Type = "null.String"
case `"char"`:
c.Type = "null.Byte"
case "bytea":
c.Type = "null.Bytes"
case "json", "jsonb":
Expand Down Expand Up @@ -330,6 +359,8 @@ func (p *PostgresDriver) TranslateColumnType(c bdb.Column) bdb.Column {
c.Type = "float32"
case "bit", "interval", "uuint", "bit varying", "character", "money", "character varying", "cidr", "inet", "macaddr", "text", "uuid", "xml":
c.Type = "string"
case `"char"`:
c.Type = "types.Byte"
case "json", "jsonb":
c.Type = "types.JSON"
case "bytea":
Expand Down
3 changes: 1 addition & 2 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
test:
pre:
- 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 install github.com/jstemmer/go-junit-report
- go get -u 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
- createdb -U ubuntu sqlboiler
- psql -U ubuntu sqlboiler < ./testdata/postgres_test_schema.sql
Expand Down
Loading

0 comments on commit ca748b0

Please sign in to comment.