Compare commits

..

No commits in common. "0371fb4144c2ed9dcd043afc8e624b37688ceab2" and "45c9e15dd31f481935b8d093f68d40419d279da1" have entirely different histories.

16 changed files with 36 additions and 233 deletions

View File

@ -11,7 +11,3 @@ TODO: modified-timestamps
TODO: generator-foreign-keys
- add auto-foreign-key checking blocks to SaveXyz
TODO: migration-structs
- Right now, migrations are strings. Could be a struct with "name", "up" and "down" fields
- Adding a "down" operation enables handling newer DB versions with "down instead of error-out" for development (perhaps a flag)

View File

@ -37,31 +37,6 @@
- focus on testing
- ORM-like affordances (but not actually using an ORM)
## Vendoring vs Package Management vs In-Sourcing
Dependencies are technical debt. See [Loris Cro's talk about "How To Write Better Software with Zig"](https://www.youtube.com/watch?v=AEybWzeAkho).
## Scaffolding
Scaffolding is not boilerplate code, generated code, or library code. It's *starter code* which is *intended to be modified* as needed.
Library code is provided as a pre-made, off-the-shelf solution. If your problem is exactly the one the library is intended to solve, and the library does a good job, you should use it. Lots of big stuff is like this; nobody implements their own HTTP server or SQL engine as part of an application, because the domain is big, stable and standardized. It makes perfect sense to use libraries for this.
Boilerplate code and generated code (the latter being a common solution to the former) are usually indicators of bad abstractions. If there's truly something that needs to be done exactly the same way by rote, every time, then there should be a reusable library for it-- or maybe you're even using the wrong programming language.
Scaffolding isn't either of those, because scaffolding is intended to be *changed*. It's just a starting point as you flesh out your ideas.
Consider [this parable](https://rcrowley.org/2022/rails-django-parable.html) comparing Rails and Django on their initial setup and tutorial. The analogy isn't perfect, but his claim is basically that the original Rails official tutorial left you with a tiny app with almost on code and a huge amount of functionality; but since all the functionality was invisible "magic" provided by Rails, as soon as you want something custom, you're nearly starting from scratch'. By comparison, the Django tutorial produces a large amount of code which makes the abstractions explicit. The author refers to all those extra lines of code as "footholds", from which you can start working.
Scaffolding *begins* as generic boilerplate, but evolves as your application logic becomes more custom and requirements change. One piece of scaffolded code might never change, because the scaffolding was good enough; another piece might be tweaked over time, as you add more to it (or remove parts you don't need); and another piece might change so much that no traces of the original scaffolding existed. One app could contain all three of these.
Scaffolding is intended to make "in-sourcing" your code easier, by getting you to something bare-bones-but-working faster.
## Dynamic vs Static linking
TODO: write about this and why it matters for the GAS stack
## SQLite and ROWID
Tables must be EITHER:

View File

@ -1,7 +0,0 @@
- Urbit
- BCHS stack
- Ruby on Rails
- Hasen Judi's "Data Storage and Retrieval From First Principles": https://hasen.substack.com/p/data-storage-and-retrieval
- Matklad's "Basic Things": https://matklad.github.io/2024/03/22/basic-things.html
- Zig, Andrew Kelley, Loris Cro
- Max Tagher's "8 Lints for your Postgres Schema": https://mercury.com/blog/lints-for-postgres-schema

View File

@ -1,41 +0,0 @@
See: https://guides.rubyonrails.org/v3.2/getting_started.html#getting-up-and-running-quickly-with-scaffolding
# DB
db/migrate/20100207214725_create_posts.rb:
- Migration to create the posts table in your database (your name will include a different timestamp)
app/models/post.rb:
- The Post model
test/unit/post_test.rb:
- Unit testing harness for the posts model
test/fixtures/posts.yml:
- Sample posts for use in testing
# Web
config/routes.rb:
- Edited to include routing information for posts
app/controllers/posts_controller.rb:
- The Posts controller
app/views/posts/index.html.erb:
- A view to display an index of all posts
app/views/posts/edit.html.erb:
- A view to edit an existing post
app/views/posts/show.html.erb:
- A view to display a single post
app/views/posts/new.html.erb:
- A view to create a new post
app/views/posts/_form.html.erb:
- A partial to control the overall look and feel of the form used in edit and new views
test/functional/posts_controller_test.rb:
- Functional testing harness for the posts controller
app/helpers/posts_helper.rb:
- Helper functions to be used from the post views
test/unit/helpers/posts_helper_test.rb:
- Unit testing harness for the posts helper
app/assets/javascripts/posts.js.coffee:
- CoffeeScript for the posts controller
app/assets/stylesheets/posts.css.scss:
- Cascading style sheet for the posts controller
app/assets/stylesheets/scaffolds.css.scss:
- Cascading style sheet to make the scaffolded views look better

View File

@ -1,88 +0,0 @@
# SQLite Schema Rules and Linter
The `sqlite_lint` subcommand enforces some rules that the GAS stack considers best-practices.
All checks are enabled by default. Disabling checks isn't recommended; many GAS stack methodologies assume your schema is designed in accordance with these rules, and will be less effective if you don't follow them.
Currently the only way to disable them is setting an environment variable with the check name in capitals prefixed with `INPUT_`, e.g., `INPUT_REQUIRE_NOT_NULL=false` disables the `require_not_null` check.
```bash
INPUT_REQUIRE_NOT_NULL=false gas sqlite_lint <path/to/schema.sql> # `require_not_null` check will be skipped
```
## Running the linter
```bash
gas sqlite_lint <path/to/schema.sql>
```
## Available Checks
This is a list of currently available checks.
### `require_not_null`
Enforce that all columns should be marked as `not null`, unless they are foreign keys.
**Explanation**:
- Nulls are a common source of unexpected bugs, because they're usually an invalid state but often get created by mistake (e.g., you forgot to set a value). Explicitly disabling nulls prevents such mistakes.
- If the "natural zero value" is a valid value in your application and you explicitly need to distinguish it from "missing data", use an `has_xyz` or `is_xyz_valid` flag of some kind, rather than a nullable field.
- This is usually unnecessary, because the natural zero-values `0` and `""` (empty string) are usually sufficient to indicate "no value". This is called a "sentinel value", or "in-band null value", because you don't need a special data type (null) to declare absence of data.
- Foreign keys are exempt in this check, because `null` is a special value the integrity checker uses to say "this row has no related item".
### `require_strict`
Enforce that all tables should be marked as `strict`.
**Explanation**:
- By default, SQLite tables are very loose with what values they accept, and don't enforce any type checking. "Strict" disables this "looseness", and enforces that inserted values match the stated type of the column.
- "Strict" tables also limit to a small number of column types: `int`, `integer`, `real`, `text`, `blob` or `any`.
- To represent dates / times, use Unix epoch times in milliseconds, and convert to formatted dates (and timezones) only when displaying the value to a user. This is the most portable and least bug-prone method to handle dates.
See more about "strict" tables in SQLite's documentation: <https://sqlite.org/stricttables.html>
### `forbid_int_type`
Enforce that all columns should use `integer` type instead of `int`.
**Explanation**:
- This is an extension of "strict" tables, which allow two redundant integer types, `integer` and `int`. This check standardizes the types further, permitting only `integer`.
### `require_explicit_primary_key`
Enforce that all tables must have an explicitly declared primary key.
**Explanation**:
- All tables need to have a primary key for storage reasons, so if you don't declare one, SQLite will auto-generate a hidden "rowid" column. Making it explicit (rowid or otherwise) improves schema readability.
See more about the special behavior of `rowid` in SQLite's documentation: <https://sqlite.org/lang_createtable.html#rowid>
### `require_explicit_rowid`
Enforce that any table that's not declared `without rowid` has an explicit `rowid integer primary key` column.
**Explanation**:
- In SQLite, all tables implicitly have a `rowid` column unless they are declared `without rowid`. Making it explicit improves schema readability.
See more about the `without rowid` modifier in SQLite's documentation: <https://sqlite.org/withoutrowid.html>
### `forbid_rowid_on_without_rowid_table`
Enforce that `without rowid` tables don't have a rowid column.
**Explanation**:
- This is pretty self explanatory. You can technically give a `without rowid` table a rowid column. But don't.
### `require_indexes_for_foreign_keys`
Enforce that columns referenced by foreign keys must have indexes.
**Explanation**:
- Foreign keys are usually used for `join`s. Joining on un-indexed columns is very slow. Ensuring that all foreign-key-referenced columns have indexes will greatly improve the performance of database operations.

View File

@ -1,3 +0,0 @@
#!/bin/sh
go build -tags fts5 -o gas ./cmd

View File

@ -13,8 +13,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.."
# Compile `gas`
gas="/tmp/gas"
ops/compile.sh
mv gas $gas
go build -o $gas ./cmd
test_project="/memory/test_gasproj"
if [[ -e $test_project ]]; then

View File

@ -13,8 +13,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.."
# Compile `gas`
gas="/tmp/gas"
ops/compile.sh
mv gas $gas
go build -o $gas ./cmd
test_schema_dir="pkg/schema/lint/test_schemas"

View File

@ -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.GoTypeName),
Name: ast.NewIdent(table.TypeName),
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.GoTypeName))}, &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.TypeName))}, &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.GoTypeName),
Name: ast.NewIdent("Save" + tbl.TypeName),
Type: &ast.FuncType{
Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: &ast.StarExpr{X: ast.NewIdent(tbl.GoTypeName)}}}},
Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: &ast.StarExpr{X: ast.NewIdent(tbl.TypeName)}}}},
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.GoTypeName + "ByID"
funcName := "Get" + tbl.TypeName + "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.GoTypeName)}, {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.TypeName)}, {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.GoTypeName)}, ast.NewIdent("ErrNotInDB")}}}},
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CompositeLit{Type: ast.NewIdent(tbl.TypeName)}, 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.GoTypeName)
funcName := "GetAll" + inflection.Plural(tbl.TypeName)
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.GoTypeName)}},
{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: &ast.ArrayType{Elt: ast.NewIdent(tbl.TypeName)}},
}}
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.GoTypeName
funcName := "Delete" + tbl.TypeName
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.GoTypeName)}}}
arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: ast.NewIdent(tbl.TypeName)}}}
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.GoTypeName))},
&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.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.GoTypeName) + "SQLFields")
return ast.NewIdent(strings.ToLower(tbl.TypeName) + "SQLFields")
}

