-
Notifications
You must be signed in to change notification settings - Fork 0
/
column.go
440 lines (386 loc) · 11.4 KB
/
column.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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
// Copyright 2020 Author: Ruslan Bikchentaev. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package psql
import (
"fmt"
"go/types"
"strings"
"github.com/pkg/errors"
"github.com/ruslanBik4/logs"
"github.com/ruslanBik4/gotools/typesExt"
"github.com/ruslanBik4/dbEngine/dbEngine"
)
// Column implement store data of column of table
type Column struct {
table *Table
name string
DataType string
colDefault any
isNullable bool
CharacterSetName string
comment string
UdtName string
characterMaximumLength int
autoInc bool
PrimaryKey bool
Constraints map[string]*dbEngine.ForeignKey
IsHidden bool
Position int32
UserDefined *dbEngine.Types
basicKind types.BasicKind
}
// UserDefinedType return define type
func (col *Column) UserDefinedType() *dbEngine.Types {
return col.UserDefined
}
// NewColumnForTableBuf create Column for scanning operation of Table
func NewColumnForTableBuf(table *Table) *Column {
return &Column{
table: table,
Constraints: make(map[string]*dbEngine.ForeignKey),
}
}
// Foreign return first foreign key of column
func (col *Column) Foreign() *dbEngine.ForeignKey {
for _, c := range col.Constraints {
if c != nil {
return c
}
}
return nil
}
// IsNullable return isNullable flag
func (col *Column) IsNullable() bool {
return col.isNullable
}
// AutoIncrement return true if column is autoincrement
func (col *Column) AutoIncrement() bool {
return col.autoInc
}
// Copy column & return new instance
func (col *Column) Copy() *Column {
return &Column{
table: col.table,
name: col.name,
basicKind: col.basicKind,
DataType: col.DataType,
colDefault: col.colDefault,
isNullable: col.isNullable,
CharacterSetName: col.CharacterSetName,
comment: col.comment,
UdtName: col.UdtName,
characterMaximumLength: col.characterMaximumLength,
autoInc: col.autoInc,
PrimaryKey: col.PrimaryKey,
Constraints: col.Constraints,
IsHidden: col.IsHidden,
UserDefined: col.UserDefined,
}
}
// GetFields implement RowColumn interface
func (col *Column) GetFields(columns []dbEngine.Column) []any {
v := make([]any, len(columns))
for i, column := range columns {
v[i] = col.RefColValue(column.Name())
}
return v
}
// NewColumnPone create new column with several properties
func NewColumnPone(name string, comment string, characterMaximumLength int) *Column {
return &Column{name: name, comment: comment, characterMaximumLength: characterMaximumLength}
}
// NewColumn create new column with many properties
func NewColumn(
table *Table,
name string,
dataType string,
colDefault any,
isNullable bool,
characterSetName string,
comment string,
udtName string,
characterMaximumLength int,
primaryKey bool,
isHidden bool,
) *Column {
col := &Column{
table: table,
name: name,
DataType: dataType,
isNullable: isNullable,
CharacterSetName: characterSetName,
comment: comment,
UdtName: udtName,
characterMaximumLength: characterMaximumLength,
PrimaryKey: primaryKey,
IsHidden: isHidden,
}
col.SetDefault(colDefault)
return col
}
// BasicTypeInfo of columns value
func (col *Column) BasicTypeInfo() types.BasicInfo {
switch col.BasicType() {
case types.Bool:
return types.IsBoolean
case types.Int32, types.Int64:
return types.IsInteger
case types.Float32, types.Float64, types.UntypedFloat:
return types.IsFloat
case types.String:
return types.IsString
default:
return types.IsUntyped
}
}
// BasicType return golang type of column
func (col *Column) BasicType() types.BasicKind {
if col.basicKind != types.Invalid {
return col.basicKind
}
col.defineBasicType(nil, nil)
if col.basicKind == types.UntypedNil {
logs.StatusLog(col, col.UdtName, col.UserDefined)
logs.ErrorStack(errors.New("invalid type"), col.UdtName)
}
return col.basicKind
}
func (col *Column) defineBasicType(dbTypes map[string]dbEngine.Types, tables map[string]dbEngine.Table) {
udtName := col.UdtName
// it's non-standard type we need chk its user defined
if col.DataType == "USER-DEFINED" {
t, ok := dbTypes[udtName]
if ok {
col.UserDefined = &t
} else if _, ok := tables[udtName]; ok {
} else {
logs.DebugLog("Routine %s use unknown type %s for params %s", col.Name(), udtName, col.Name())
}
}
if col.UserDefined != nil {
// enumerate always string
if len(col.UserDefined.Enumerates) > 0 {
col.basicKind = types.String
return
}
// we must seek domain type
for _, tAttr := range col.UserDefined.Attr {
if tAttr.Name == "domain" {
logs.StatusLog(col.name, udtName, col.UserDefined, tAttr.Type)
udtName = tAttr.Type
break
}
}
}
col.basicKind = UdtNameToType(udtName)
udtName = strings.TrimPrefix(udtName, "_")
if col.BasicType() == types.UntypedNil {
if t, ok := dbTypes[udtName]; ok {
col.basicKind = typesExt.TStruct
col.UserDefined = &t
} else if _, ok := tables[udtName]; ok {
col.basicKind = typesExt.TStruct
//col.UserDefined = &t
} else {
logs.ErrorLog(ErrUnknownType, udtName, typesExt.StringTypeKinds(col.basicKind), col.Name())
}
}
}
// Table implement dbEngine.Column interface
// return table of column
func (col *Column) Table() dbEngine.Table {
return col.table
}
// UdtNameToType return types.BasicKind according to psql udtName
func UdtNameToType(udtName string) types.BasicKind {
switch udtName {
case "bool":
return types.Bool
case "int2", "_int2":
return types.Int16
case "int4", "_int4":
return types.Int32
case "int8", "_int8":
return types.Int64
case "float4", "_float4":
return types.Float32
case "float8", "_float8", "money", "_money", "double precision":
return types.Float64
case "numeric", "decimal", "_numeric", "_decimal":
// todo add check field length UntypedFloat
return types.Float64
case "date", "timestamp", "timestamptz", "time", "_date", "_timestamp", "_timestamptz", "_time", "timerange", "tsrange", "daterange",
"numrange", "cube", "point":
return typesExt.TStruct
case "json", "jsonb":
return types.UnsafePointer
case "char", "_char", "varchar", "_varchar", "text", "_text", "citext", "_citext",
"character varying", "_character varying", "bpchar", "_bpchar":
return types.String
case "bytea", "_bytea":
return types.UnsafePointer
case "inet", "interval":
return typesExt.TMap
default:
logs.DebugLog("unknown type: %s", udtName)
return types.UntypedNil
}
}
const (
isNotNullable = "not null"
isDefineArray = "[]"
)
var dataTypeAlias = map[string][]string{
"character varying": {"varchar(255)", "varchar"},
"bpchar": {"char"},
"character": {"char"},
"varchar": {"character varying"},
"int4": {"serial", "int", "integer"},
"smallint": {"smallserial"},
"bigint": {"bigserial", "biginteger", "int8"},
"int8": {"bigserial", "biginteger"},
"double precision": {"float", "real"},
"timestamp without time zone": {"timestamp"},
"timestamp with time zone": {"timestamptz"},
// todo: add check user-defined types
"USER-DEFINED": {"timerange"},
//"ARRAY": {"integer[]", "character varying[]", "citext[]", "bpchar[]", "char"},
}
// CheckAttr check attributes of column on DB schema according to ddl-file
func (col *Column) CheckAttr(colDefine string) (flags []dbEngine.FlagColumn) {
colDefine = strings.ToLower(colDefine)
// todo: add check arrays
lenCol := col.CharacterMaximumLength()
udtName := col.UdtName
a, isArray := strings.CutPrefix(udtName, "_")
if isArray {
udtName = a + "[]"
}
isTypeValid := strings.HasPrefix(colDefine, col.DataType) || strings.HasPrefix(colDefine, udtName)
if !isTypeValid {
t := col.UdtName
if isArray {
t = a
}
for _, alias := range dataTypeAlias[t] {
isTypeValid = strings.HasPrefix(colDefine, alias)
if isTypeValid {
break
}
}
}
if isTypeValid {
if isArray != strings.Contains(colDefine, isDefineArray) {
logs.StatusLog(isArray, colDefine)
flags = append(flags, dbEngine.ChgToArray)
}
if strings.HasPrefix(col.DataType, "character") && (lenCol > 0) &&
!strings.Contains(colDefine, fmt.Sprintf("char(%d)", lenCol)) {
flags = append(flags, dbEngine.ChgLength)
}
} else {
logs.DebugLog(colDefine, col.name, col.DataType, udtName)
flags = append(flags, dbEngine.ChgType)
logs.DebugLog(col.DataType, col.UdtName, lenCol)
}
isNotNull := strings.Contains(colDefine, isNotNullable)
if col.isNullable && isNotNull {
flags = append(flags, dbEngine.MustNotNull)
} else if !col.isNullable && !isNotNull {
flags = append(flags, dbEngine.Nullable)
}
return
}
// CharacterMaximumLength return max of length text columns
func (col *Column) CharacterMaximumLength() int {
return col.characterMaximumLength
}
// Comment of column
func (col *Column) Comment() string {
return col.comment
}
// Name of column
func (col *Column) Name() string {
return col.name
}
// Primary return true if column is primary key
func (col *Column) Primary() bool {
return col.PrimaryKey
}
// Type of column (psql native)
func (col *Column) Type() string {
return col.UdtName
}
// IsArray of column (psql native)
func (col *Column) IsArray() bool {
return strings.HasPrefix(col.UdtName, "_")
}
// Required return true if column need a value
func (col *Column) Required() bool {
return !col.isNullable && (col.colDefault == nil)
}
// SetNullable set nullable flag of column
func (col *Column) SetNullable(f bool) {
col.isNullable = f
}
// Default return default value of column
func (col *Column) Default() any {
return col.colDefault
}
// SetDefault set default value into column
func (col *Column) SetDefault(d any) {
str, ok := d.(string)
if !ok {
col.colDefault = nil
return
}
if !(strings.HasPrefix(str, "(") && strings.HasSuffix(str, ")")) {
str = (strings.Split(str, "::"))[0]
if str == "NULL" {
col.colDefault = nil
return
}
}
const DEFAULT_SERIAL = "nextval("
isSerial := strings.HasPrefix(str, DEFAULT_SERIAL)
if isSerial {
col.colDefault = strings.Trim(strings.TrimPrefix(str, DEFAULT_SERIAL), "'")
} else {
col.colDefault = strings.Trim(str, "'")
}
// todo add other case of autogenerate column value
upperS := strings.ToUpper(str)
col.autoInc = isSerial ||
strings.Contains(upperS, "CURRENT_TIMESTAMP") ||
strings.Contains(upperS, "CURRENT_DATE") ||
strings.Contains(upperS, "CURRENT_USER") ||
strings.Contains(upperS, "NOW()")
}
// RefColValue referral of column property 'name'
func (col *Column) RefColValue(name string) any {
switch name {
case "data_type":
return &col.DataType
case "column_name":
return &col.name
case "column_default":
return &col.colDefault
case "is_nullable":
return &col.isNullable
case "character_set_name":
return &col.CharacterSetName
case "character_maximum_length":
return &col.characterMaximumLength
case "udt_name":
return &col.UdtName
case "column_comment":
return &col.comment
case "keys":
return &col.Constraints
case "ordinal_position":
return &col.Position
default:
panic("not implement scan for field " + name)
}
}