This commit is contained in:
136
pkg/schema/lint/checks.go
Normal file
136
pkg/schema/lint/checks.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"git.offline-twitter.com/offline-labs/gas-stack/pkg/schema"
|
||||
)
|
||||
|
||||
type Check struct {
|
||||
Name string
|
||||
Explanation string
|
||||
Execute func(schema.Schema) []CheckResult
|
||||
}
|
||||
|
||||
// CheckResult represents a row in the query result with error message, table name, and column name.
|
||||
type CheckResult struct {
|
||||
ErrorMsg string `db:"error_msg"`
|
||||
TableName string `db:"table_name"`
|
||||
ColumnName string `db:"column_name"`
|
||||
}
|
||||
|
||||
var Checks = []Check{
|
||||
{
|
||||
Name: "require_not_null",
|
||||
Explanation: "All columns should be marked as `not null` unless they are foreign keys. (Primary keys are\n" +
|
||||
"automatically not-null, and don't need to be specified.)",
|
||||
Execute: func(s schema.Schema) (ret []CheckResult) {
|
||||
for tablename := range s.Tables {
|
||||
for _, column := range s.Tables[tablename].Columns {
|
||||
if !column.IsNotNull && !column.IsForeignKey && !column.IsPrimaryKey {
|
||||
ret = append(ret, CheckResult{
|
||||
ErrorMsg: "Column should be \"not null\"",
|
||||
TableName: tablename,
|
||||
ColumnName: column.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "require_strict",
|
||||
Explanation: "All tables should be marked as `strict` (must specify column types; types must be int,\n" +
|
||||
"integer, real, text, blob or any). This disallows all 'date' and 'time' column types.\n" +
|
||||
"See more: https://www.sqlite.org/stricttables.html",
|
||||
Execute: func(s schema.Schema) (ret []CheckResult) {
|
||||
for tablename := range s.Tables {
|
||||
if !s.Tables[tablename].IsStrict {
|
||||
ret = append(ret, CheckResult{
|
||||
ErrorMsg: "Table should be marked \"strict\"",
|
||||
TableName: tablename,
|
||||
ColumnName: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "forbid_int_type",
|
||||
Explanation: "All columns should use `integer` type instead of `int`.",
|
||||
Execute: func(s schema.Schema) (ret []CheckResult) {
|
||||
for tablename := range s.Tables {
|
||||
for _, column := range s.Tables[tablename].Columns {
|
||||
if column.Type == "int" {
|
||||
ret = append(ret, CheckResult{
|
||||
ErrorMsg: "Column should use \"integer\" type instead of \"int\"",
|
||||
TableName: tablename,
|
||||
ColumnName: column.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "require_explicit_primary_key",
|
||||
Explanation: "All tables must have a primary key. If it's rowid, it has to be named explicitly.",
|
||||
Execute: func(s schema.Schema) (ret []CheckResult) {
|
||||
tableloop:
|
||||
for tablename := range s.Tables {
|
||||
for _, column := range s.Tables[tablename].Columns {
|
||||
if column.IsPrimaryKey {
|
||||
continue tableloop
|
||||
}
|
||||
}
|
||||
ret = append(ret, CheckResult{
|
||||
ErrorMsg: "Table should declare an explicit primary key",
|
||||
TableName: tablename,
|
||||
ColumnName: "",
|
||||
})
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "require_indexes_for_foreign_keys",
|
||||
Explanation: "Columns referenced by foreign keys must have indexes.",
|
||||
Execute: func(s schema.Schema) (ret []CheckResult) {
|
||||
for tablename := range s.Tables {
|
||||
fk_loop:
|
||||
for _, column := range s.Tables[tablename].Columns {
|
||||
if !column.IsForeignKey {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if target column is a primary key
|
||||
for _, target_col := range s.Tables[column.ForeignKeyTargetTable].Columns {
|
||||
if target_col.Name == column.ForeignKeyTargetColumn && target_col.IsPrimaryKey {
|
||||
continue fk_loop
|
||||
}
|
||||
}
|
||||
|
||||
// Check if target column is at the beginning of any index
|
||||
for _, idx := range s.Indexes {
|
||||
if idx.TableName != column.ForeignKeyTargetTable {
|
||||
continue
|
||||
}
|
||||
for _, idx_col_name := range idx.Columns {
|
||||
if column.ForeignKeyTargetColumn == idx_col_name {
|
||||
continue fk_loop
|
||||
}
|
||||
break // Only look at 1st column
|
||||
}
|
||||
}
|
||||
ret = append(ret, CheckResult{
|
||||
ErrorMsg: "Foreign keys should point to indexed columns",
|
||||
TableName: tablename,
|
||||
ColumnName: column.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user