Merge branch 'master' of github.com:nullbio/sqlboiler
This commit is contained in:
commit
0275677216
7 changed files with 169 additions and 21 deletions
|
@ -179,7 +179,7 @@ func (p *PostgresDriver) ForeignKeyInfo(tableName string) ([]bdb.ForeignKey, err
|
||||||
from information_schema.table_constraints as tc
|
from information_schema.table_constraints as tc
|
||||||
inner join information_schema.key_column_usage as kcu ON tc.constraint_name = kcu.constraint_name
|
inner join information_schema.key_column_usage as kcu ON tc.constraint_name = kcu.constraint_name
|
||||||
inner join information_schema.constraint_column_usage as ccu ON tc.constraint_name = ccu.constraint_name
|
inner join information_schema.constraint_column_usage as ccu ON tc.constraint_name = ccu.constraint_name
|
||||||
where tc.table_name = $1 and tc.constraint_type = 'FOREIGN KEY' and tc.table_schema = 'information_schema';`
|
where tc.table_name = $1 and tc.constraint_type = 'FOREIGN KEY' and tc.table_schema = 'public';`
|
||||||
|
|
||||||
var rows *sql.Rows
|
var rows *sql.Rows
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -7,7 +7,11 @@ type ToManyRelationship struct {
|
||||||
Column string
|
Column string
|
||||||
ForeignTable string
|
ForeignTable string
|
||||||
ForeignColumn string
|
ForeignColumn string
|
||||||
ToJoinTable bool
|
|
||||||
|
ToJoinTable bool
|
||||||
|
JoinTable string
|
||||||
|
JoinLocalColumn string
|
||||||
|
JoinForeignColumn string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToManyRelationships relationship lookups
|
// ToManyRelationships relationship lookups
|
||||||
|
@ -25,16 +29,38 @@ func ToManyRelationships(table string, tables []Table) []ToManyRelationship {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
relationship := ToManyRelationship{
|
relationships = append(relationships, buildRelationship(table, f, t))
|
||||||
Column: f.ForeignColumn,
|
|
||||||
ForeignTable: t.Name,
|
|
||||||
ForeignColumn: f.Column,
|
|
||||||
ToJoinTable: t.IsJoinTable,
|
|
||||||
}
|
|
||||||
|
|
||||||
relationships = append(relationships, relationship)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return relationships
|
return relationships
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildRelationship(localTable string, foreignKey ForeignKey, foreignTable Table) ToManyRelationship {
|
||||||
|
if !foreignTable.IsJoinTable {
|
||||||
|
return ToManyRelationship{
|
||||||
|
Column: foreignKey.ForeignColumn,
|
||||||
|
ForeignTable: foreignTable.Name,
|
||||||
|
ForeignColumn: foreignKey.Column,
|
||||||
|
ToJoinTable: foreignTable.IsJoinTable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relationship := ToManyRelationship{
|
||||||
|
Column: foreignKey.ForeignColumn,
|
||||||
|
ToJoinTable: true,
|
||||||
|
JoinTable: foreignTable.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fk := range foreignTable.FKeys {
|
||||||
|
if fk.ForeignTable != localTable {
|
||||||
|
relationship.JoinForeignColumn = fk.Column
|
||||||
|
relationship.ForeignTable = fk.ForeignTable
|
||||||
|
relationship.ForeignColumn = fk.ForeignColumn
|
||||||
|
} else {
|
||||||
|
relationship.JoinLocalColumn = fk.Column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return relationship
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
package bdb
|
package bdb
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
func TestToManyRelationships(t *testing.T) {
|
func TestToManyRelationships(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tables := []Table{
|
tables := []Table{
|
||||||
Table{
|
Table{
|
||||||
Name: "videos",
|
Name: "videos",
|
||||||
IsJoinTable: true,
|
|
||||||
FKeys: []ForeignKey{
|
FKeys: []ForeignKey{
|
||||||
{Name: "videos_user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id"},
|
{Name: "videos_user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id"},
|
||||||
{Name: "videos_contest_id_fk", Column: "contest_id", ForeignTable: "contests", ForeignColumn: "id"},
|
{Name: "videos_contest_id_fk", Column: "contest_id", ForeignTable: "contests", ForeignColumn: "id"},
|
||||||
|
@ -21,9 +24,22 @@ func TestToManyRelationships(t *testing.T) {
|
||||||
{Name: "notifications_source_id_fk", Column: "source_id", ForeignTable: "users", ForeignColumn: "id"},
|
{Name: "notifications_source_id_fk", Column: "source_id", ForeignTable: "users", ForeignColumn: "id"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Table{
|
||||||
|
Name: "users_video_tags",
|
||||||
|
IsJoinTable: true,
|
||||||
|
FKeys: []ForeignKey{
|
||||||
|
{Name: "user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id"},
|
||||||
|
{Name: "video_id_fk", Column: "video_id", ForeignTable: "videos", ForeignColumn: "id"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
relationships := ToManyRelationships("users", tables)
|
relationships := ToManyRelationships("users", tables)
|
||||||
|
spew.Dump(relationships)
|
||||||
|
if len(relationships) != 4 {
|
||||||
|
t.Error("wrong # of relationships:", len(relationships))
|
||||||
|
}
|
||||||
|
|
||||||
r := relationships[0]
|
r := relationships[0]
|
||||||
if r.Column != "id" {
|
if r.Column != "id" {
|
||||||
t.Error("wrong local column:", r.Column)
|
t.Error("wrong local column:", r.Column)
|
||||||
|
@ -34,7 +50,58 @@ func TestToManyRelationships(t *testing.T) {
|
||||||
if r.ForeignColumn != "user_id" {
|
if r.ForeignColumn != "user_id" {
|
||||||
t.Error("wrong foreign column:", r.ForeignColumn)
|
t.Error("wrong foreign column:", r.ForeignColumn)
|
||||||
}
|
}
|
||||||
|
if r.ToJoinTable {
|
||||||
|
t.Error("not a join table")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = relationships[1]
|
||||||
|
if r.Column != "id" {
|
||||||
|
t.Error("wrong local column:", r.Column)
|
||||||
|
}
|
||||||
|
if r.ForeignTable != "notifications" {
|
||||||
|
t.Error("wrong foreign table:", r.ForeignTable)
|
||||||
|
}
|
||||||
|
if r.ForeignColumn != "user_id" {
|
||||||
|
t.Error("wrong foreign column:", r.ForeignColumn)
|
||||||
|
}
|
||||||
|
if r.ToJoinTable {
|
||||||
|
t.Error("not a join table")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = relationships[2]
|
||||||
|
if r.Column != "id" {
|
||||||
|
t.Error("wrong local column:", r.Column)
|
||||||
|
}
|
||||||
|
if r.ForeignTable != "notifications" {
|
||||||
|
t.Error("wrong foreign table:", r.ForeignTable)
|
||||||
|
}
|
||||||
|
if r.ForeignColumn != "source_id" {
|
||||||
|
t.Error("wrong foreign column:", r.ForeignColumn)
|
||||||
|
}
|
||||||
|
if r.ToJoinTable {
|
||||||
|
t.Error("not a join table")
|
||||||
|
}
|
||||||
|
|
||||||
|
r = relationships[3]
|
||||||
|
if r.Column != "id" {
|
||||||
|
t.Error("wrong local column:", r.Column)
|
||||||
|
}
|
||||||
|
if r.ForeignColumn != "id" {
|
||||||
|
t.Error("wrong foreign column:", r.Column)
|
||||||
|
}
|
||||||
|
if r.ForeignTable != "videos" {
|
||||||
|
t.Error("wrong foreign table:", r.ForeignTable)
|
||||||
|
}
|
||||||
|
if r.JoinTable != "users_video_tags" {
|
||||||
|
t.Error("wrong join table:", r.ForeignTable)
|
||||||
|
}
|
||||||
|
if r.JoinLocalColumn != "user_id" {
|
||||||
|
t.Error("wrong local join column:", r.JoinLocalColumn)
|
||||||
|
}
|
||||||
|
if r.JoinForeignColumn != "video_id" {
|
||||||
|
t.Error("wrong foreign join column:", r.JoinForeignColumn)
|
||||||
|
}
|
||||||
if !r.ToJoinTable {
|
if !r.ToJoinTable {
|
||||||
t.Error("expected a join table - kind of - not really but we're faking it")
|
t.Error("expected a join table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,42 @@ package strmangle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jinzhu/inflection"
|
"github.com/jinzhu/inflection"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
idAlphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Identifier creates an identifier useful for a query
|
||||||
|
// This is essentially a base conversion from Base 10 integers to Base 26
|
||||||
|
// integers that are represented by an alphabet from a-z
|
||||||
|
// See tests for example outputs.
|
||||||
|
func Identifier(in int) string {
|
||||||
|
ln := len(idAlphabet)
|
||||||
|
var n int
|
||||||
|
if in == 0 {
|
||||||
|
n = 1
|
||||||
|
} else {
|
||||||
|
n = 1 + int(math.Log(float64(in))/math.Log(float64(ln)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := make([]byte, n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
divisor := int(math.Pow(float64(ln), float64(n-i-1)))
|
||||||
|
rem := in / divisor
|
||||||
|
cols[i] = idAlphabet[rem]
|
||||||
|
|
||||||
|
in -= rem * divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(cols)
|
||||||
|
}
|
||||||
|
|
||||||
// Plural converts singular words to plural words (eg: person to people)
|
// Plural converts singular words to plural words (eg: person to people)
|
||||||
func Plural(name string) string {
|
func Plural(name string) string {
|
||||||
splits := strings.Split(name, "_")
|
splits := strings.Split(name, "_")
|
||||||
|
|
|
@ -5,6 +5,28 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestIDGen(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
In int
|
||||||
|
Out string
|
||||||
|
}{
|
||||||
|
{In: 0, Out: "a"},
|
||||||
|
{In: 25, Out: "z"},
|
||||||
|
{In: 26, Out: "ba"},
|
||||||
|
{In: 52, Out: "ca"},
|
||||||
|
{In: 675, Out: "zz"},
|
||||||
|
{In: 676, Out: "baa"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
if got := Identifier(test.In); got != test.Out {
|
||||||
|
t.Errorf("[%d] want: %q, got: %q", test.In, test.Out, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDriverUsesLastInsertID(t *testing.T) {
|
func TestDriverUsesLastInsertID(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,7 @@ var templateFunctions = template.FuncMap{
|
||||||
"replace": func(rep, with, str string) string { return strings.Replace(str, rep, with, -1) },
|
"replace": func(rep, with, str string) string { return strings.Replace(str, rep, with, -1) },
|
||||||
"prefix": func(add, str string) string { return fmt.Sprintf("%s%s", add, str) },
|
"prefix": func(add, str string) string { return fmt.Sprintf("%s%s", add, str) },
|
||||||
"quoteWrap": func(a string) string { return fmt.Sprintf(`"%s"`, a) },
|
"quoteWrap": func(a string) string { return fmt.Sprintf(`"%s"`, a) },
|
||||||
|
"id": strmangle.Identifier,
|
||||||
|
|
||||||
// Pluralization
|
// Pluralization
|
||||||
"singular": strmangle.Singular,
|
"singular": strmangle.Singular,
|
||||||
|
|
|
@ -7,16 +7,14 @@
|
||||||
{{- $colName := $localTableSing | printf "%s_id" -}}
|
{{- $colName := $localTableSing | printf "%s_id" -}}
|
||||||
{{- $receiver := .Table.Name | toLower | substring 0 1 -}}
|
{{- $receiver := .Table.Name | toLower | substring 0 1 -}}
|
||||||
{{- range toManyRelationships .Table.Name .Tables -}}
|
{{- range toManyRelationships .Table.Name .Tables -}}
|
||||||
{{- if .ToJoinTable -}}
|
|
||||||
{{- else -}}
|
|
||||||
{{- $foreignTableSing := .ForeignTable | singular}}
|
{{- $foreignTableSing := .ForeignTable | singular}}
|
||||||
{{- $foreignTable := $foreignTableSing | titleCase}}
|
{{- $foreignTable := $foreignTableSing | titleCase}}
|
||||||
{{- $foreignSlice := $foreignTableSing | camelCase | printf "%sSlice"}}
|
{{- $foreignSlice := $foreignTableSing | camelCase | printf "%sSlice"}}
|
||||||
{{- $foreignTableHumanReadable := .ForeignTable | replace "_" " " -}}
|
{{- $foreignTableHumanReadable := .ForeignTable | replace "_" " " -}}
|
||||||
{{- $foreignPluralNoun := .ForeignTable | plural | titleCase -}}
|
{{- $foreignPluralNoun := .ForeignTable | plural | titleCase -}}
|
||||||
{{- $isNormal := eq $colName .ForeignColumn -}}
|
{{- $isForeignKeySimplyTableName := or (eq $colName .ForeignColumn) .ToJoinTable -}}
|
||||||
|
|
||||||
{{- if $isNormal -}}
|
{{- if $isForeignKeySimplyTableName -}}
|
||||||
// {{$foreignPluralNoun}} retrieves all the {{$localTableSing}}'s {{$foreignTableHumanReadable}}.
|
// {{$foreignPluralNoun}} retrieves all the {{$localTableSing}}'s {{$foreignTableHumanReadable}}.
|
||||||
func ({{$receiver}} *{{$localTable}}) {{$foreignPluralNoun}}(
|
func ({{$receiver}} *{{$localTable}}) {{$foreignPluralNoun}}(
|
||||||
|
|
||||||
|
@ -29,7 +27,11 @@ func ({{$receiver}} *{{$localTable}}) {{$fnName}}(
|
||||||
exec boil.Executor, selectCols ...string) ({{$foreignSlice}}, error) {
|
exec boil.Executor, selectCols ...string) ({{$foreignSlice}}, error) {
|
||||||
var ret {{$foreignSlice}}
|
var ret {{$foreignSlice}}
|
||||||
|
|
||||||
|
{{if .ToJoinTable -}}
|
||||||
|
query := fmt.Sprintf(`select "%s" from {{.ForeignTable}} "{{id 0}}" inner join {{.JoinTable}} as "{{id 1}}" on "{{id 1}}"."{{.JoinForeignColumn}}" = "{{id 0}}"."{{.ForeignColumn}}" where "{{id 1}}"."{{.JoinLocalColumn}}"=$1`, `"{{id 1}}".` + strings.Join(selectCols, `","{{id 0}}"."`))
|
||||||
|
{{else -}}
|
||||||
query := fmt.Sprintf(`select "%s" from {{.ForeignTable}} where "{{.ForeignColumn}}"=$1`, strings.Join(selectCols, `","`))
|
query := fmt.Sprintf(`select "%s" from {{.ForeignTable}} where "{{.ForeignColumn}}"=$1`, strings.Join(selectCols, `","`))
|
||||||
|
{{end}}
|
||||||
rows, err := exec.Query(query, {{.Column | titleCase | printf "%s.%s" $receiver }})
|
rows, err := exec.Query(query, {{.Column | titleCase | printf "%s.%s" $receiver }})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`{{$dot.PkgName}}: unable to select from {{.ForeignTable}}: %v`, err)
|
return nil, fmt.Errorf(`{{$dot.PkgName}}: unable to select from {{.ForeignTable}}: %v`, err)
|
||||||
|
@ -53,6 +55,5 @@ exec boil.Executor, selectCols ...string) ({{$foreignSlice}}, error) {
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
{{end -}}{{/* if join table */}}
|
{{end -}}{{- /* range relationships */ -}}
|
||||||
{{- end -}}{{/* range relationships */}}
|
{{- end -}}{{- /* outer if join table */ -}}
|
||||||
{{- end -}}{{/* outer if join table */}}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue