sqlite_lint: add support for FTS5 and virtual tables
This commit is contained in:
parent
d70cbc1913
commit
0371fb4144
3
ops/compile.sh
Executable file
3
ops/compile.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
go build -tags fts5 -o gas ./cmd
|
||||
@ -13,7 +13,8 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
# Compile `gas`
|
||||
gas="/tmp/gas"
|
||||
go build -o $gas ./cmd
|
||||
ops/compile.sh
|
||||
mv gas $gas
|
||||
|
||||
test_project="/memory/test_gasproj"
|
||||
if [[ -e $test_project ]]; then
|
||||
|
||||
@ -13,7 +13,8 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
# Compile `gas`
|
||||
gas="/tmp/gas"
|
||||
go build -o $gas ./cmd
|
||||
ops/compile.sh
|
||||
mv gas $gas
|
||||
|
||||
test_schema_dir="pkg/schema/lint/test_schemas"
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ func GenerateModelAST(table schema.Table) *ast.GenDecl {
|
||||
return &ast.GenDecl{
|
||||
Tok: token.TYPE,
|
||||
Specs: []ast.Spec{&ast.TypeSpec{
|
||||
Name: ast.NewIdent(table.TypeName),
|
||||
Name: ast.NewIdent(table.GoTypeName),
|
||||
Type: &ast.StructType{Fields: &ast.FieldList{List: fields}},
|
||||
}},
|
||||
}
|
||||
@ -171,7 +171,7 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
Op: token.NEQ,
|
||||
Y: &ast.BasicLit{Kind: token.INT, Value: "1"},
|
||||
},
|
||||
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ExprStmt{X: &ast.CallExpr{Fun: ast.NewIdent("panic"), Args: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("fmt.Errorf"), Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"got %s with ID (%%d), so attempted update, but it doesn't exist\"", strings.ToLower(tbl.TypeName))}, &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")}}}}}}}},
|
||||
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ExprStmt{X: &ast.CallExpr{Fun: ast.NewIdent("panic"), Args: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("fmt.Errorf"), Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"got %s with ID (%%d), so attempted update, but it doesn't exist\"", strings.ToLower(tbl.GoTypeName))}, &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")}}}}}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -181,9 +181,9 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
|
||||
funcDecl := &ast.FuncDecl{
|
||||
Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}},
|
||||
Name: ast.NewIdent("Save" + tbl.TypeName),
|
||||
Name: ast.NewIdent("Save" + tbl.GoTypeName),
|
||||
Type: &ast.FuncType{
|
||||
Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: &ast.StarExpr{X: ast.NewIdent(tbl.TypeName)}}}},
|
||||
Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: &ast.StarExpr{X: ast.NewIdent(tbl.GoTypeName)}}}},
|
||||
Results: nil,
|
||||
},
|
||||
Body: funcBody,
|
||||
@ -194,11 +194,11 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
// GenerateGetItemByIDFunc produces an AST for the `GetXyzByID()` function.
|
||||
// E.g., a table with `table.TypeName = "foods"` will produce a "GetFoodByID()" function.
|
||||
func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
funcName := "Get" + tbl.TypeName + "ByID"
|
||||
funcName := "Get" + tbl.GoTypeName + "ByID"
|
||||
|
||||
recv := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}}
|
||||
arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("id")}, Type: ast.NewIdent(tbl.TypeIDName)}}}
|
||||
result := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: ast.NewIdent(tbl.TypeName)}, {Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}}}
|
||||
result := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: ast.NewIdent(tbl.GoTypeName)}, {Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}}}
|
||||
|
||||
// Use the xyzSQLFields constant in the select query
|
||||
selectExpr := &ast.BinaryExpr{
|
||||
@ -220,7 +220,7 @@ func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
},
|
||||
&ast.IfStmt{
|
||||
Cond: &ast.CallExpr{Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")}, Args: []ast.Expr{ast.NewIdent("err"), &ast.SelectorExpr{X: ast.NewIdent("sql"), Sel: ast.NewIdent("ErrNoRows")}}},
|
||||
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CompositeLit{Type: ast.NewIdent(tbl.TypeName)}, ast.NewIdent("ErrNotInDB")}}}},
|
||||
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CompositeLit{Type: ast.NewIdent(tbl.GoTypeName)}, ast.NewIdent("ErrNotInDB")}}}},
|
||||
},
|
||||
&ast.ReturnStmt{},
|
||||
},
|
||||
@ -238,12 +238,12 @@ func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
// GenerateGetAllItemsFunc produces an AST for the `GetAllXyzs()` function.
|
||||
// E.g., a table with `table.TypeName = "foods"` will produce a "GetAllFoods()" function.
|
||||
func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
funcName := "GetAll" + inflection.Plural(tbl.TypeName)
|
||||
funcName := "GetAll" + inflection.Plural(tbl.GoTypeName)
|
||||
recv := &ast.FieldList{List: []*ast.Field{
|
||||
{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")},
|
||||
}}
|
||||
result := &ast.FieldList{List: []*ast.Field{
|
||||
{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: &ast.ArrayType{Elt: ast.NewIdent(tbl.TypeName)}},
|
||||
{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: &ast.ArrayType{Elt: ast.NewIdent(tbl.GoTypeName)}},
|
||||
}}
|
||||
|
||||
selectCall := &ast.CallExpr{
|
||||
@ -291,9 +291,9 @@ func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
// GenerateDeleteItemFunc produces an AST for the `DeleteXyz()` function.
|
||||
// E.g., a table with `table.TypeName = "foods"` will produce a "DeleteFood()" function.
|
||||
func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
funcName := "Delete" + tbl.TypeName
|
||||
funcName := "Delete" + tbl.GoTypeName
|
||||
recv := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}}
|
||||
arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: ast.NewIdent(tbl.TypeName)}}}
|
||||
arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: ast.NewIdent(tbl.GoTypeName)}}}
|
||||
|
||||
funcBody := &ast.BlockStmt{
|
||||
List: []ast.Stmt{
|
||||
@ -328,7 +328,7 @@ func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
|
||||
Args: []ast.Expr{&ast.CallExpr{
|
||||
Fun: ast.NewIdent("fmt.Errorf"),
|
||||
Args: []ast.Expr{
|
||||
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"tried to delete %s with ID (%%d) but it doesn't exist\"", strings.ToLower(tbl.TypeName))},
|
||||
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"tried to delete %s with ID (%%d) but it doesn't exist\"", strings.ToLower(tbl.GoTypeName))},
|
||||
&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")},
|
||||
},
|
||||
}},
|
||||
@ -372,5 +372,5 @@ func GenerateSQLFieldsConst(tbl schema.Table) *ast.GenDecl {
|
||||
// ---------------
|
||||
|
||||
func SQLFieldsConstIdent(tbl schema.Table) *ast.Ident {
|
||||
return ast.NewIdent(strings.ToLower(tbl.TypeName) + "SQLFields")
|
||||
return ast.NewIdent(strings.ToLower(tbl.GoTypeName) + "SQLFields")
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
description2 := `"a big item"`
|
||||
|
||||
testCreateUpdateDelete := &ast.FuncDecl{
|
||||
Name: ast.NewIdent("TestCreateUpdateDelete" + tbl.TypeName),
|
||||
Name: ast.NewIdent("TestCreateUpdateDelete" + tbl.GoTypeName),
|
||||
Type: &ast.FuncType{
|
||||
Params: &ast.FieldList{
|
||||
List: []*ast.Field{{
|
||||
@ -106,7 +106,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
Lhs: []ast.Expr{testObj},
|
||||
Tok: token.DEFINE,
|
||||
Rhs: []ast.Expr{&ast.CompositeLit{
|
||||
Type: ast.NewIdent(tbl.TypeName),
|
||||
Type: ast.NewIdent(tbl.GoTypeName),
|
||||
Elts: []ast.Expr{
|
||||
&ast.KeyValueExpr{
|
||||
Key: fieldName,
|
||||
@ -118,7 +118,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
|
||||
// TestDB.SaveItem(&item)
|
||||
&ast.ExprStmt{X: &ast.CallExpr{
|
||||
Fun: ast.NewIdent("TestDB.Save" + tbl.TypeName),
|
||||
Fun: ast.NewIdent("TestDB.Save" + tbl.GoTypeName),
|
||||
Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: testObj}},
|
||||
}},
|
||||
|
||||
@ -135,7 +135,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
Rhs: []ast.Expr{&ast.CallExpr{
|
||||
Fun: ast.NewIdent("Must"),
|
||||
Args: []ast.Expr{&ast.CallExpr{
|
||||
Fun: ast.NewIdent("TestDB.Get" + tbl.TypeName + "ByID"),
|
||||
Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"),
|
||||
Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
|
||||
}},
|
||||
}},
|
||||
@ -160,7 +160,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
|
||||
// TestDB.SaveItem(&item)
|
||||
&ast.ExprStmt{X: &ast.CallExpr{
|
||||
Fun: ast.NewIdent("TestDB.Save" + tbl.TypeName),
|
||||
Fun: ast.NewIdent("TestDB.Save" + tbl.GoTypeName),
|
||||
Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: testObj}},
|
||||
}},
|
||||
|
||||
@ -171,7 +171,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
Rhs: []ast.Expr{&ast.CallExpr{
|
||||
Fun: ast.NewIdent("Must"),
|
||||
Args: []ast.Expr{&ast.CallExpr{
|
||||
Fun: ast.NewIdent("TestDB.Get" + tbl.TypeName + "ByID"),
|
||||
Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"),
|
||||
Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
|
||||
}},
|
||||
}},
|
||||
@ -189,7 +189,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
|
||||
// TestDB.DeleteItem(item)
|
||||
&ast.ExprStmt{X: &ast.CallExpr{
|
||||
Fun: ast.NewIdent("TestDB.Delete" + tbl.TypeName),
|
||||
Fun: ast.NewIdent("TestDB.Delete" + tbl.GoTypeName),
|
||||
Args: []ast.Expr{testObj},
|
||||
}},
|
||||
|
||||
@ -198,7 +198,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")},
|
||||
Tok: token.DEFINE,
|
||||
Rhs: []ast.Expr{&ast.CallExpr{
|
||||
Fun: ast.NewIdent("TestDB.Get" + tbl.TypeName + "ByID"),
|
||||
Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"),
|
||||
Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
|
||||
}},
|
||||
},
|
||||
@ -217,7 +217,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
}
|
||||
|
||||
testGetAll := &ast.FuncDecl{
|
||||
Name: ast.NewIdent("TestGetAll" + inflection.Plural(tbl.TypeName)),
|
||||
Name: ast.NewIdent("TestGetAll" + inflection.Plural(tbl.GoTypeName)),
|
||||
Type: &ast.FuncType{Params: &ast.FieldList{List: []*ast.Field{
|
||||
{Names: []*ast.Ident{ast.NewIdent("t")}, Type: &ast.StarExpr{X: ast.NewIdent("testing.T")}},
|
||||
}}, Results: nil},
|
||||
@ -229,7 +229,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
|
||||
Rhs: []ast.Expr{&ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{
|
||||
X: ast.NewIdent("TestDB"),
|
||||
Sel: ast.NewIdent("GetAll" + inflection.Plural(tbl.TypeName)),
|
||||
Sel: ast.NewIdent("GetAll" + inflection.Plural(tbl.GoTypeName)),
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
||||
@ -23,7 +23,10 @@ var Checks = []Check{
|
||||
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 tablename, tbl := range s.Tables {
|
||||
if tbl.TableType != "table" {
|
||||
continue
|
||||
}
|
||||
for _, column := range s.Tables[tablename].Columns {
|
||||
if !column.IsNotNull && !column.IsForeignKey && !column.IsPrimaryKey {
|
||||
ret = append(ret, CheckResult{
|
||||
@ -43,7 +46,10 @@ var Checks = []Check{
|
||||
"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 {
|
||||
for tablename, tbl := range s.Tables {
|
||||
if tbl.TableType != "table" {
|
||||
continue
|
||||
}
|
||||
if !s.Tables[tablename].IsStrict {
|
||||
ret = append(ret, CheckResult{
|
||||
ErrorMsg: "Table should be marked \"strict\"",
|
||||
@ -78,7 +84,10 @@ var Checks = []Check{
|
||||
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 tablename, tbl := range s.Tables {
|
||||
if tbl.TableType != "table" {
|
||||
continue
|
||||
}
|
||||
for _, column := range s.Tables[tablename].Columns {
|
||||
if column.IsPrimaryKey {
|
||||
continue tableloop
|
||||
@ -161,6 +170,9 @@ var Checks = []Check{
|
||||
Execute: func(s schema.Schema) (ret []CheckResult) {
|
||||
tbl_loop:
|
||||
for tblName, tbl := range s.Tables {
|
||||
if tbl.TableType != "table" {
|
||||
continue
|
||||
}
|
||||
if tbl.IsWithoutRowid {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -5,6 +5,14 @@ create table stuff (
|
||||
) strict;
|
||||
create index index_stuff_amount on stuff (amount);
|
||||
|
||||
create virtual table stuff_fts using fts5(
|
||||
data,
|
||||
content='stuff',
|
||||
content_rowid='rowid',
|
||||
tokenize='trigram'
|
||||
);
|
||||
|
||||
|
||||
create table stuff2 (
|
||||
weird_pk integer primary key,
|
||||
label text not null unique,
|
||||
|
||||
@ -38,10 +38,10 @@ func SchemaFromDB(db *sqlx.DB) Schema {
|
||||
ret := Schema{Tables: map[string]Table{}, Indexes: map[string]Index{}}
|
||||
|
||||
var tables []Table
|
||||
PanicIf(db.Select(&tables, `select name, is_strict, is_without_rowid from tables`))
|
||||
PanicIf(db.Select(&tables, `select name, table_type, is_strict, is_without_rowid from tables`))
|
||||
for _, tbl := range tables {
|
||||
tbl.TypeName = TypenameFromTablename(tbl.TableName)
|
||||
tbl.TypeIDName = tbl.TypeName + "ID"
|
||||
tbl.GoTypeName = TypenameFromTablename(tbl.TableName)
|
||||
tbl.TypeIDName = tbl.GoTypeName + "ID"
|
||||
tbl.VarName = strings.ToLower(string(tbl.TableName[0]))
|
||||
|
||||
PanicIf(db.Select(&tbl.Columns, `select * from columns where table_name = ?`, tbl.TableName))
|
||||
|
||||
@ -25,7 +25,7 @@ func TestParseSchema(t *testing.T) {
|
||||
|
||||
foods := schema.Tables["foods"]
|
||||
assert.Equal(foods.TableName, "foods")
|
||||
assert.Equal(foods.TypeName, "Food")
|
||||
assert.Equal(foods.GoTypeName, "Food")
|
||||
assert.Equal(foods.TypeIDName, "FoodID")
|
||||
assert.Equal(foods.IsStrict, true)
|
||||
assert.Len(foods.Columns, 20)
|
||||
|
||||
@ -23,14 +23,21 @@ func (c Column) IsNullableForeignKey() bool {
|
||||
// Table is a single SQLite table.
|
||||
type Table struct {
|
||||
TableName string `db:"name"`
|
||||
|
||||
// One of "table", "view", "shadow", or "virtual"
|
||||
TableType string `db:"table_type"`
|
||||
IsStrict bool `db:"is_strict"`
|
||||
IsWithoutRowid bool `db:"is_without_rowid"`
|
||||
|
||||
Columns []Column
|
||||
|
||||
TypeIDName string
|
||||
|
||||
// Default variable name for variables of this type to use when generating Go code
|
||||
VarName string
|
||||
TypeName string
|
||||
|
||||
// Name of corresponding model type to be generated
|
||||
GoTypeName string
|
||||
}
|
||||
|
||||
type Index struct {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
create temporary view tables as
|
||||
select l.schema,
|
||||
l.name,
|
||||
l.type,
|
||||
l.type as table_type,
|
||||
l.wr as is_without_rowid,
|
||||
l.strict as is_strict
|
||||
from sqlite_schema s
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user