1// Copyright 2012 James Cooper. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5// Package gorp provides a simple way to marshal Go structs to and from
6// SQL databases.  It uses the database/sql package, and should work with any
7// compliant database/sql driver.
8//
9// Source code and project home:
10// https://github.com/go-gorp/gorp
11
12package gorp
13
14import (
15	"fmt"
16	"reflect"
17	"strings"
18)
19
20// TableMap represents a mapping between a Go struct and a database table
21// Use dbmap.AddTable() or dbmap.AddTableWithName() to create these
22type TableMap struct {
23	// Name of database table.
24	TableName      string
25	SchemaName     string
26	gotype         reflect.Type
27	Columns        []*ColumnMap
28	keys           []*ColumnMap
29	indexes        []*IndexMap
30	uniqueTogether [][]string
31	version        *ColumnMap
32	insertPlan     bindPlan
33	updatePlan     bindPlan
34	deletePlan     bindPlan
35	getPlan        bindPlan
36	dbmap          *DbMap
37}
38
39// ResetSql removes cached insert/update/select/delete SQL strings
40// associated with this TableMap.  Call this if you've modified
41// any column names or the table name itself.
42func (t *TableMap) ResetSql() {
43	t.insertPlan = bindPlan{}
44	t.updatePlan = bindPlan{}
45	t.deletePlan = bindPlan{}
46	t.getPlan = bindPlan{}
47}
48
49// SetKeys lets you specify the fields on a struct that map to primary
50// key columns on the table.  If isAutoIncr is set, result.LastInsertId()
51// will be used after INSERT to bind the generated id to the Go struct.
52//
53// Automatically calls ResetSql() to ensure SQL statements are regenerated.
54//
55// Panics if isAutoIncr is true, and fieldNames length != 1
56//
57func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap {
58	if isAutoIncr && len(fieldNames) != 1 {
59		panic(fmt.Sprintf(
60			"gorp: SetKeys: fieldNames length must be 1 if key is auto-increment. (Saw %v fieldNames)",
61			len(fieldNames)))
62	}
63	t.keys = make([]*ColumnMap, 0)
64	for _, name := range fieldNames {
65		colmap := t.ColMap(name)
66		colmap.isPK = true
67		colmap.isAutoIncr = isAutoIncr
68		t.keys = append(t.keys, colmap)
69	}
70	t.ResetSql()
71
72	return t
73}
74
75// SetUniqueTogether lets you specify uniqueness constraints across multiple
76// columns on the table. Each call adds an additional constraint for the
77// specified columns.
78//
79// Automatically calls ResetSql() to ensure SQL statements are regenerated.
80//
81// Panics if fieldNames length < 2.
82//
83func (t *TableMap) SetUniqueTogether(fieldNames ...string) *TableMap {
84	if len(fieldNames) < 2 {
85		panic(fmt.Sprintf(
86			"gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint."))
87	}
88
89	columns := make([]string, 0, len(fieldNames))
90	for _, name := range fieldNames {
91		columns = append(columns, name)
92	}
93
94	alreadyExists := false
95checkDuplicates:
96	for _, existingColumns := range t.uniqueTogether {
97		if len(existingColumns) == len(columns) {
98			for i := range columns {
99				if existingColumns[i] != columns[i] {
100					continue checkDuplicates
101				}
102			}
103
104			alreadyExists = true
105			break checkDuplicates
106		}
107	}
108	if !alreadyExists {
109		t.uniqueTogether = append(t.uniqueTogether, columns)
110		t.ResetSql()
111	}
112
113	return t
114}
115
116// ColMap returns the ColumnMap pointer matching the given struct field
117// name.  It panics if the struct does not contain a field matching this
118// name.
119func (t *TableMap) ColMap(field string) *ColumnMap {
120	col := colMapOrNil(t, field)
121	if col == nil {
122		e := fmt.Sprintf("No ColumnMap in table %s type %s with field %s",
123			t.TableName, t.gotype.Name(), field)
124
125		panic(e)
126	}
127	return col
128}
129
130func colMapOrNil(t *TableMap, field string) *ColumnMap {
131	for _, col := range t.Columns {
132		if col.fieldName == field || col.ColumnName == field {
133			return col
134		}
135	}
136	return nil
137}
138
139// IdxMap returns the IndexMap pointer matching the given index name.
140func (t *TableMap) IdxMap(field string) *IndexMap {
141	for _, idx := range t.indexes {
142		if idx.IndexName == field {
143			return idx
144		}
145	}
146	return nil
147}
148
149// AddIndex registers the index with gorp for specified table with given parameters.
150// This operation is idempotent. If index is already mapped, the
151// existing *IndexMap is returned
152// Function will panic if one of the given for index columns does not exists
153//
154// Automatically calls ResetSql() to ensure SQL statements are regenerated.
155//
156func (t *TableMap) AddIndex(name string, idxtype string, columns []string) *IndexMap {
157	// check if we have a index with this name already
158	for _, idx := range t.indexes {
159		if idx.IndexName == name {
160			return idx
161		}
162	}
163	for _, icol := range columns {
164		if res := t.ColMap(icol); res == nil {
165			e := fmt.Sprintf("No ColumnName in table %s to create index on", t.TableName)
166			panic(e)
167		}
168	}
169
170	idx := &IndexMap{IndexName: name, Unique: false, IndexType: idxtype, columns: columns}
171	t.indexes = append(t.indexes, idx)
172	t.ResetSql()
173	return idx
174}
175
176// SetVersionCol sets the column to use as the Version field.  By default
177// the "Version" field is used.  Returns the column found, or panics
178// if the struct does not contain a field matching this name.
179//
180// Automatically calls ResetSql() to ensure SQL statements are regenerated.
181func (t *TableMap) SetVersionCol(field string) *ColumnMap {
182	c := t.ColMap(field)
183	t.version = c
184	t.ResetSql()
185	return c
186}
187
188// SqlForCreateTable gets a sequence of SQL commands that will create
189// the specified table and any associated schema
190func (t *TableMap) SqlForCreate(ifNotExists bool) string {
191	s := strings.Builder{}
192	dialect := t.dbmap.Dialect
193
194	if strings.TrimSpace(t.SchemaName) != "" {
195		schemaCreate := "create schema"
196		if ifNotExists {
197			s.WriteString(dialect.IfSchemaNotExists(schemaCreate, t.SchemaName))
198		} else {
199			s.WriteString(schemaCreate)
200		}
201		s.WriteString(fmt.Sprintf(" %s;", t.SchemaName))
202	}
203
204	tableCreate := "create table"
205	if ifNotExists {
206		s.WriteString(dialect.IfTableNotExists(tableCreate, t.SchemaName, t.TableName))
207	} else {
208		s.WriteString(tableCreate)
209	}
210	s.WriteString(fmt.Sprintf(" %s (", dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
211
212	x := 0
213	for _, col := range t.Columns {
214		if !col.Transient {
215			if x > 0 {
216				s.WriteString(", ")
217			}
218
219			stype := col.DataType
220			if stype == "" {
221				stype = dialect.ToSqlType(col.gotype, col.MaxSize, col.isAutoIncr)
222			}
223
224			s.WriteString(fmt.Sprintf("%s %s", dialect.QuoteField(col.ColumnName), stype))
225
226			if col.isPK || col.isNotNull {
227				s.WriteString(" not null")
228			}
229			if col.DefaultConstraint != nil {
230				s.WriteString(fmt.Sprintf(" default '%s'", *col.DefaultConstraint))
231			}
232			if col.isPK && len(t.keys) == 1 {
233				s.WriteString(" primary key")
234			}
235			if col.Unique {
236				s.WriteString(" unique")
237			}
238			if col.isAutoIncr {
239				s.WriteString(fmt.Sprintf(" %s", dialect.AutoIncrStr()))
240			}
241
242			x++
243		}
244	}
245	if len(t.keys) > 1 {
246		s.WriteString(", primary key (")
247		for x := range t.keys {
248			if x > 0 {
249				s.WriteString(", ")
250			}
251			s.WriteString(dialect.QuoteField(t.keys[x].ColumnName))
252		}
253		s.WriteString(")")
254	}
255	if len(t.uniqueTogether) > 0 {
256		for _, columns := range t.uniqueTogether {
257			s.WriteString(", unique (")
258			for i, column := range columns {
259				if i > 0 {
260					s.WriteString(", ")
261				}
262				s.WriteString(dialect.QuoteField(column))
263			}
264			s.WriteString(")")
265		}
266	}
267	s.WriteString(") ")
268	s.WriteString(dialect.CreateTableSuffix())
269	s.WriteString(dialect.QuerySuffix())
270	return s.String()
271}
272