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