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