func EnsureSchema(db *sql.DB, address string, dir string) (bool, error) {
	someNodesAreBehind := false
	apiExtensions := version.APIExtensionsCount()

	backupDone := false
	hook := (func(version int, tx *sql.Tx) error {
		// Check if this is a fresh instance.
		isUpdate, err := schema.DoesSchemaTableExist(tx)
		if err != nil {
			return errors.Wrap(err, "failed to check if schema table exists")
		}

		if !isUpdate {
			return nil
		}

		// Check if we're clustered
		clustered := true
		n, err := selectUnclusteredNodesCount(tx)
		if err != nil {
			return errors.Wrap(err, "failed to fetch unclustered nodes count")
		}
		if n > 1 {
			// This should never happen, since we only add nodes
			// with valid addresses, but check it for sanity.
			return fmt.Errorf("found more than one unclustered nodes")
		} else if n == 1 {
			clustered = false
		}

		// If we're not clustered, backup the local cluster database directory
		// before performing any schema change. This makes sense only in the
		// non-clustered case, because otherwise the directory would be
		// re-populated by replication.
		if !clustered && !backupDone {
			logger.Infof("Updating the LXD global schema. Backup made as \"global.bak\"")
			err := shared.DirCopy(
				filepath.Join(dir, "global"),
				filepath.Join(dir, "global.bak"),
			)
			if err != nil {
				return errors.Wrap(err, "failed to backup global database")
			}
			backupDone = true
		}

		if version == -1 {
			logger.Debugf("Running pre-update queries from file for global DB schema")
		} else {
			logger.Debugf("Updating global DB schema from %d to %d", version, version+1)
		}

		return nil
	})

	check := func(current int, tx *sql.Tx) error {
		// If we're bootstrapping a fresh schema, skip any check, since
		// it's safe to assume we are the only node.
		if current == 0 {
			return nil
		}

		// Check if we're clustered
		n, err := selectUnclusteredNodesCount(tx)
		if err != nil {
			return errors.Wrap(err, "failed to fetch unclustered nodes count")
		}
		if n > 1 {
			// This should never happen, since we only add nodes
			// with valid addresses, but check it for sanity.
			return fmt.Errorf("found more than one unclustered nodes")
		} else if n == 1 {
			address = "0.0.0.0" // We're not clustered
		}

		// Update the schema and api_extension columns of ourselves.
		err = updateNodeVersion(tx, address, apiExtensions)
		if err != nil {
			return errors.Wrap(err, "failed to update node version info")
		}

		err = checkClusterIsUpgradable(tx, [2]int{len(updates), apiExtensions})
		if err == errSomeNodesAreBehind {
			someNodesAreBehind = true
			return schema.ErrGracefulAbort
		}
		return err
	}

	schema := Schema()
	schema.File(filepath.Join(dir, "patch.global.sql")) // Optional custom queries
	schema.Check(check)
	schema.Hook(hook)

	var initial int
	err := query.Retry(func() error {
		var err error
		initial, err = schema.Ensure(db)
		return err
	})
	if someNodesAreBehind {
		return false, nil
	}
	if err != nil {
		return false, err
	}

	// When creating a database from scratch, insert an entry for node
	// 1. This is needed for referential integrity with other tables. Also,
	// create a default profile.
	if initial == 0 {
		tx, err := db.Begin()
		if err != nil {
			return false, err
		}
		stmt := `
INSERT INTO nodes(id, name, address, schema, api_extensions) VALUES(1, 'none', '0.0.0.0', ?, ?)
`
		_, err = tx.Exec(stmt, SchemaVersion, apiExtensions)
		if err != nil {
			tx.Rollback()
			return false, err
		}

		// Default project
		stmt = `
INSERT INTO projects (name, description) VALUES ('default', 'Default LXD project');
INSERT INTO projects_config (project_id, key, value) VALUES (1, 'features.images', 'true');
INSERT INTO projects_config (project_id, key, value) VALUES (1, 'features.profiles', 'true');
`
		_, err = tx.Exec(stmt)
		if err != nil {
			tx.Rollback()
			return false, err
		}

		// Default profile
		stmt = `
INSERT INTO profiles (name, description, project_id) VALUES ('default', 'Default LXD profile', 1)
`
		_, err = tx.Exec(stmt)
		if err != nil {
			tx.Rollback()
			return false, err
		}

		err = tx.Commit()
		if err != nil {
			return false, err
		}
	}

	return true, err
}
func EnsureSchema(db *sql.DB, address string, dir string) (bool, error) {
	someNodesAreBehind := false
	apiExtensions := version.APIExtensionsCount()

	backupDone := false
	hook := (func(version int, tx *sql.Tx) error {
		
		isUpdate, err := schema.DoesSchemaTableExist(tx)
		if err != nil {
			return errors.Wrap(err, "failed to check if schema table exists")
		}

		if !isUpdate {
			return nil
		}

		
		clustered := true
		n, err := selectUnclusteredNodesCount(tx)
		if err != nil {
			return errors.Wrap(err, "failed to fetch unclustered nodes count")
		}
		if n > 1 {
			
			
			return fmt.Errorf("found more than one unclustered nodes")
		} else if n == 1 {
			clustered = false
		}

		
		
		
		
		if !clustered && !backupDone {
			logger.Infof("Updating the LXD global schema. Backup made as \"global.bak\"")
			err := shared.DirCopy(
				filepath.Join(dir, "global"),
				filepath.Join(dir, "global.bak"),
			)
			if err != nil {
				return errors.Wrap(err, "failed to backup global database")
			}
			backupDone = true
		}

		if version == -1 {
			logger.Debugf("Running pre-update queries from file for global DB schema")
		} else {
			logger.Debugf("Updating global DB schema from %d to %d", version, version+1)
		}

		return nil
	})

	check := func(current int, tx *sql.Tx) error {
		
		
		if current == 0 {
			return nil
		}

		
		n, err := selectUnclusteredNodesCount(tx)
		if err != nil {
			return errors.Wrap(err, "failed to fetch unclustered nodes count")
		}
		if n > 1 {
			
			
			return fmt.Errorf("found more than one unclustered nodes")
		} else if n == 1 {
			address = "0.0.0.0" 
		}

		
		err = updateNodeVersion(tx, address, apiExtensions)
		if err != nil {
			return errors.Wrap(err, "failed to update node version info")
		}

		err = checkClusterIsUpgradable(tx, [2]int{len(updates), apiExtensions})
		if err == errSomeNodesAreBehind {
			someNodesAreBehind = true
			return schema.ErrGracefulAbort
		}
		return err
	}

	schema := Schema()
	schema.File(filepath.Join(dir, "patch.global.sql")) 
	schema.Check(check)
	schema.Hook(hook)

	var initial int
	err := query.Retry(func() error {
		var err error
		initial, err = schema.Ensure(db)
		return err
	})
	if someNodesAreBehind {
		return false, nil
	}
	if err != nil {
		return false, err
	}

	
	
	
	if initial == 0 {
		tx, err := db.Begin()
		if err != nil {
			return false, err
		}
		stmt := `
INSERT INTO nodes(id, name, address, schema, api_extensions) VALUES(1, 'none', '0.0.0.0', ?, ?)
`
		_, err = tx.Exec(stmt, SchemaVersion, apiExtensions)
		if err != nil {
			tx.Rollback()
			return false, err
		}

		
		stmt = `
INSERT INTO projects (name, description) VALUES ('default', 'Default LXD project');
INSERT INTO projects_config (project_id, key, value) VALUES (1, 'features.images', 'true');
INSERT INTO projects_config (project_id, key, value) VALUES (1, 'features.profiles', 'true');
`
		_, err = tx.Exec(stmt)
		if err != nil {
			tx.Rollback()
			return false, err
		}

		
		stmt = `
INSERT INTO profiles (name, description, project_id) VALUES ('default', 'Default LXD profile', 1)
`
		_, err = tx.Exec(stmt)
		if err != nil {
			tx.Rollback()
			return false, err
		}

		err = tx.Commit()
		if err != nil {
			return false, err
		}
	}

	return true, err
}
func EnsureSchema ( db * sql . DB , address string , dir string ) ( bool , error ) { someNodesAreBehind := false apiExtensions := version . APIExtensionsCount ( ) backupDone := false hook := ( func ( version int , tx * sql . Tx ) error { // Check if this is a fresh instance. isUpdate , err := schema . DoesSchemaTableExist ( tx ) if err != nil { return errors . Wrap ( err , " " ) } if ! isUpdate { return nil } // Check if we're clustered clustered := true n , err := selectUnclusteredNodesCount ( tx ) if err != nil { return errors . Wrap ( err , " " ) } if n > 1 { // This should never happen, since we only add nodes // with valid addresses, but check it for sanity. return fmt . Errorf ( " " ) } else if n == 1 { clustered = false } // If we're not clustered, backup the local cluster database directory // before performing any schema change. This makes sense only in the // non-clustered case, because otherwise the directory would be // re-populated by replication. if ! clustered && ! backupDone { logger . Infof ( " \" \" " ) err := shared . DirCopy ( filepath . Join ( dir , " " ) , filepath . Join ( dir , " " ) , ) if err != nil { return errors . Wrap ( err , " " ) } backupDone = true } if version == - 1 { logger . Debugf ( " " ) } else { logger . Debugf ( " " , version , version + 1 ) } return nil } ) check := func ( current int , tx * sql . Tx ) error { // If we're bootstrapping a fresh schema, skip any check, since // it's safe to assume we are the only node. if current == 0 { return nil } // Check if we're clustered n , err := selectUnclusteredNodesCount ( tx ) if err != nil { return errors . Wrap ( err , " " ) } if n > 1 { // This should never happen, since we only add nodes // with valid addresses, but check it for sanity. return fmt . Errorf ( " " ) } else if n == 1 { address = " " // We're not clustered } // Update the schema and api_extension columns of ourselves. err = updateNodeVersion ( tx , address , apiExtensions ) if err != nil { return errors . Wrap ( err , " " ) } err = checkClusterIsUpgradable ( tx , [ 2 ] int { len ( updates ) , apiExtensions } ) if err == errSomeNodesAreBehind { someNodesAreBehind = true return schema . ErrGracefulAbort } return err } schema := Schema ( ) schema . File ( filepath . Join ( dir , " " ) ) // Optional custom queries schema . Check ( check ) schema . Hook ( hook ) var initial int err := query . Retry ( func ( ) error { var err error initial , err = schema . Ensure ( db ) return err } ) if someNodesAreBehind { return false , nil } if err != nil { return false , err } // When creating a database from scratch, insert an entry for node // 1. This is needed for referential integrity with other tables. Also, // create a default profile. if initial == 0 { tx , err := db . Begin ( ) if err != nil { return false , err } stmt := `
INSERT INTO nodes(id, name, address, schema, api_extensions) VALUES(1, 'none', '0.0.0.0', ?, ?)
` _ , err = tx . Exec ( stmt , SchemaVersion , apiExtensions ) if err != nil { tx . Rollback ( ) return false , err } // Default project stmt = `
INSERT INTO projects (name, description) VALUES ('default', 'Default LXD project');
INSERT INTO projects_config (project_id, key, value) VALUES (1, 'features.images', 'true');
INSERT INTO projects_config (project_id, key, value) VALUES (1, 'features.profiles', 'true');
` _ , err = tx . Exec ( stmt ) if err != nil { tx . Rollback ( ) return false , err } // Default profile stmt = `
INSERT INTO profiles (name, description, project_id) VALUES ('default', 'Default LXD profile', 1)
` _ , err = tx . Exec ( stmt ) if err != nil { tx . Rollback ( ) return false , err } err = tx . Commit ( ) if err != nil { return false , err } } return true , err }
// EnsureSchema applies all relevant schema updates to the cluster database.
//
// Before actually doing anything, this function will make sure that all nodes
// in the cluster have a schema version and a number of API extensions that
// match our one. If it's not the case, we either return an error (if some
// nodes have version greater than us and we need to be upgraded), or return
// false and no error (if some nodes have a lower version, and we need to wait
// till they get upgraded and restarted).
EnsureSchema applies all relevant schema updates to the cluster database . Before actually doing anything this function will make sure that all nodes in the cluster have a schema version and a number of API extensions that match our one . If it s not the case we either return an error ( if some nodes have version greater than us and we need to be upgraded ) or return false and no error ( if some nodes have a lower version and we need to wait till they get upgraded and restarted ) .