github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/pkg/graphdb/graphdb.go (about)

     1  package graphdb
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"path"
     7  	"strings"
     8  	"sync"
     9  )
    10  
    11  const (
    12  	createEntityTable = `
    13      CREATE TABLE IF NOT EXISTS entity (
    14          id text NOT NULL PRIMARY KEY
    15      );`
    16  
    17  	createEdgeTable = `
    18      CREATE TABLE IF NOT EXISTS edge (
    19          "entity_id" text NOT NULL,
    20          "parent_id" text NULL,
    21          "name" text NOT NULL,
    22          CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
    23          CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
    24          );
    25      `
    26  
    27  	createEdgeIndices = `
    28      CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
    29      `
    30  )
    31  
    32  // Entity with a unique id.
    33  type Entity struct {
    34  	id string
    35  }
    36  
    37  // An Edge connects two entities together.
    38  type Edge struct {
    39  	EntityID string
    40  	Name     string
    41  	ParentID string
    42  }
    43  
    44  // Entities stores the list of entities.
    45  type Entities map[string]*Entity
    46  
    47  // Edges stores the relationships between entities.
    48  type Edges []*Edge
    49  
    50  // WalkFunc is a function invoked to process an individual entity.
    51  type WalkFunc func(fullPath string, entity *Entity) error
    52  
    53  // Database is a graph database for storing entities and their relationships.
    54  type Database struct {
    55  	conn *sql.DB
    56  	mux  sync.RWMutex
    57  }
    58  
    59  // IsNonUniqueNameError processes the error to check if it's caused by
    60  // a constraint violation.
    61  // This is necessary because the error isn't the same across various
    62  // sqlite versions.
    63  func IsNonUniqueNameError(err error) bool {
    64  	str := err.Error()
    65  	// sqlite 3.7.17-1ubuntu1 returns:
    66  	// Set failure: Abort due to constraint violation: columns parent_id, name are not unique
    67  	if strings.HasSuffix(str, "name are not unique") {
    68  		return true
    69  	}
    70  	// sqlite-3.8.3-1.fc20 returns:
    71  	// Set failure: Abort due to constraint violation: UNIQUE constraint failed: edge.parent_id, edge.name
    72  	if strings.Contains(str, "UNIQUE constraint failed") && strings.Contains(str, "edge.name") {
    73  		return true
    74  	}
    75  	// sqlite-3.6.20-1.el6 returns:
    76  	// Set failure: Abort due to constraint violation: constraint failed
    77  	if strings.HasSuffix(str, "constraint failed") {
    78  		return true
    79  	}
    80  	return false
    81  }
    82  
    83  // NewDatabase creates a new graph database initialized with a root entity.
    84  func NewDatabase(conn *sql.DB) (*Database, error) {
    85  	if conn == nil {
    86  		return nil, fmt.Errorf("Database connection cannot be nil")
    87  	}
    88  	db := &Database{conn: conn}
    89  
    90  	// Create root entities
    91  	tx, err := conn.Begin()
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	if _, err := tx.Exec(createEntityTable); err != nil {
    97  		return nil, err
    98  	}
    99  	if _, err := tx.Exec(createEdgeTable); err != nil {
   100  		return nil, err
   101  	}
   102  	if _, err := tx.Exec(createEdgeIndices); err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	if _, err := tx.Exec("DELETE FROM entity where id = ?", "0"); err != nil {
   107  		tx.Rollback()
   108  		return nil, err
   109  	}
   110  
   111  	if _, err := tx.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
   112  		tx.Rollback()
   113  		return nil, err
   114  	}
   115  
   116  	if _, err := tx.Exec("DELETE FROM edge where entity_id=? and name=?", "0", "/"); err != nil {
   117  		tx.Rollback()
   118  		return nil, err
   119  	}
   120  
   121  	if _, err := tx.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
   122  		tx.Rollback()
   123  		return nil, err
   124  	}
   125  
   126  	if err := tx.Commit(); err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	return db, nil
   131  }
   132  
   133  // Close the underlying connection to the database.
   134  func (db *Database) Close() error {
   135  	return db.conn.Close()
   136  }
   137  
   138  // Set the entity id for a given path.
   139  func (db *Database) Set(fullPath, id string) (*Entity, error) {
   140  	db.mux.Lock()
   141  	defer db.mux.Unlock()
   142  
   143  	tx, err := db.conn.Begin()
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	var entityID string
   149  	if err := tx.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityID); err != nil {
   150  		if err == sql.ErrNoRows {
   151  			if _, err := tx.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
   152  				tx.Rollback()
   153  				return nil, err
   154  			}
   155  		} else {
   156  			tx.Rollback()
   157  			return nil, err
   158  		}
   159  	}
   160  	e := &Entity{id}
   161  
   162  	parentPath, name := splitPath(fullPath)
   163  	if err := db.setEdge(parentPath, name, e, tx); err != nil {
   164  		tx.Rollback()
   165  		return nil, err
   166  	}
   167  
   168  	if err := tx.Commit(); err != nil {
   169  		return nil, err
   170  	}
   171  	return e, nil
   172  }
   173  
   174  // Exists returns true if a name already exists in the database.
   175  func (db *Database) Exists(name string) bool {
   176  	db.mux.RLock()
   177  	defer db.mux.RUnlock()
   178  
   179  	e, err := db.get(name)
   180  	if err != nil {
   181  		return false
   182  	}
   183  	return e != nil
   184  }
   185  
   186  func (db *Database) setEdge(parentPath, name string, e *Entity, tx *sql.Tx) error {
   187  	parent, err := db.get(parentPath)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	if parent.id == e.id {
   192  		return fmt.Errorf("Cannot set self as child")
   193  	}
   194  
   195  	if _, err := tx.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
   196  		return err
   197  	}
   198  	return nil
   199  }
   200  
   201  // RootEntity returns the root "/" entity for the database.
   202  func (db *Database) RootEntity() *Entity {
   203  	return &Entity{
   204  		id: "0",
   205  	}
   206  }
   207  
   208  // Get returns the entity for a given path.
   209  func (db *Database) Get(name string) *Entity {
   210  	db.mux.RLock()
   211  	defer db.mux.RUnlock()
   212  
   213  	e, err := db.get(name)
   214  	if err != nil {
   215  		return nil
   216  	}
   217  	return e
   218  }
   219  
   220  func (db *Database) get(name string) (*Entity, error) {
   221  	e := db.RootEntity()
   222  	// We always know the root name so return it if
   223  	// it is requested
   224  	if name == "/" {
   225  		return e, nil
   226  	}
   227  
   228  	parts := split(name)
   229  	for i := 1; i < len(parts); i++ {
   230  		p := parts[i]
   231  		if p == "" {
   232  			continue
   233  		}
   234  
   235  		next := db.child(e, p)
   236  		if next == nil {
   237  			return nil, fmt.Errorf("Cannot find child for %s", name)
   238  		}
   239  		e = next
   240  	}
   241  	return e, nil
   242  
   243  }
   244  
   245  // List all entities by from the name.
   246  // The key will be the full path of the entity.
   247  func (db *Database) List(name string, depth int) Entities {
   248  	db.mux.RLock()
   249  	defer db.mux.RUnlock()
   250  
   251  	out := Entities{}
   252  	e, err := db.get(name)
   253  	if err != nil {
   254  		return out
   255  	}
   256  
   257  	children, err := db.children(e, name, depth, nil)
   258  	if err != nil {
   259  		return out
   260  	}
   261  
   262  	for _, c := range children {
   263  		out[c.FullPath] = c.Entity
   264  	}
   265  	return out
   266  }
   267  
   268  // Walk through the child graph of an entity, calling walkFunc for each child entity.
   269  // It is safe for walkFunc to call graph functions.
   270  func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
   271  	children, err := db.Children(name, depth)
   272  	if err != nil {
   273  		return err
   274  	}
   275  
   276  	// Note: the database lock must not be held while calling walkFunc
   277  	for _, c := range children {
   278  		if err := walkFunc(c.FullPath, c.Entity); err != nil {
   279  			return err
   280  		}
   281  	}
   282  	return nil
   283  }
   284  
   285  // Children returns the children of the specified entity.
   286  func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
   287  	db.mux.RLock()
   288  	defer db.mux.RUnlock()
   289  
   290  	e, err := db.get(name)
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  
   295  	return db.children(e, name, depth, nil)
   296  }
   297  
   298  // Parents returns the parents of a specified entity.
   299  func (db *Database) Parents(name string) ([]string, error) {
   300  	db.mux.RLock()
   301  	defer db.mux.RUnlock()
   302  
   303  	e, err := db.get(name)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	return db.parents(e)
   308  }
   309  
   310  // Refs returns the reference count for a specified id.
   311  func (db *Database) Refs(id string) int {
   312  	db.mux.RLock()
   313  	defer db.mux.RUnlock()
   314  
   315  	var count int
   316  	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
   317  		return 0
   318  	}
   319  	return count
   320  }
   321  
   322  // RefPaths returns all the id's path references.
   323  func (db *Database) RefPaths(id string) Edges {
   324  	db.mux.RLock()
   325  	defer db.mux.RUnlock()
   326  
   327  	refs := Edges{}
   328  
   329  	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
   330  	if err != nil {
   331  		return refs
   332  	}
   333  	defer rows.Close()
   334  
   335  	for rows.Next() {
   336  		var name string
   337  		var parentID string
   338  		if err := rows.Scan(&name, &parentID); err != nil {
   339  			return refs
   340  		}
   341  		refs = append(refs, &Edge{
   342  			EntityID: id,
   343  			Name:     name,
   344  			ParentID: parentID,
   345  		})
   346  	}
   347  	return refs
   348  }
   349  
   350  // Delete the reference to an entity at a given path.
   351  func (db *Database) Delete(name string) error {
   352  	db.mux.Lock()
   353  	defer db.mux.Unlock()
   354  
   355  	if name == "/" {
   356  		return fmt.Errorf("Cannot delete root entity")
   357  	}
   358  
   359  	parentPath, n := splitPath(name)
   360  	parent, err := db.get(parentPath)
   361  	if err != nil {
   362  		return err
   363  	}
   364  
   365  	if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
   366  		return err
   367  	}
   368  	return nil
   369  }
   370  
   371  // Purge removes the entity with the specified id
   372  // Walk the graph to make sure all references to the entity
   373  // are removed and return the number of references removed
   374  func (db *Database) Purge(id string) (int, error) {
   375  	db.mux.Lock()
   376  	defer db.mux.Unlock()
   377  
   378  	tx, err := db.conn.Begin()
   379  	if err != nil {
   380  		return -1, err
   381  	}
   382  
   383  	// Delete all edges
   384  	rows, err := tx.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
   385  	if err != nil {
   386  		tx.Rollback()
   387  		return -1, err
   388  	}
   389  	changes, err := rows.RowsAffected()
   390  	if err != nil {
   391  		return -1, err
   392  	}
   393  
   394  	// Clear who's using this id as parent
   395  	refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id)
   396  	if err != nil {
   397  		tx.Rollback()
   398  		return -1, err
   399  	}
   400  	refsCount, err := refs.RowsAffected()
   401  	if err != nil {
   402  		return -1, err
   403  	}
   404  
   405  	// Delete entity
   406  	if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil {
   407  		tx.Rollback()
   408  		return -1, err
   409  	}
   410  
   411  	if err := tx.Commit(); err != nil {
   412  		return -1, err
   413  	}
   414  
   415  	return int(changes + refsCount), nil
   416  }
   417  
   418  // Rename an edge for a given path
   419  func (db *Database) Rename(currentName, newName string) error {
   420  	db.mux.Lock()
   421  	defer db.mux.Unlock()
   422  
   423  	parentPath, name := splitPath(currentName)
   424  	newParentPath, newEdgeName := splitPath(newName)
   425  
   426  	if parentPath != newParentPath {
   427  		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
   428  	}
   429  
   430  	parent, err := db.get(parentPath)
   431  	if err != nil {
   432  		return err
   433  	}
   434  
   435  	rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
   436  	if err != nil {
   437  		return err
   438  	}
   439  	i, err := rows.RowsAffected()
   440  	if err != nil {
   441  		return err
   442  	}
   443  	if i == 0 {
   444  		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
   445  	}
   446  	return nil
   447  }
   448  
   449  // WalkMeta stores the walk metadata.
   450  type WalkMeta struct {
   451  	Parent   *Entity
   452  	Entity   *Entity
   453  	FullPath string
   454  	Edge     *Edge
   455  }
   456  
   457  func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
   458  	if e == nil {
   459  		return entities, nil
   460  	}
   461  
   462  	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
   463  	if err != nil {
   464  		return nil, err
   465  	}
   466  	defer rows.Close()
   467  
   468  	for rows.Next() {
   469  		var entityID, entityName string
   470  		if err := rows.Scan(&entityID, &entityName); err != nil {
   471  			return nil, err
   472  		}
   473  		child := &Entity{entityID}
   474  		edge := &Edge{
   475  			ParentID: e.id,
   476  			Name:     entityName,
   477  			EntityID: child.id,
   478  		}
   479  
   480  		meta := WalkMeta{
   481  			Parent:   e,
   482  			Entity:   child,
   483  			FullPath: path.Join(name, edge.Name),
   484  			Edge:     edge,
   485  		}
   486  
   487  		entities = append(entities, meta)
   488  
   489  		if depth != 0 {
   490  			nDepth := depth
   491  			if depth != -1 {
   492  				nDepth--
   493  			}
   494  			entities, err = db.children(child, meta.FullPath, nDepth, entities)
   495  			if err != nil {
   496  				return nil, err
   497  			}
   498  		}
   499  	}
   500  
   501  	return entities, nil
   502  }
   503  
   504  func (db *Database) parents(e *Entity) (parents []string, err error) {
   505  	if e == nil {
   506  		return parents, nil
   507  	}
   508  
   509  	rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id)
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  	defer rows.Close()
   514  
   515  	for rows.Next() {
   516  		var parentID string
   517  		if err := rows.Scan(&parentID); err != nil {
   518  			return nil, err
   519  		}
   520  		parents = append(parents, parentID)
   521  	}
   522  
   523  	return parents, nil
   524  }
   525  
   526  // Return the entity based on the parent path and name.
   527  func (db *Database) child(parent *Entity, name string) *Entity {
   528  	var id string
   529  	if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
   530  		return nil
   531  	}
   532  	return &Entity{id}
   533  }
   534  
   535  // ID returns the id used to reference this entity.
   536  func (e *Entity) ID() string {
   537  	return e.id
   538  }
   539  
   540  // Paths returns the paths sorted by depth.
   541  func (e Entities) Paths() []string {
   542  	out := make([]string, len(e))
   543  	var i int
   544  	for k := range e {
   545  		out[i] = k
   546  		i++
   547  	}
   548  	sortByDepth(out)
   549  
   550  	return out
   551  }