/
alterLog.go
365 lines (325 loc) · 9.17 KB
/
alterLog.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package shifter
import (
"bytes"
"errors"
"fmt"
"go/format"
"os"
"reflect"
"sort"
"strings"
"text/template"
"time"
"github.com/go-pg/pg"
"github.com/iancoleman/strcase"
"github.com/mayur-tolexo/pg-shifter/model"
)
//sLog : structure log model
type sLog struct {
StructNameWT string
StructName string
TableName string
Data []model.ColSchema
Unique []model.UKSchema
Index []model.Index
Date string
importedPkg map[string]struct{}
}
//pgToStructType to golang type mapping
var pgToGoType = map[string]string{
userDefined: "string",
"bigint": "int",
"bigserial": "int",
"varbit": "[]bytes",
"boolean": "bool",
"character": "string",
"character varying": "string",
"double precision": "float64",
"integer": "int",
"numeric": "float64",
"real": "float64",
"smallint": "int",
"smallserial": "int",
"serial": "int",
"text": "string",
"citext": "string",
"time without time zone": "time.Time",
"time with time zone": "time.Time",
"timestamp without time zone": "time.Time",
"timestamp with time zone": "time.Time",
}
//createAlterStructLog will create alter struct log
func (s *Shifter) createAlterStructLog(schema map[string]model.ColSchema,
ukSchema []model.UKSchema, idx []model.Index, wt bool) (err error) {
var (
log sLog
fData []byte
)
if log, fData, err = s.getTableStructSchema(schema, ukSchema, idx, wt); err == nil {
err = s.logTableChange(log, fData)
}
return
}
//generateTableStructSchema will generate table schema from database in struct form
func (s *Shifter) generateTableStructSchema(tx *pg.Tx, tableName string, wt bool) (
log sLog, fData []byte, exists bool, err error) {
var (
tUK []model.UKSchema
idx []model.Index
tSchema map[string]model.ColSchema
)
exists = tableExists(tx, tableName)
if exists {
if tSchema, err = s.getTableSchema(tx, tableName); err == nil {
if tUK, err = getDBCompositeUniqueKey(tx, tableName); err == nil {
if idx, err = getDBIndex(tx, tableName); err == nil {
log, fData, err = s.getTableStructSchema(tSchema, tUK, idx, wt)
}
}
}
}
return
}
//getTableStructSchema will return table schema from database as in struct form
func (s *Shifter) getTableStructSchema(schema map[string]model.ColSchema,
ukSchema []model.UKSchema, idx []model.Index, wt bool) (log sLog, fData []byte, err error) {
var logStr string
log = s.getSLogModel(schema, ukSchema, idx, wt)
if logStr, err = execLogTmpl(log); err == nil {
prefix := getWarning(wt) + getPkg(log.StructName) + getImportPkg(log.importedPkg)
logStr = prefix + logStr
fData, _ = format.Source([]byte(logStr))
}
if err != nil {
err = errors.New("Log Creation Error: " + err.Error())
}
return
}
func getPkg(structName string) string {
return "package " + structName + "\n\n"
}
//logTableChange will log table change in file
func (s *Shifter) logTableChange(log sLog, logStr []byte) (err error) {
var (
fp *os.File
logDir string
)
if logDir, err = s.makeStructLogDir(log.StructName); err == nil {
file := logDir + "/" + log.StructNameWT + ".go"
if fp, err = os.Create(file); err == nil {
fp.Write(logStr)
}
}
return
}
//getSLogModel will return slog model
func (s *Shifter) getSLogModel(schema map[string]model.ColSchema,
ukSchema []model.UKSchema, idx []model.Index, wt bool) (log sLog) {
sTime := time.Now().UTC()
tName := getTableName(schema)
sName := getFieldName(tName)
if model, exists := s.table[tName]; exists {
sName = getTableNameFromStruct(model)
}
sNameWithTime := sName
if wt {
sNameWithTime = fmt.Sprintf("%v%v", sName, sTime.Unix())
}
log = sLog{
StructNameWT: sNameWithTime,
StructName: sName,
TableName: tName,
Data: getLogData(schema),
Unique: ukSchema,
Index: idx,
Date: sTime.Format("Mon _2 Jan 2006 15:04:05"),
importedPkg: make(map[string]struct{}),
}
return
}
//getImportPkg will append all imported packeges
func getImportPkg(pkg map[string]struct{}) (impPkg string) {
if len(pkg) > 0 {
impPkg += "import (\n"
for k := range pkg {
// impPkg += "\"" + k + "\"\n"
impPkg += k + "\n"
}
impPkg += ")\n"
}
return
}
//makeStructLogDir will create struct log dir if not exists
func (s *Shifter) makeStructLogDir(structName string) (logDir string, err error) {
if s.logPath == "" {
s.logPath, err = os.Getwd()
s.logPath += "/log/"
}
if err == nil {
logDir = s.logPath + "/" + structName
if _, err = os.Stat(logDir); os.IsNotExist(err) {
err = os.MkdirAll(logDir, os.ModePerm)
}
}
return
}
//execLogTmpl will execute log template
func execLogTmpl(log sLog) (logStr string, err error) {
var (
tmpl *template.Template
buf bytes.Buffer
)
tmplStr := getLogTmpl()
if tmpl, err = template.New("template").
Funcs(getLogTmplFunc()).
Parse(tmplStr); err == nil {
if err = tmpl.Execute(&buf, &log); err == nil {
// fmt.Println(buf.String())
logStr = buf.String()
}
}
return
}
//getTmplFunc will return template functions
func getLogTmplFunc() template.FuncMap {
return template.FuncMap{
"Title": getFieldName,
"getSQLTag": getSQLTag,
}
}
//getSQLTag will return struct sql tag from schema struct
func getSQLTag(schema model.ColSchema) (dType string) {
dType = getStructDataType(schema)
dType += getNullDTypeSQL(schema.IsNullable)
dType += getDefaultDTypeSQL(schema)
dType += getUniqueDTypeSQL(schema)
dType += getConstraintTagSQL(schema)
return
}
//getConstraintTagSQL will return sql tag constraint
func getConstraintTagSQL(schema model.ColSchema) (sql string) {
switch schema.ConstraintType {
case primaryKey:
sql = " " + primaryKey
case foreignKey:
sql = getStructConstraintSQL(schema)
}
return
}
//getTableNameFromStruct will return table name from struct
func getTableNameFromStruct(model interface{}) string {
return reflect.TypeOf(model).Elem().Name()
}
//GetStructFieldType will return struct field type from schema datatype
func (l *sLog) GetStructFieldType(dataType string) (sType string) {
var exists bool
if sType, exists = pgToGoType[dataType]; exists == false {
sType = "interface{}"
}
//if any package is used then adding that in import
if strings.Contains(sType, ".") {
pkg := strings.Split(sType, ".")[0]
l.importedPkg["\""+pkg+"\""] = struct{}{}
}
return
}
//GetIndexType will return index type for template
func (l *sLog) GetIndexType(iType string) (sType string) {
l.importedPkg[curPkg] = struct{}{}
switch iType {
case BtreeIndex:
sType = "shifter.BtreeIndex"
case GinIndex:
sType = "shifter.GinIndex"
case GistIndex:
sType = "shifter.GistIndex"
case HashIndex:
sType = "shifter.HashIndex"
case BrinIndex:
sType = "shifter.BrinIndex"
case SPGistIndex:
sType = "shifter.SPGistIndex"
}
return
}
//getTableName will return table name from schema
func getTableName(schema map[string]model.ColSchema) (tName string) {
for _, v := range schema {
tName = v.TableName
break
}
return
}
//getFieldName will return field name in Camel Case
func getFieldName(k string) (f string) {
f = strcase.ToCamel(k)
return
}
//getLogData will return data based on position of column in table
func getLogData(schema map[string]model.ColSchema) (data []model.ColSchema) {
for _, v := range schema {
data = append(data, v)
}
sort.Slice(data, func(i, j int) bool {
return data[i].Position < data[j].Position
})
return
}
//getLogTmpl will return struct log template
func getLogTmpl() (tmplStr string) {
tmplStr = `
//{{ .StructNameWT }} : {{ .TableName }} table model [As on {{ .Date }} UTC]
type {{ .StructNameWT }} struct {
tableName struct{} ` + "`sql:\"{{ .TableName }}\"`" + `
{{- range $key, $value := .Data}}
{{ if eq $value.StructColumnName "" -}}
{{ Title $value.ColumnName -}}
{{ else -}}
{{ $value.StructColumnName -}}
{{ end -}}
{{print " "}} {{ $.GetStructFieldType $value.DataType }}` + " `sql:\"{{ .ColumnName }},type:{{ getSQLTag $value }}\"`" + `
{{- end }}
}
{{ $length := len .Unique }} {{ if gt $length 0 }}
//UniqueKey of the table. This is for composite unique keys
func ({{ .StructNameWT }}) UniqueKey() []string {
uk := []string{
{{- range $key, $value := .Unique}}
"{{ $value.Columns }}", //{{ $value.ConstraintName }}
{{- end }}
}
return uk
}
{{ end }}
{{ $length := len .Index }} {{ if gt $length 0 }}
//Index of the table. For composite index use ,
//Default index type is btree. For gin index use gin value
func ({{ .StructNameWT }}) Index() map[string]string {
idx := map[string]string{
{{- range $key, $value := .Index}}
"{{ $value.Columns }}": {{ $.GetIndexType $value.IType }}, //{{ $value.IdxName }}
{{- end }}
}
return idx
}
{{ end }}
`
return
}
//getWarning will return warning string
func getWarning(wt bool) (wrng string) {
wrng = `
/*
@uthor Mayur Das<mayur.das4@gmail.com>
https://www.linkedin.com/in/mayurdaeron/`
if wt {
wrng += `
This is a history of the table before altering it.
We will use this to revert back to the previous state if needed.
----- DO NOT EDIT THIS -----`
}
wrng += `
*/
`
return
}