From 9f4c86f39f4552b6cdf156910dfe5f596c151a86 Mon Sep 17 00:00:00 2001 From: jdl Date: Thu, 25 Jun 2026 13:14:06 +0200 Subject: [PATCH] WIP: initial commit --- README.md | 21 ++- cmd/sqlgen/main.go | 7 + gen.go.tmpl | 216 ++++++++++++++++++++++++++ go.mod | 3 + main.go | 47 ++++++ parse.go | 144 ++++++++++++++++++ parse_test.go | 45 ++++++ schema.go | 277 ++++++++++++++++++++++++++++++++++ template.go | 40 +++++ test-files/TestParse/000.def | 3 + test-files/TestParse/000.json | 17 +++ test-files/TestParse/001.def | 4 + test-files/TestParse/001.json | 22 +++ test-files/TestParse/002.def | 6 + test-files/TestParse/002.json | 33 ++++ test-files/TestParse/003.def | 12 ++ test-files/TestParse/003.json | 61 ++++++++ test-files/TestParse/004.def | 13 ++ test-files/TestParse/004.json | 66 ++++++++ 19 files changed, 1036 insertions(+), 1 deletion(-) create mode 100644 cmd/sqlgen/main.go create mode 100644 gen.go.tmpl create mode 100644 go.mod create mode 100644 main.go create mode 100644 parse.go create mode 100644 parse_test.go create mode 100644 schema.go create mode 100644 template.go create mode 100644 test-files/TestParse/000.def create mode 100644 test-files/TestParse/000.json create mode 100644 test-files/TestParse/001.def create mode 100644 test-files/TestParse/001.json create mode 100644 test-files/TestParse/002.def create mode 100644 test-files/TestParse/002.json create mode 100644 test-files/TestParse/003.def create mode 100644 test-files/TestParse/003.json create mode 100644 test-files/TestParse/004.def create mode 100644 test-files/TestParse/004.json diff --git a/README.md b/README.md index 994e1ea..360f886 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ # sqlgen -SQL crud generator for Go. \ No newline at end of file +## Installing + +``` +go install git.crumpington.com/lib/go/sqlgen/cmd/sqlgen@latest +``` + +## Usage + +``` +sqlgen [driver] [defs-path] [output-path] +``` + +## File Format + +``` +TABLE [sql-name] OF [go-type] ( + [sql-column] [go-type] , + ... +); +``` diff --git a/cmd/sqlgen/main.go b/cmd/sqlgen/main.go new file mode 100644 index 0000000..9486806 --- /dev/null +++ b/cmd/sqlgen/main.go @@ -0,0 +1,7 @@ +package main + +import "git.crumpington.com/app/sqlgen" + +func main() { + sqlgen.Main() +} diff --git a/gen.go.tmpl b/gen.go.tmpl new file mode 100644 index 0000000..9eea406 --- /dev/null +++ b/gen.go.tmpl @@ -0,0 +1,216 @@ +package {{.PackageName}} + +import ( + "database/sql" + "iter" +) + +type TX interface { + Exec(query string, args ...any) (sql.Result, error) + Query(query string, args ...any) (*sql.Rows, error) + QueryRow(query string, args ...any) *sql.Row +} + +{{range .Schema.Tables}} + +// ---------------------------------------------------------------------------- +// Table: {{.Name}} +// ---------------------------------------------------------------------------- + +type {{.Type}} struct { + {{- range .Columns}} + {{.Name}} {{.Type}}{{end}} +} + +const {{.Type}}_SelectQuery = "{{.SelectQuery}}" + +{{if not .NoInsert -}} + +func {{.Type}}_Insert( + tx TX, + row *{{.Type}}, +) (err error) { + {{.Type}}_Sanitize(row) + if err = {{.Type}}_Validate(row); err != nil { + return err + } + + _, err = tx.Exec("{{.InsertQuery}}", {{.InsertArgs}}) + return err +} + +{{- end}} {{/* if not .NoInsert */}} + +{{if not .NoUpdate -}} + +{{if .UpdateCols -}} + +func {{.Type}}_Update( + tx TX, + row *{{.Type}}, +) (err error) { + {{.Type}}_Sanitize(row) + if err = {{.Type}}_Validate(row); err != nil { + return err + } + + result, err := tx.Exec("{{.UpdateQuery}}", {{.UpdateArgs}}) + if err != nil { + return err + } + + n, err := result.RowsAffected() + if err != nil { + return err + } + switch n { + case 0: + return sql.ErrNoRows + case 1: + return nil + default: + panic("multiple rows updated") + } +} +{{- end}} + +{{if .UpdateFullCols -}} + +func {{.Type}}_UpdateFull( + tx TX, + row *{{.Type}}, +) (err error) { + {{.Type}}_Sanitize(row) + if err = {{.Type}}_Validate(row); err != nil { + return err + } + + result, err := tx.Exec("{{.UpdateFullQuery}}", {{.UpdateFullArgs}}) + if err != nil { + return err + } + + n, err := result.RowsAffected() + if err != nil { + return err + } + switch n { + case 0: + return sql.ErrNoRows + case 1: + return nil + default: + panic("multiple rows updated") + } +} +{{- end}} + + +{{- end}} {{/* if not .NoUpdate */}} + +{{if not .NoDelete -}} + +func {{.Type}}_Delete( + tx TX, + {{.PKFunctionArgs -}} +) (err error) { + result, err := tx.Exec("{{.DeleteQuery}}", {{.DeleteArgs}}) + if err != nil { + return err + } + + n, err := result.RowsAffected() + if err != nil { + return err + } + switch n { + case 0: + return sql.ErrNoRows + case 1: + return nil + default: + panic("multiple rows deleted") + } +} + +{{- end}} + +func {{.Type}}_Get( + tx TX, + {{.PKFunctionArgs -}} +) ( + row *{{.Type}}, + err error, +) { + row = &{{.Type}}{} + r := tx.QueryRow("{{.GetQuery}}", {{.DeleteArgs}}) + if err = r.Scan({{.ScanArgs}}); err != nil { + row = nil + } + return +} + + +func {{.Type}}_GetWhere( + tx TX, + query string, + args ...any, +) ( + row *{{.Type}}, + err error, +) { + row = &{{.Type}}{} + r := tx.QueryRow(query, args...) + if err = r.Scan({{.ScanArgs}}); err != nil { + row = nil + } + return +} + + + +func {{.Type}}_Iterate( + tx TX, + query string, + args ...any, +) ( + iter.Seq2[*{{.Type}}, error], +) { + rows, err := tx.Query(query, args...) + if err != nil { + return func(yield func(*{{.Type}}, error) bool) { + yield(nil, err) + } + } + + return func(yield func(*{{.Type}}, error) bool) { + defer rows.Close() + for rows.Next() { + row := &{{.Type}}{} + err := rows.Scan({{.ScanArgs}}) + if !yield(row, err) { + return + } + } + } +} + +func {{.Type}}_List( + tx TX, + query string, + args ...any, +) ( + l []*{{.Type}}, + err error, +) { + for row, err := range {{.Type}}_Iterate(tx, query, args...) { + if err != nil { + return nil, err + } + l = append(l, row) + } + return l, nil +} + + +{{end}} {{/* range .Schema.Tables */}} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..135cb85 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.crumpington.com/app/sqlgen + +go 1.25.1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..76f9885 --- /dev/null +++ b/main.go @@ -0,0 +1,47 @@ +package sqlgen + +import ( + "fmt" + "os" +) + +const ( + DRIVER_SQLITE = "sqlite" + DRIVER_POSTGRES = "postgres" +) + +func Main() { + usage := func() { + fmt.Fprintf(os.Stderr, ` +%s DRIVER DEFS_PATH OUTPUT_PATH + +Drivers are one of: %s %s +`, + os.Args[0], DRIVER_SQLITE, DRIVER_POSTGRES) + os.Exit(1) + } + + if len(os.Args) != 4 { + usage() + } + + var ( + driver = os.Args[1] + defsPath = os.Args[2] + outputPath = os.Args[3] + ) + + switch driver { + case DRIVER_SQLITE, DRIVER_POSTGRES: + // OK + default: + fmt.Fprintf(os.Stderr, "Unknown driver: %s", driver) + usage() + } + + err := render(driver, defsPath, outputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v", err) + os.Exit(1) + } +} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..1d974f6 --- /dev/null +++ b/parse.go @@ -0,0 +1,144 @@ +package sqlgen + +import ( + "errors" + "os" + "strings" +) + +func parsePath(driver, filePath string) (*schema, error) { + fileBytes, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + return parseBytes(driver, fileBytes) +} + +func parseBytes(driver string, fileBytes []byte) (*schema, error) { + s := string(fileBytes) + for _, c := range []string{",", "(", ")", ";"} { + s = strings.ReplaceAll(s, c, " "+c+" ") + } + + var ( + tokens = strings.Fields(s) + schema = &schema{} + err error + ) + + for len(tokens) > 0 { + switch tokens[0] { + case "TABLE": + tokens, err = parseTable(driver, schema, tokens) + if err != nil { + return nil, err + } + + default: + return nil, errors.New("invalid token: " + tokens[0]) + } + } + + return schema, nil +} + +func parseTable(driver string, schema *schema, tokens []string) ([]string, error) { + tokens = tokens[1:] + if len(tokens) < 3 { + return tokens, errors.New("incomplete table definition") + } + if tokens[1] != "OF" { + return tokens, errors.New("expected OF in table definition") + } + + table := &table{ + driver: driver, + Name: tokens[0], + Type: tokens[2], + } + schema.Tables = append(schema.Tables, table) + + tokens = tokens[3:] + + if len(tokens) == 0 { + return tokens, errors.New("missing table definition body") + } + + for len(tokens) > 0 { + switch tokens[0] { + case "NoInsert": + table.NoInsert = true + tokens = tokens[1:] + case "NoUpdate": + table.NoUpdate = true + tokens = tokens[1:] + case "NoDelete": + table.NoDelete = true + tokens = tokens[1:] + case "(": + return parseTableBody(table, tokens[1:]) + default: + return tokens, errors.New("unexpected token in table definition: " + tokens[0]) + } + } + + return tokens, errors.New("incomplete table definition") +} + +func parseTableBody(table *table, tokens []string) ([]string, error) { + var err error + + for len(tokens) > 0 && tokens[0] != ";" { + tokens, err = parseTableColumn(table, tokens) + if err != nil { + return tokens, err + } + } + + if len(tokens) < 1 || tokens[0] != ";" { + return tokens, errors.New("incomplete table column definitions") + } + + return tokens[1:], nil +} + +func parseTableColumn(table *table, tokens []string) ([]string, error) { + if len(tokens) < 2 { + return tokens, errors.New("incomplete column definition") + } + column := &column{ + Name: tokens[0], + Type: tokens[1], + SqlName: tokens[0], + } + table.Columns = append(table.Columns, column) + + tokens = tokens[2:] + for len(tokens) > 0 && tokens[0] != "," && tokens[0] != ")" { + switch tokens[0] { + case "AS": + if len(tokens) < 2 { + return tokens, errors.New("incomplete AS clause in column definition") + } + column.Name = tokens[1] + tokens = tokens[2:] + case "PK": + column.PK = true + tokens = tokens[1:] + case "NoInsert": + column.NoInsert = true + tokens = tokens[1:] + case "NoUpdate": + column.NoUpdate = true + tokens = tokens[1:] + default: + return tokens, errors.New("unexpected token in column definition: " + tokens[0]) + } + } + + if len(tokens) == 0 { + return tokens, errors.New("incomplete column definition") + } + + return tokens[1:], nil +} diff --git a/parse_test.go b/parse_test.go new file mode 100644 index 0000000..0e08c22 --- /dev/null +++ b/parse_test.go @@ -0,0 +1,45 @@ +package sqlgen + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "strings" + "testing" +) + +func TestParse(t *testing.T) { + toString := func(v any) string { + txt, _ := json.MarshalIndent(v, "", " ") + return string(txt) + } + + paths, err := filepath.Glob("test-files/TestParse/*.def") + if err != nil { + t.Fatal(err) + } + + for _, defPath := range paths { + t.Run(filepath.Base(defPath), func(t *testing.T) { + parsed, err := parsePath("", defPath) + if err != nil { + t.Fatal(err) + } + + b, err := os.ReadFile(strings.TrimSuffix(defPath, "def") + "json") + if err != nil { + t.Fatal(err) + } + + expected := &schema{} + if err := json.Unmarshal(b, expected); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(parsed, expected) { + t.Fatalf("%s != %s", toString(parsed), toString(expected)) + } + }) + } +} diff --git a/schema.go b/schema.go new file mode 100644 index 0000000..ba8516f --- /dev/null +++ b/schema.go @@ -0,0 +1,277 @@ +package sqlgen + +import ( + "fmt" + "strings" +) + +type schema struct { + Tables []*table +} + +type table struct { + driver string // + Name string // Name in SQL + Type string // Go type + NoInsert bool + NoUpdate bool + NoDelete bool + Columns []*column +} + +type column struct { + Name string + Type string + SqlName string // Defaults to Name + PK bool // PK won't be updated + NoInsert bool + NoUpdate bool // Don't update column in update function +} + +// ---------------------------------------------------------------------------- + +func (t *table) translateQuery(in string) string { + if t.driver == DRIVER_SQLITE { + return in + } + + i := 1 + for strings.Contains(in, "?") { + in = strings.Replace(in, "?", fmt.Sprintf("$%d", i), 1) + i++ + } + return in +} + +func (t *table) colSQLNames() []string { + names := make([]string, len(t.Columns)) + for i := range names { + names[i] = t.Columns[i].SqlName + } + return names +} + +func (t *table) SelectQuery() string { + return fmt.Sprintf(`SELECT %s FROM %s`, + strings.Join(t.colSQLNames(), ","), + t.Name) +} + +func (t *table) insertCols() (cols []*column) { + for _, c := range t.Columns { + if !c.NoInsert { + cols = append(cols, c) + } + } + return cols +} + +func (t *table) InsertQuery() string { + cols := t.insertCols() + b := &strings.Builder{} + b.WriteString(`INSERT INTO `) + b.WriteString(t.Name) + b.WriteString(`(`) + for i, c := range cols { + if i != 0 { + b.WriteString(`,`) + } + b.WriteString(c.SqlName) + } + b.WriteString(`) VALUES(`) + for i := range cols { + if i != 0 { + b.WriteString(`,`) + } + b.WriteString(`?`) + } + b.WriteString(`)`) + return t.translateQuery(b.String()) +} + +func (t *table) InsertArgs() string { + args := []string{} + for i, col := range t.Columns { + if !col.NoInsert { + args = append(args, "row."+t.Columns[i].Name) + } + } + return strings.Join(args, ", ") +} + +func (t *table) UpdateCols() (cols []*column) { + for _, c := range t.Columns { + if !(c.PK || c.NoUpdate) { + cols = append(cols, c) + } + } + return cols +} + +func (t *table) UpdateQuery() string { + cols := t.UpdateCols() + + b := &strings.Builder{} + b.WriteString(`UPDATE `) + b.WriteString(t.Name + ` SET `) + for i, col := range cols { + if i != 0 { + b.WriteByte(',') + } + b.WriteString(col.SqlName + `=?`) + } + + b.WriteString(` WHERE`) + + for i, c := range t.pkCols() { + if i != 0 { + b.WriteString(` AND`) + } + b.WriteString(` ` + c.SqlName + `=?`) + } + + return t.translateQuery(b.String()) +} + +func (t *table) UpdateArgs() string { + cols := t.UpdateCols() + + b := &strings.Builder{} + for i, col := range cols { + if i != 0 { + b.WriteString(`, `) + } + b.WriteString("row." + col.Name) + } + for _, col := range t.pkCols() { + b.WriteString(", row." + col.Name) + } + return b.String() +} + +func (t *table) UpdateFullCols() (cols []*column) { + for _, c := range t.Columns { + if !c.PK { + cols = append(cols, c) + } + } + return cols +} + +func (t *table) UpdateFullQuery() string { + cols := t.UpdateFullCols() + + b := &strings.Builder{} + b.WriteString(`UPDATE `) + b.WriteString(t.Name + ` SET `) + for i, col := range cols { + if i != 0 { + b.WriteByte(',') + } + b.WriteString(col.SqlName + `=?`) + } + + b.WriteString(` WHERE`) + + for i, c := range t.pkCols() { + if i != 0 { + b.WriteString(` AND`) + } + b.WriteString(` ` + c.SqlName + `=?`) + } + + return t.translateQuery(b.String()) +} + +func (t *table) UpdateFullArgs() string { + cols := t.UpdateFullCols() + + b := &strings.Builder{} + for i, col := range cols { + if i != 0 { + b.WriteString(`, `) + } + b.WriteString("row." + col.Name) + } + + for _, col := range t.pkCols() { + b.WriteString(", row." + col.Name) + } + + return b.String() +} + +func (t *table) pkCols() (cols []*column) { + for _, c := range t.Columns { + if c.PK { + cols = append(cols, c) + } + } + return cols +} + +func (t *table) PKFunctionArgs() string { + b := &strings.Builder{} + for _, col := range t.pkCols() { + b.WriteString(col.Name) + b.WriteString(` `) + b.WriteString(col.Type) + b.WriteString(",\n") + } + return b.String() +} + +func (t *table) DeleteQuery() string { + cols := t.pkCols() + + b := &strings.Builder{} + b.WriteString(`DELETE FROM `) + b.WriteString(t.Name) + b.WriteString(` WHERE `) + + for i, col := range cols { + if i != 0 { + b.WriteString(` AND `) + } + b.WriteString(col.SqlName) + b.WriteString(`=?`) + } + return t.translateQuery(b.String()) +} + +func (t *table) DeleteArgs() string { + cols := t.pkCols() + b := &strings.Builder{} + + for i, col := range cols { + if i != 0 { + b.WriteString(`,`) + } + b.WriteString(col.Name) + } + return b.String() +} + +func (t *table) GetQuery() string { + b := &strings.Builder{} + b.WriteString(t.SelectQuery()) + b.WriteString(` WHERE `) + for i, col := range t.pkCols() { + if i != 0 { + b.WriteString(` AND `) + } + b.WriteString(col.SqlName + `=?`) + } + return t.translateQuery(b.String()) +} + +func (t *table) ScanArgs() string { + b := &strings.Builder{} + for i, col := range t.Columns { + if i != 0 { + b.WriteString(`, `) + } + b.WriteString(`&row.` + col.Name) + } + return b.String() +} diff --git a/template.go b/template.go new file mode 100644 index 0000000..19dc50e --- /dev/null +++ b/template.go @@ -0,0 +1,40 @@ +package sqlgen + +import ( + _ "embed" + "os" + "os/exec" + "path/filepath" + "text/template" +) + +//go:embed gen.go.tmpl +var fileTemplate string + +func render(driver, schemaPath, outputPath string) (err error) { + outputPath, err = filepath.Abs(outputPath) + if err != nil { + return err + } + sch, err := parsePath(driver, schemaPath) + if err != nil { + return err + } + + tmpl := template.Must(template.New("").Parse(fileTemplate)) + fOut, err := os.Create(outputPath) + if err != nil { + return err + } + defer fOut.Close() + + err = tmpl.Execute(fOut, struct { + PackageName string + Schema *schema + }{filepath.Base(filepath.Dir(outputPath)), sch}) + if err != nil { + return err + } + + return exec.Command("gofmt", "-w", outputPath).Run() +} diff --git a/test-files/TestParse/000.def b/test-files/TestParse/000.def new file mode 100644 index 0000000..ca57f1f --- /dev/null +++ b/test-files/TestParse/000.def @@ -0,0 +1,3 @@ +TABLE users OF User NoDelete ( + user_id string AS UserID PK +); \ No newline at end of file diff --git a/test-files/TestParse/000.json b/test-files/TestParse/000.json new file mode 100644 index 0000000..596727c --- /dev/null +++ b/test-files/TestParse/000.json @@ -0,0 +1,17 @@ +{ + "Tables": [ + { + "Name": "users", + "Type": "User", + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + } + ] + } + ] +} diff --git a/test-files/TestParse/001.def b/test-files/TestParse/001.def new file mode 100644 index 0000000..1ddc7b8 --- /dev/null +++ b/test-files/TestParse/001.def @@ -0,0 +1,4 @@ +TABLE users OF User NoDelete ( + user_id string AS UserID PK, + email string AS Email NoUpdate +); \ No newline at end of file diff --git a/test-files/TestParse/001.json b/test-files/TestParse/001.json new file mode 100644 index 0000000..ded27f4 --- /dev/null +++ b/test-files/TestParse/001.json @@ -0,0 +1,22 @@ +{ + "Tables": [ + { + "Name": "users", + "Type": "User", + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + }, { + "Name": "Email", + "Type": "string", + "SqlName": "email", + "NoUpdate": true + } + ] + } + ] +} diff --git a/test-files/TestParse/002.def b/test-files/TestParse/002.def new file mode 100644 index 0000000..019e3b5 --- /dev/null +++ b/test-files/TestParse/002.def @@ -0,0 +1,6 @@ +TABLE users OF User NoDelete ( + user_id string AS UserID PK, + email string AS Email NoUpdate, + name string AS Name NoInsert, + admin bool AS Admin NoInsert NoUpdate +); diff --git a/test-files/TestParse/002.json b/test-files/TestParse/002.json new file mode 100644 index 0000000..2693687 --- /dev/null +++ b/test-files/TestParse/002.json @@ -0,0 +1,33 @@ +{ + "Tables": [ + { + "Name": "users", + "Type": "User", + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + }, { + "Name": "Email", + "Type": "string", + "SqlName": "email", + "NoUpdate": true + }, { + "Name": "Name", + "Type": "string", + "SqlName": "name", + "NoInsert": true + }, { + "Name": "Admin", + "Type": "bool", + "SqlName": "admin", + "NoInsert": true, + "NoUpdate": true + } + ] + } + ] +} diff --git a/test-files/TestParse/003.def b/test-files/TestParse/003.def new file mode 100644 index 0000000..f7fa25c --- /dev/null +++ b/test-files/TestParse/003.def @@ -0,0 +1,12 @@ +TABLE users OF User NoDelete ( + user_id string AS UserID PK, + email string AS Email NoUpdate, + name string AS Name NoInsert, + admin bool AS Admin NoInsert NoUpdate +); + +TABLE users_view OF UserView NoInsert NoUpdate NoDelete ( + user_id string AS UserID PK, + email string AS Email, + name string AS Name +); \ No newline at end of file diff --git a/test-files/TestParse/003.json b/test-files/TestParse/003.json new file mode 100644 index 0000000..110d1fb --- /dev/null +++ b/test-files/TestParse/003.json @@ -0,0 +1,61 @@ +{ + "Tables": [ + { + "Name": "users", + "Type": "User", + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + }, + { + "Name": "Email", + "Type": "string", + "SqlName": "email", + "NoUpdate": true + }, + { + "Name": "Name", + "Type": "string", + "SqlName": "name", + "NoInsert": true + }, + { + "Name": "Admin", + "Type": "bool", + "SqlName": "admin", + "NoInsert": true, + "NoUpdate": true + } + ] + }, + { + "Name": "users_view", + "Type": "UserView", + "NoInsert": true, + "NoUpdate": true, + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + }, + { + "Name": "Email", + "Type": "string", + "SqlName": "email" + }, + { + "Name": "Name", + "Type": "string", + "SqlName": "name" + } + ] + } + ] +} diff --git a/test-files/TestParse/004.def b/test-files/TestParse/004.def new file mode 100644 index 0000000..9618d26 --- /dev/null +++ b/test-files/TestParse/004.def @@ -0,0 +1,13 @@ +TABLE users OF User NoDelete ( + user_id string AS UserID PK, + email string AS Email NoUpdate, + name string AS Name NoInsert, + admin bool AS Admin NoInsert NoUpdate, + SSN string NoUpdate +); + +TABLE users_view OF UserView NoInsert NoUpdate NoDelete ( + user_id string AS UserID PK, + email string AS Email, + name string AS Name +); diff --git a/test-files/TestParse/004.json b/test-files/TestParse/004.json new file mode 100644 index 0000000..088f931 --- /dev/null +++ b/test-files/TestParse/004.json @@ -0,0 +1,66 @@ +{ + "Tables": [ + { + "Name": "users", + "Type": "User", + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + }, + { + "Name": "Email", + "Type": "string", + "SqlName": "email", + "NoUpdate": true + }, + { + "Name": "Name", + "Type": "string", + "SqlName": "name", + "NoInsert": true + }, + { + "Name": "Admin", + "Type": "bool", + "SqlName": "admin", + "NoInsert": true, + "NoUpdate": true + }, { + "Name": "SSN", + "Type": "string", + "SqlName": "SSN", + "NoUpdate": true + } + ] + }, + { + "Name": "users_view", + "Type": "UserView", + "NoInsert": true, + "NoUpdate": true, + "NoDelete": true, + "Columns": [ + { + "Name": "UserID", + "Type": "string", + "SqlName": "user_id", + "PK": true + }, + { + "Name": "Email", + "Type": "string", + "SqlName": "email" + }, + { + "Name": "Name", + "Type": "string", + "SqlName": "name" + } + ] + } + ] +}