View File

@ -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.GoTypeName),
Name: ast.NewIdent("TestCreateUpdateDelete" + tbl.TypeName),
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.GoTypeName),
Type: ast.NewIdent(tbl.TypeName),
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.GoTypeName),
Fun: ast.NewIdent("TestDB.Save" + tbl.TypeName),
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.GoTypeName + "ByID"),
Fun: ast.NewIdent("TestDB.Get" + tbl.TypeName + "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.GoTypeName),
Fun: ast.NewIdent("TestDB.Save" + tbl.TypeName),
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.GoTypeName + "ByID"),
Fun: ast.NewIdent("TestDB.Get" + tbl.TypeName + "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.GoTypeName),
Fun: ast.NewIdent("TestDB.Delete" + tbl.TypeName),
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.GoTypeName + "ByID"),
Fun: ast.NewIdent("TestDB.Get" + tbl.TypeName + "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.GoTypeName)),
Name: ast.NewIdent("TestGetAll" + inflection.Plural(tbl.TypeName)),
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.GoTypeName)),
Sel: ast.NewIdent("GetAll" + inflection.Plural(tbl.TypeName)),
},
}},
},

View File

@ -23,10 +23,7 @@ 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, tbl := range s.Tables {
if tbl.TableType != "table" {
continue
}
for tablename := range s.Tables {
for _, column := range s.Tables[tablename].Columns {
if !column.IsNotNull && !column.IsForeignKey && !column.IsPrimaryKey {
ret = append(ret, CheckResult{
@ -46,10 +43,7 @@ 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, tbl := range s.Tables {
if tbl.TableType != "table" {
continue
}
for tablename := range s.Tables {
if !s.Tables[tablename].IsStrict {
ret = append(ret, CheckResult{
ErrorMsg: "Table should be marked \"strict\"",
@ -84,10 +78,7 @@ 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, tbl := range s.Tables {
if tbl.TableType != "table" {
continue
}
for tablename := range s.Tables {
for _, column := range s.Tables[tablename].Columns {
if column.IsPrimaryKey {
continue tableloop
@ -170,9 +161,6 @@ 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
}

View File

@ -5,14 +5,6 @@ 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,

View File

@ -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, table_type, is_strict, is_without_rowid from tables`))
PanicIf(db.Select(&tables, `select name, is_strict, is_without_rowid from tables`))
for _, tbl := range tables {
tbl.GoTypeName = TypenameFromTablename(tbl.TableName)
tbl.TypeIDName = tbl.GoTypeName + "ID"
tbl.TypeName = TypenameFromTablename(tbl.TableName)
tbl.TypeIDName = tbl.TypeName + "ID"
tbl.VarName = strings.ToLower(string(tbl.TableName[0]))
PanicIf(db.Select(&tbl.Columns, `select * from columns where table_name = ?`, tbl.TableName))

View File

@ -25,7 +25,7 @@ func TestParseSchema(t *testing.T) {
foods := schema.Tables["foods"]
assert.Equal(foods.TableName, "foods")
assert.Equal(foods.GoTypeName, "Food")
assert.Equal(foods.TypeName, "Food")
assert.Equal(foods.TypeIDName, "FoodID")
assert.Equal(foods.IsStrict, true)
assert.Len(foods.Columns, 20)

View File

@ -22,22 +22,15 @@ 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"`
TableName string `db:"name"`
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
// Name of corresponding model type to be generated
GoTypeName string
VarName string
TypeName string
}
type Index struct {

View File

@ -1,7 +1,7 @@
create temporary view tables as
select l.schema,
l.name,
l.type as table_type,
l.type,
l.wr as is_without_rowid,
l.strict as is_strict
from sqlite_schema s