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