func PathForObject(dirPath, objectName string) string {
	return filepath.Join(dirPath, FileNameForObject(objectName))
}
PathForObject returns a string containing a path to use for the SQLFile
representing the supplied object name. Special characters in the objectName
will be removed; however, there is no risk of "conflicts" since a single
SQLFile can store definitions for multiple objects.
package fs
import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"unicode"
	"github.com/skeema/tengo"
)
type SQLFile struct {
	Dir      string
	FileName string
}
type TokenizedSQLFile struct {
	SQLFile
	Statements []*Statement
}
func (sf SQLFile) Path() string {
	return filepath.Join(sf.Dir, sf.FileName)
}
func (sf SQLFile) String() string {
	return sf.Path()
}
func (sf SQLFile) Exists() (bool, error) {
	_, err := os.Stat(sf.Path())
	if err == nil {
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
func (sf SQLFile) Create(contents string) error {
	if exists, err := sf.Exists(); err != nil {
		return err
	} else if exists {
		return fmt.Errorf("Cannot create %s: already exists", sf)
	}
	return ioutil.WriteFile(sf.Path(), []byte(contents), 0666)
}
func (sf SQLFile) Delete() error {
	return os.Remove(sf.Path())
}
func (sf SQLFile) Tokenize() (*TokenizedSQLFile, error) {
	tokenizer := newStatementTokenizer(sf.Path(), ";")
	statements, err := tokenizer.statements()
	tryReparse := true
	var seenRoutine, unknownAfterRoutine bool
	for _, stmt := range statements {
		switch stmt.Type {
		case StatementTypeNoop:
		case StatementTypeCreate:
			if !seenRoutine && stmt.isCreateWithBegin() {
				seenRoutine = true
			} else {
				tryReparse = false
			}
		case StatementTypeUnknown:
			if seenRoutine {
				unknownAfterRoutine = true
			}
		default:
			tryReparse = false
		}
		if !tryReparse {
			break
		}
	}
	if seenRoutine && unknownAfterRoutine && tryReparse {
		tokenizer := newStatementTokenizer(sf.Path(), "\000")
		if statements2, err2 := tokenizer.statements(); err2 == nil {
			statements = statements2
			err = nil
		}
	}
	return NewTokenizedSQLFile(sf, statements), err
}
func (sf SQLFile) WriteStatements(statements []*Statement) (int, error) {
	lines := make([]string, len(statements))
	for n := range statements {
		lines[n] = string(statements[n].Text)
	}
	value := strings.Join(lines, "")
	err := ioutil.WriteFile(sf.Path(), []byte(value), 0666)
	if err != nil {
		return 0, err
	}
	return len(value), nil
}
func NewTokenizedSQLFile(sf SQLFile, statements []*Statement) *TokenizedSQLFile {
	result := &TokenizedSQLFile{
		SQLFile:    sf,
		Statements: statements,
	}
	for _, stmt := range statements {
		stmt.FromFile = result
	}
	return result
}
func (tsf *TokenizedSQLFile) Rewrite() (int, error) {
	var keepFile bool
	for _, stmt := range tsf.Statements {
		if stmt.Type != StatementTypeNoop && stmt.Type != StatementTypeCommand {
			keepFile = true
			break
		}
	}
	if keepFile {
		return tsf.WriteStatements(tsf.Statements)
	}
	return 0, tsf.Delete()
}
func FileNameForObject(objectName string) string {
	objectName = strings.Map(removeSpecialChars, objectName)
	if objectName == "" {
		objectName = "symbols"
	}
	return fmt.Sprintf("%s.sql", objectName)
}