<commit_before>// Copyright (c) 2012-2014 <NAME>
// Copyright (c) 2016 <NAME> <<EMAIL>>
// released under the MIT license

package irc

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"github.com/oragono/oragono/irc/modes"
	"github.com/oragono/oragono/irc/utils"

	"github.com/tidwall/buntdb"
)

const (
	// 'version' of the database schema
	keySchemaVersion = "db.version"
	// latest schema of the db
	latestDbSchema = "3"
)

type SchemaChanger func(*Config, *buntdb.Tx) error

type SchemaChange struct {
	InitialVersion string // the change will take this version
	TargetVersion  string // and transform it into this version
	Changer        SchemaChanger
}

// maps an initial version to a schema change capable of upgrading it
var schemaChanges map[string]SchemaChange

type incompatibleSchemaError struct {
	currentVersion  string
	requiredVersion string
}

func IncompatibleSchemaError(currentVersion string) (result *incompatibleSchemaError) {
	return &incompatibleSchemaError{
		currentVersion:  currentVersion,
		requiredVersion: latestDbSchema,
	}
}

func (err *incompatibleSchemaError) Error() string {
	return fmt.Sprintf("Database requires update. Expected schema v%s, got v%s", err.requiredVersion, err.currentVersion)
}

// InitDB creates the database.
func InitDB(path string) {
	// prepare kvstore db
	//TODO(dan): fail if already exists instead? don't want to overwrite good data
	os.Remove(path)
	store, err := buntdb.Open(path)
	if err != nil {
		log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
	}
	defer store.Close()

	err = store.Update(func(tx *buntdb.Tx) error {
		// set schema version
		tx.Set(keySchemaVersion, latestDbSchema, nil)
		return nil
	})

	if err != nil {
		log.Fatal("Could not save datastore:", err.Error())
	}
}

// OpenDatabase returns an existing database, performing a schema version check.
func OpenDatabase(config *Config) (*buntdb.DB, error) {
	return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
}

// open the database, giving it at most one chance to auto-upgrade the schema
func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
	db, err = buntdb.Open(config.Datastore.Path)
	if err != nil {
		return
	}

	defer func() {
		if err != nil && db != nil {
			db.Close()
			db = nil
		}
	}()

	// read the current version string
	var version string
	err = db.View(func(tx *buntdb.Tx) error {
		version, err = tx.Get(keySchemaVersion)
		return err
	})
	if err != nil {
		return
	}

	if version == latestDbSchema {
		// success
		return
	}<commit_msg>prevents default create/open of buntdb, requires user to call initdb<commit_after>// Copyright (c) 2012-2014 <NAME>
// Copyright (c) 2016 <NAME> <<EMAIL>>
// released under the MIT license

package irc

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"github.com/oragono/oragono/irc/modes"
	"github.com/oragono/oragono/irc/utils"

	"github.com/tidwall/buntdb"
)

const (
	// 'version' of the database schema
	keySchemaVersion = "db.version"
	// latest schema of the db
	latestDbSchema = "3"
)

type SchemaChanger func(*Config, *buntdb.Tx) error

type SchemaChange struct {
	InitialVersion string // the change will take this version
	TargetVersion  string // and transform it into this version
	Changer        SchemaChanger
}

// maps an initial version to a schema change capable of upgrading it
var schemaChanges map[string]SchemaChange

type incompatibleSchemaError struct {
	currentVersion  string
	requiredVersion string
}

func IncompatibleSchemaError(currentVersion string) (result *incompatibleSchemaError) {
	return &incompatibleSchemaError{
		currentVersion:  currentVersion,
		requiredVersion: latestDbSchema,
	}
}

func (err *incompatibleSchemaError) Error() string {
	return fmt.Sprintf("Database requires update. Expected schema v%s, got v%s", err.requiredVersion, err.currentVersion)
}

// InitDB creates the database.
func InitDB(path string) {
	// prepare kvstore db
	//TODO(dan): fail if already exists instead? don't want to overwrite good data
	os.Remove(path)
	store, err := buntdb.Open(path)
	if err != nil {
		log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
	}
	defer store.Close()

	err = store.Update(func(tx *buntdb.Tx) error {
		// set schema version
		tx.Set(keySchemaVersion, latestDbSchema, nil)
		return nil
	})

	if err != nil {
		log.Fatal("Could not save datastore:", err.Error())
	}
}

// OpenDatabase returns an existing database, performing a schema version check.
func OpenDatabase(config *Config) (*buntdb.DB, error) {
	return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
}

// open the database, giving it at most one chance to auto-upgrade the schema
func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
	_, err = os.Stat(config.Datastore.Path)
	if os.IsNotExist(err) {
		return
	}
	db, err = buntdb.Open(config.Datastore.Path)
	if err != nil {
		return
	}

	defer func() {
		if err != nil && db != nil {
			db.Close()
			db = nil
		}
	}()

	// read the current version string
	var version string
	err = db.View(func(tx *buntdb.Tx) error {
		version, err = tx.Get(keySchemaVersion)
		return err
	})
	if err != nil {
		return
	}

	if version == latestDbSchema {
		// success
		return
	}
{'fraction_non_alphanumeric': 0.11155078198605616, 'fraction_numerical': 0.004899189749387601, 'mean_word_length': 4.050428163653663, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1149250', 'n_tokens_mistral': 1833, 'n_tokens_neox': 1717, 'n_words': 528}