diff --git a/ops/compile.sh b/ops/compile.sh new file mode 100755 index 0000000..488281c --- /dev/null +++ b/ops/compile.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +go build -tags fts5 -o gas ./cmd diff --git a/ops/gas_init_test.sh b/ops/gas_init_test.sh index 2eddec2..5d1c2ef 100755 --- a/ops/gas_init_test.sh +++ b/ops/gas_init_test.sh @@ -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 diff --git a/ops/sqlite_lint_test.sh b/ops/sqlite_lint_test.sh index 7c1c587..f6de2d2 100755 --- a/ops/sqlite_lint_test.sh +++ b/ops/sqlite_lint_test.sh @@ -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" diff --git a/pkg/codegen/modelgenerate/generate_model.go b/pkg/codegen/modelgenerate/generate_model.go index ea7b244..5a0b75c 100644 --- a/pkg/codegen/modelgenerate/generate_model.go +++ b/pkg/codegen/modelgenerate/generate_model.go @@ -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") } diff --git a/pkg/codegen/modelgenerate/generate_testfile.go b/pkg/codegen/modelgenerate/generate_testfile.go index fbc1228..9fc9b75 100644 --- a/pkg/codegen/modelgenerate/generate_testfile.go +++ b/pkg/codegen/modelgenerate/generate_testfile.go @@ -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)), }, }}, }, diff --git a/pkg/schema/lint/checks.go b/pkg/schema/lint/checks.go index 545a2cc..be52abb 100644 --- a/pkg/schema/lint/checks.go +++ b/pkg/schema/lint/checks.go @@ -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 } diff --git a/pkg/schema/lint/test_schemas/success.sql b/pkg/schema/lint/test_schemas/success.sql index c0bd17d..ec98087 100644 --- a/pkg/schema/lint/test_schemas/success.sql +++ b/pkg/schema/lint/test_schemas/success.sql @@ -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, diff --git a/pkg/schema/parse.go b/pkg/schema/parse.go index 113a895..c2f5876 100644 --- a/pkg/schema/parse.go +++ b/pkg/schema/parse.go @@ -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)) diff --git a/pkg/schema/parse_test.go b/pkg/schema/parse_test.go index 8dd102f..7e652f2 100644 --- a/pkg/schema/parse_test.go +++ b/pkg/schema/parse_test.go @@ -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) diff --git a/pkg/schema/table.go b/pkg/schema/table.go index aca4f65..3763e76 100644 --- a/pkg/schema/table.go +++ b/pkg/schema/table.go @@ -22,15 +22,22 @@ func (c Column) IsNullableForeignKey() bool { // Table is a single SQLite table. type Table struct { - TableName string `db:"name"` + 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 - VarName string - TypeName string + + // Default variable name for variables of this type to use when generating Go code + VarName string + + // Name of corresponding model type to be generated + GoTypeName string } type Index struct { diff --git a/pkg/schema/views.sql b/pkg/schema/views.sql index d868e28..2b6825a 100644 --- a/pkg/schema/views.sql +++ b/pkg/schema/views.sql @@ -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