Skip to content

Instantly share code, notes, and snippets.

@divan
Created February 17, 2023 22:05
Show Gist options
  • Save divan/dd05b64ceea7541b9dc1a4b1aff09fca to your computer and use it in GitHub Desktop.
Save divan/dd05b64ceea7541b9dc1a4b1aff09fca to your computer and use it in GitHub Desktop.
edgedb-delete-checker
package main
import (
"flag"
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)
// Checker finds the links with deletion rules from the esdl files in the dir.
type Checker struct {
Dir string
Links []Link
}
// Link represents a link in the esdl file.
type Link struct {
Source string // source type name
Target string // target type name
Name string // link name
OnDelete string // on delete constraint
}
// String implements the Stringer interface.
func (l Link) String() string {
switch l.OnDelete {
case "allow":
return fmt.Sprintf("If you delete %s, it will be deleted from %s (via .%s), and %s will stay", l.Target, l.Source, l.Name, l.Source)
case "delete source":
return fmt.Sprintf("If you delete %s, all linked %s will be deleted (via .%s)", l.Target, l.Source, l.Name)
case "restrict":
return fmt.Sprintf("If you try to delete %s that is referenced by %s (via .%s), it'll return an error", l.Target, l.Source, l.Name)
default:
return fmt.Sprintf("Unknown on delete constraint %s", l.OnDelete)
}
}
func main() {
var dir = flag.String("dir", ".", "directory with dbschema/.esdl files for your project")
flag.Parse()
c := Checker{
Dir: *dir,
}
// find all dbschema/.esdl files in the directory
err := filepath.WalkDir(*dir, func(s string, d fs.DirEntry, e error) error {
if e != nil {
return e
}
if d.IsDir() {
return nil
}
if filepath.Ext(s) != ".esdl" {
return nil
}
return c.processESDL(s)
})
if err != nil {
fmt.Println("[ERROR]:", err)
os.Exit(1)
}
// sort by target for output
sort.Slice(c.Links, func(i, j int) bool {
return c.Links[i].Target < c.Links[j].Target
})
for _, link := range c.Links {
fmt.Println(link)
}
fmt.Println()
fmt.Println("See details on: https://www.edgedb.com/docs/datamodel/links#ref-datamodel-link-deletion")
}
func (c *Checker) processESDL(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %w", err)
}
// find lines theat contain "link "
// and print them
currentType := ""
lines := strings.Split(string(data), "\n")
for i, line := range lines {
lastLine := i == len(lines)-1
line = strings.TrimSpace(line)
// save current type (esdl can contain many types
if strings.Contains(line, "type ") {
currentType = strings.Split(line, " ")[1]
continue
}
if strings.Contains(line, "link ") {
// peek the next line and check if it contains "on target delete"
var onDelete string = "restrict"
if !lastLine {
nextLine := strings.TrimSpace(lines[i+1])
if strings.Contains(nextLine, "on target delete") {
if strings.Contains(nextLine, "allow") {
onDelete = "allow"
} else if strings.Contains(nextLine, "delete source") {
onDelete = "delete source"
}
}
}
// strip required/multi
line = strings.ReplaceAll(line, "required ", "")
line = strings.ReplaceAll(line, "multi ", "")
// split by spaces
fields := strings.Fields(line)
var name, target string
name = fields[1]
target = fields[3]
// skip backlinks
if strings.HasPrefix(target, ".<") {
continue
}
// cleanup
if strings.HasPrefix(name, "`") {
name = strings.Trim(name, "`")
}
if strings.HasSuffix(target, ";") {
target = strings.TrimSuffix(target, ";")
}
link := Link{
Source: currentType,
Target: target,
Name: name,
OnDelete: onDelete,
}
c.Links = append(c.Links, link)
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment