commit
bba352922a
@ -7,8 +7,45 @@ package base
|
||||
|
||||
import ()
|
||||
|
||||
type RowsEstimateMethod string
|
||||
|
||||
const (
|
||||
TableStatusRowsEstimate RowsEstimateMethod = "TableStatusRowsEstimate"
|
||||
ExplainRowsEstimate = "ExplainRowsEstimate"
|
||||
CountRowsEstimate = "CountRowsEstimate"
|
||||
)
|
||||
|
||||
type MigrationContext struct {
|
||||
DatabaseName string
|
||||
OriginalTableName string
|
||||
GhostTableName string
|
||||
AlterStatement string
|
||||
TableEngine string
|
||||
CountTableRows bool
|
||||
RowsEstimate int64
|
||||
UsedRowsEstimateMethod RowsEstimateMethod
|
||||
ChunkSize int
|
||||
OriginalBinlogFormat string
|
||||
OriginalBinlogRowImage string
|
||||
}
|
||||
|
||||
var context *MigrationContext
|
||||
|
||||
func init() {
|
||||
context = newMigrationContext()
|
||||
}
|
||||
|
||||
func newMigrationContext() *MigrationContext {
|
||||
return &MigrationContext{
|
||||
ChunkSize: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
func GetMigrationContext() *MigrationContext {
|
||||
return context
|
||||
}
|
||||
|
||||
// RequiresBinlogFormatChange
|
||||
func (this *MigrationContext) RequiresBinlogFormatChange() bool {
|
||||
return this.OriginalBinlogFormat != "ROW"
|
||||
}
|
||||
|
@ -88,6 +88,10 @@ func (this *GoMySQLReader) ReadEntries(logFile string, startPos uint64, stopPos
|
||||
} else {
|
||||
ev.Dump(os.Stdout)
|
||||
}
|
||||
// TODO : convert to entries
|
||||
// need to parse multi-row entries
|
||||
// insert & delete are just one row per db orw
|
||||
// update: where-row_>values-row, repeating
|
||||
}
|
||||
}
|
||||
log.Debugf("done")
|
||||
|
@ -18,7 +18,7 @@ func init() {
|
||||
log.SetLevel(log.ERROR)
|
||||
}
|
||||
|
||||
func TestRBRSample0(t *testing.T) {
|
||||
func __TestRBRSample0(t *testing.T) {
|
||||
testFile, err := os.Open("testdata/rbr-sample-0.txt")
|
||||
test.S(t).ExpectNil(err)
|
||||
defer testFile.Close()
|
||||
|
@ -10,7 +10,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/github/gh-osc/go/base"
|
||||
"github.com/github/gh-osc/go/binlog"
|
||||
"github.com/github/gh-osc/go/logic"
|
||||
"github.com/github/gh-osc/go/mysql"
|
||||
"github.com/outbrain/golib/log"
|
||||
)
|
||||
@ -18,6 +20,7 @@ import (
|
||||
// main is the application's entry point. It will either spawn a CLI or HTTP itnerfaces.
|
||||
func main() {
|
||||
var connectionConfig mysql.ConnectionConfig
|
||||
migrationContext := base.GetMigrationContext()
|
||||
|
||||
// mysqlBasedir := flag.String("mysql-basedir", "", "the --basedir config for MySQL (auto-detected if not given)")
|
||||
// mysqlDatadir := flag.String("mysql-datadir", "", "the --datadir config for MySQL (auto-detected if not given)")
|
||||
@ -29,6 +32,11 @@ func main() {
|
||||
flag.StringVar(&connectionConfig.User, "user", "root", "MySQL user")
|
||||
flag.StringVar(&connectionConfig.Password, "password", "", "MySQL password")
|
||||
|
||||
flag.StringVar(&migrationContext.DatabaseName, "database", "", "database name (mandatory)")
|
||||
flag.StringVar(&migrationContext.OriginalTableName, "table", "", "table name (mandatory)")
|
||||
flag.StringVar(&migrationContext.AlterStatement, "alter", "", "alter statement (mandatory)")
|
||||
flag.BoolVar(&migrationContext.CountTableRows, "exact-rowcount", false, "actually count table rows as opposed to estimate them (results in more accurate progress estimation)")
|
||||
|
||||
quiet := flag.Bool("quiet", false, "quiet")
|
||||
verbose := flag.Bool("verbose", false, "verbose")
|
||||
debug := flag.Bool("debug", false, "debug mode (very verbose)")
|
||||
@ -56,6 +64,17 @@ func main() {
|
||||
// Override!!
|
||||
log.SetLevel(log.ERROR)
|
||||
}
|
||||
|
||||
if migrationContext.DatabaseName == "" {
|
||||
log.Fatalf("--database must be provided and database name must not be empty")
|
||||
}
|
||||
if migrationContext.OriginalTableName == "" {
|
||||
log.Fatalf("--table must be provided and table name must not be empty")
|
||||
}
|
||||
if migrationContext.AlterStatement == "" {
|
||||
log.Fatalf("--alter must be provided and statement must not be empty")
|
||||
}
|
||||
|
||||
log.Info("starting gh-osc")
|
||||
|
||||
if *internalExperiment {
|
||||
@ -69,5 +88,12 @@ func main() {
|
||||
log.Fatale(err)
|
||||
}
|
||||
binlogReader.ReadEntries(*binlogFile, 0, 0)
|
||||
return
|
||||
}
|
||||
migrator := logic.NewMigrator(&connectionConfig)
|
||||
err := migrator.Migrate()
|
||||
if err != nil {
|
||||
log.Fatale(err)
|
||||
}
|
||||
log.Info("Done")
|
||||
}
|
||||
|
325
go/logic/inspect.go
Normal file
325
go/logic/inspect.go
Normal file
@ -0,0 +1,325 @@
|
||||
/*
|
||||
Copyright 2016 GitHub Inc.
|
||||
See https://github.com/github/gh-osc/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
gosql "database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/github/gh-osc/go/base"
|
||||
"github.com/github/gh-osc/go/mysql"
|
||||
"github.com/github/gh-osc/go/sql"
|
||||
|
||||
"github.com/outbrain/golib/log"
|
||||
"github.com/outbrain/golib/sqlutils"
|
||||
)
|
||||
|
||||
// Inspector reads data from the read-MySQL-server (typically a replica, but can be the master)
|
||||
// It is used for gaining initial status and structure, and later also follow up on progress and changelog
|
||||
type Inspector struct {
|
||||
connectionConfig *mysql.ConnectionConfig
|
||||
db *gosql.DB
|
||||
migrationContext *base.MigrationContext
|
||||
}
|
||||
|
||||
func NewInspector(connectionConfig *mysql.ConnectionConfig) *Inspector {
|
||||
return &Inspector{
|
||||
connectionConfig: connectionConfig,
|
||||
migrationContext: base.GetMigrationContext(),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Inspector) InitDBConnections() (err error) {
|
||||
inspectorUri := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", this.connectionConfig.User, this.connectionConfig.Password, this.connectionConfig.Hostname, this.connectionConfig.Port, this.migrationContext.DatabaseName)
|
||||
if this.db, _, err = sqlutils.GetDB(inspectorUri); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := this.validateConnection(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := this.validateGrants(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := this.validateBinlogs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := this.validateTable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if this.migrationContext.CountTableRows {
|
||||
if err := this.countTableRows(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := this.estimateTableRowsViaExplain(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Inspector) InspectTables() (err error) {
|
||||
uniqueKeys, err := this.getCandidateUniqueKeys(this.migrationContext.OriginalTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(uniqueKeys) == 0 {
|
||||
return fmt.Errorf("No PRIMARY nor UNIQUE key found in table! Bailing out")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateConnection issues a simple can-connect to MySQL
|
||||
func (this *Inspector) validateConnection() error {
|
||||
query := `select @@port`
|
||||
var port int
|
||||
if err := this.db.QueryRow(query).Scan(&port); err != nil {
|
||||
return err
|
||||
}
|
||||
if port != this.connectionConfig.Port {
|
||||
return fmt.Errorf("Unexpected database port reported: %+v", port)
|
||||
}
|
||||
log.Infof("connection validated on port %+v", port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateGrants verifies the user by which we're executing has necessary grants
|
||||
// to do its thang.
|
||||
func (this *Inspector) validateGrants() error {
|
||||
query := `show /* gh-osc */ grants for current_user()`
|
||||
foundAll := false
|
||||
foundSuper := false
|
||||
foundReplicationSlave := false
|
||||
foundDBAll := false
|
||||
|
||||
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
|
||||
for _, grantData := range rowMap {
|
||||
grant := grantData.String
|
||||
if strings.Contains(grant, `GRANT ALL PRIVILEGES ON *.*`) {
|
||||
foundAll = true
|
||||
}
|
||||
if strings.Contains(grant, `SUPER`) && strings.Contains(grant, ` ON *.*`) {
|
||||
foundSuper = true
|
||||
}
|
||||
if strings.Contains(grant, `REPLICATION SLAVE`) && strings.Contains(grant, ` ON *.*`) {
|
||||
foundReplicationSlave = true
|
||||
}
|
||||
if strings.Contains(grant, fmt.Sprintf("GRANT ALL PRIVILEGES ON `%s`.*", this.migrationContext.DatabaseName)) {
|
||||
foundDBAll = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return log.Errore(err)
|
||||
}
|
||||
|
||||
if foundAll {
|
||||
log.Infof("User has ALL privileges")
|
||||
return nil
|
||||
}
|
||||
if foundSuper && foundReplicationSlave && foundDBAll {
|
||||
log.Infof("User has SUPER, REPLICATION SLAVE privileges, and has ALL privileges on `%s`", this.migrationContext.DatabaseName)
|
||||
return nil
|
||||
}
|
||||
return log.Errorf("User has insufficient privileges for migration.")
|
||||
}
|
||||
|
||||
// validateConnection issues a simple can-connect to MySQL
|
||||
func (this *Inspector) validateBinlogs() error {
|
||||
query := `select @@global.log_bin, @@global.log_slave_updates, @@global.binlog_format`
|
||||
var hasBinaryLogs, logSlaveUpdates bool
|
||||
if err := this.db.QueryRow(query).Scan(&hasBinaryLogs, &logSlaveUpdates, &this.migrationContext.OriginalBinlogFormat); err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasBinaryLogs {
|
||||
return fmt.Errorf("%s:%d must have binary logs enabled", this.connectionConfig.Hostname, this.connectionConfig.Port)
|
||||
}
|
||||
if !logSlaveUpdates {
|
||||
return fmt.Errorf("%s:%d must have log_slave_updates enabled", this.connectionConfig.Hostname, this.connectionConfig.Port)
|
||||
}
|
||||
if this.migrationContext.RequiresBinlogFormatChange() {
|
||||
query := fmt.Sprintf(`show /* gh-osc */ slave hosts`)
|
||||
countReplicas := 0
|
||||
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
|
||||
countReplicas++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return log.Errore(err)
|
||||
}
|
||||
if countReplicas > 0 {
|
||||
return fmt.Errorf("%s:%d has %s binlog_format, but I'm too scared to change it to ROW because it has replicas. Bailing out", this.connectionConfig.Hostname, this.connectionConfig.Port, this.migrationContext.OriginalBinlogFormat)
|
||||
}
|
||||
log.Infof("%s:%d has %s binlog_format. I will change it to ROW for the duration of this migration.", this.connectionConfig.Hostname, this.connectionConfig.Port, this.migrationContext.OriginalBinlogFormat)
|
||||
}
|
||||
query = `select @@global.binlog_row_image`
|
||||
if err := this.db.QueryRow(query).Scan(&this.migrationContext.OriginalBinlogRowImage); err != nil {
|
||||
// Only as of 5.6. We wish to support 5.5 as well
|
||||
this.migrationContext.OriginalBinlogRowImage = ""
|
||||
}
|
||||
|
||||
log.Infof("binary logs validated on %s:%d", this.connectionConfig.Hostname, this.connectionConfig.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateTable makes sure the table we need to operate on actually exists
|
||||
func (this *Inspector) validateTable() error {
|
||||
query := fmt.Sprintf(`show /* gh-osc */ table status from %s like '%s'`, sql.EscapeName(this.migrationContext.DatabaseName), this.migrationContext.OriginalTableName)
|
||||
|
||||
tableFound := false
|
||||
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
|
||||
this.migrationContext.TableEngine = rowMap.GetString("Engine")
|
||||
this.migrationContext.RowsEstimate = rowMap.GetInt64("Rows")
|
||||
this.migrationContext.UsedRowsEstimateMethod = base.TableStatusRowsEstimate
|
||||
if rowMap.GetString("Comment") == "VIEW" {
|
||||
return fmt.Errorf("%s.%s is a VIEW, not a real table. Bailing out", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||
}
|
||||
tableFound = true
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return log.Errore(err)
|
||||
}
|
||||
if !tableFound {
|
||||
return log.Errorf("Cannot find table %s.%s!", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||
}
|
||||
log.Infof("Table found. Engine=%s", this.migrationContext.TableEngine)
|
||||
log.Debugf("Estimated number of rows via STATUS: %d", this.migrationContext.RowsEstimate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Inspector) estimateTableRowsViaExplain() error {
|
||||
query := fmt.Sprintf(`explain select /* gh-osc */ * from %s.%s where 1=1`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||
|
||||
outputFound := false
|
||||
err := sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
|
||||
this.migrationContext.RowsEstimate = rowMap.GetInt64("rows")
|
||||
this.migrationContext.UsedRowsEstimateMethod = base.ExplainRowsEstimate
|
||||
outputFound = true
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return log.Errore(err)
|
||||
}
|
||||
if !outputFound {
|
||||
return log.Errorf("Cannot run EXPLAIN on %s.%s!", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||
}
|
||||
log.Infof("Estimated number of rows via EXPLAIN: %d", this.migrationContext.RowsEstimate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Inspector) countTableRows() error {
|
||||
log.Infof("As instructed, I'm issuing a SELECT COUNT(*) on the table. This may take a while")
|
||||
query := fmt.Sprintf(`select /* gh-osc */ count(*) as rows from %s.%s`, sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||
if err := this.db.QueryRow(query).Scan(&this.migrationContext.RowsEstimate); err != nil {
|
||||
return err
|
||||
}
|
||||
this.migrationContext.UsedRowsEstimateMethod = base.CountRowsEstimate
|
||||
log.Infof("Exact number of rows via COUNT: %d", this.migrationContext.RowsEstimate)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCandidateUniqueKeys investigates a table and returns the list of unique keys
|
||||
// candidate for chunking
|
||||
func (this *Inspector) getCandidateUniqueKeys(tableName string) (uniqueKeys [](*sql.UniqueKey), err error) {
|
||||
query := `
|
||||
SELECT
|
||||
COLUMNS.TABLE_SCHEMA,
|
||||
COLUMNS.TABLE_NAME,
|
||||
COLUMNS.COLUMN_NAME,
|
||||
UNIQUES.INDEX_NAME,
|
||||
UNIQUES.COLUMN_NAMES,
|
||||
UNIQUES.COUNT_COLUMN_IN_INDEX,
|
||||
COLUMNS.DATA_TYPE,
|
||||
COLUMNS.CHARACTER_SET_NAME,
|
||||
has_nullable
|
||||
FROM INFORMATION_SCHEMA.COLUMNS INNER JOIN (
|
||||
SELECT
|
||||
TABLE_SCHEMA,
|
||||
TABLE_NAME,
|
||||
INDEX_NAME,
|
||||
COUNT(*) AS COUNT_COLUMN_IN_INDEX,
|
||||
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC) AS COLUMN_NAMES,
|
||||
SUBSTRING_INDEX(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC), ',', 1) AS FIRST_COLUMN_NAME,
|
||||
SUM(NULLABLE='YES') > 0 AS has_nullable
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE NON_UNIQUE=0
|
||||
GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
|
||||
) AS UNIQUES
|
||||
ON (
|
||||
COLUMNS.TABLE_SCHEMA = UNIQUES.TABLE_SCHEMA AND
|
||||
COLUMNS.TABLE_NAME = UNIQUES.TABLE_NAME AND
|
||||
COLUMNS.COLUMN_NAME = UNIQUES.FIRST_COLUMN_NAME
|
||||
)
|
||||
WHERE
|
||||
COLUMNS.TABLE_SCHEMA = ?
|
||||
AND COLUMNS.TABLE_NAME = ?
|
||||
ORDER BY
|
||||
COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME,
|
||||
CASE UNIQUES.INDEX_NAME
|
||||
WHEN 'PRIMARY' THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
CASE has_nullable
|
||||
WHEN 0 THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
CASE IFNULL(CHARACTER_SET_NAME, '')
|
||||
WHEN '' THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
CASE DATA_TYPE
|
||||
WHEN 'tinyint' THEN 0
|
||||
WHEN 'smallint' THEN 1
|
||||
WHEN 'int' THEN 2
|
||||
WHEN 'bigint' THEN 3
|
||||
ELSE 100
|
||||
END,
|
||||
COUNT_COLUMN_IN_INDEX
|
||||
`
|
||||
err = sqlutils.QueryRowsMap(this.db, query, func(rowMap sqlutils.RowMap) error {
|
||||
uniqueKey := &sql.UniqueKey{
|
||||
Name: rowMap.GetString("INDEX_NAME"),
|
||||
Columns: *sql.ParseColumnList(rowMap.GetString("COLUMN_NAMES")),
|
||||
HasNullable: rowMap.GetBool("has_nullable"),
|
||||
}
|
||||
uniqueKeys = append(uniqueKeys, uniqueKey)
|
||||
return nil
|
||||
}, this.migrationContext.DatabaseName, tableName)
|
||||
if err != nil {
|
||||
return uniqueKeys, err
|
||||
}
|
||||
log.Debugf("Potential unique keys: %+v", uniqueKeys)
|
||||
return uniqueKeys, nil
|
||||
}
|
||||
|
||||
// getCandidateUniqueKeys investigates a table and returns the list of unique keys
|
||||
// candidate for chunking
|
||||
func (this *Inspector) getSharedUniqueKeys() (uniqueKeys [](*sql.UniqueKey), err error) {
|
||||
originalUniqueKeys, err := this.getCandidateUniqueKeys(this.migrationContext.OriginalTableName)
|
||||
if err != nil {
|
||||
return uniqueKeys, err
|
||||
}
|
||||
ghostUniqueKeys, err := this.getCandidateUniqueKeys(this.migrationContext.GhostTableName)
|
||||
if err != nil {
|
||||
return uniqueKeys, err
|
||||
}
|
||||
// We actually do NOT rely on key name, just on the set of columns. This is because maybe
|
||||
// the ALTER is on the name itself...
|
||||
for _, originalUniqueKey := range originalUniqueKeys {
|
||||
for _, ghostUniqueKey := range ghostUniqueKeys {
|
||||
if originalUniqueKey.Columns.Equals(&ghostUniqueKey.Columns) {
|
||||
uniqueKeys = append(uniqueKeys, originalUniqueKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
return uniqueKeys, nil
|
||||
}
|
33
go/logic/migrator.go
Normal file
33
go/logic/migrator.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2016 GitHub Inc.
|
||||
See https://github.com/github/gh-osc/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"github.com/github/gh-osc/go/mysql"
|
||||
)
|
||||
|
||||
// Migrator is the main schema migration flow manager.
|
||||
type Migrator struct {
|
||||
connectionConfig *mysql.ConnectionConfig
|
||||
inspector *Inspector
|
||||
}
|
||||
|
||||
func NewMigrator(connectionConfig *mysql.ConnectionConfig) *Migrator {
|
||||
return &Migrator{
|
||||
connectionConfig: connectionConfig,
|
||||
inspector: NewInspector(connectionConfig),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Migrator) Migrate() error {
|
||||
if err := this.inspector.InitDBConnections(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := this.inspector.InspectTables(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -113,10 +113,26 @@ func BuildRangeComparison(columns []string, values []string, comparisonSign Valu
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func BuildRangePreparedComparison(columns []string, comparisonSign ValueComparisonSign) (result string, err error) {
|
||||
values := make([]string, len(columns), len(columns))
|
||||
for i := range columns {
|
||||
values[i] = "?"
|
||||
}
|
||||
return BuildRangeComparison(columns, values, comparisonSign)
|
||||
}
|
||||
|
||||
func BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName string, sharedColumns []string, uniqueKey string, uniqueKeyColumns, rangeStartValues, rangeEndValues []string) (string, error) {
|
||||
if len(sharedColumns) == 0 {
|
||||
return "", fmt.Errorf("Got 0 shared columns in BuildRangeInsertQuery")
|
||||
}
|
||||
databaseName = EscapeName(databaseName)
|
||||
originalTableName = EscapeName(originalTableName)
|
||||
ghostTableName = EscapeName(ghostTableName)
|
||||
for i := range sharedColumns {
|
||||
sharedColumns[i] = EscapeName(sharedColumns[i])
|
||||
}
|
||||
uniqueKey = EscapeName(uniqueKey)
|
||||
|
||||
sharedColumnsListing := strings.Join(sharedColumns, ", ")
|
||||
rangeStartComparison, err := BuildRangeComparison(uniqueKeyColumns, rangeStartValues, GreaterThanOrEqualsComparisonSign)
|
||||
if err != nil {
|
||||
@ -146,3 +162,49 @@ func BuildRangeInsertPreparedQuery(databaseName, originalTableName, ghostTableNa
|
||||
}
|
||||
return BuildRangeInsertQuery(databaseName, originalTableName, ghostTableName, sharedColumns, uniqueKey, uniqueKeyColumns, rangeStartValues, rangeEndValues)
|
||||
}
|
||||
|
||||
func BuildUniqueKeyRangeEndPreparedQuery(databaseName, originalTableName string, uniqueKeyColumns []string, chunkSize int) (string, error) {
|
||||
if len(uniqueKeyColumns) == 0 {
|
||||
return "", fmt.Errorf("Got 0 shared columns in BuildRangeInsertQuery")
|
||||
}
|
||||
databaseName = EscapeName(databaseName)
|
||||
originalTableName = EscapeName(originalTableName)
|
||||
|
||||
rangeStartComparison, err := BuildRangePreparedComparison(uniqueKeyColumns, GreaterThanComparisonSign)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rangeEndComparison, err := BuildRangePreparedComparison(uniqueKeyColumns, LessThanOrEqualsComparisonSign)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uniqueKeyColumnAscending := make([]string, len(uniqueKeyColumns), len(uniqueKeyColumns))
|
||||
uniqueKeyColumnDescending := make([]string, len(uniqueKeyColumns), len(uniqueKeyColumns))
|
||||
for i := range uniqueKeyColumns {
|
||||
uniqueKeyColumns[i] = EscapeName(uniqueKeyColumns[i])
|
||||
uniqueKeyColumnAscending[i] = fmt.Sprintf("%s asc", uniqueKeyColumns[i])
|
||||
uniqueKeyColumnDescending[i] = fmt.Sprintf("%s desc", uniqueKeyColumns[i])
|
||||
}
|
||||
query := fmt.Sprintf(`
|
||||
select /* gh-osc %s.%s */ %s
|
||||
from (
|
||||
select
|
||||
%s
|
||||
from
|
||||
%s.%s
|
||||
where %s and %s
|
||||
order by
|
||||
%s
|
||||
limit %d
|
||||
) select_osc_chunk
|
||||
order by
|
||||
%s
|
||||
limit 1
|
||||
`, databaseName, originalTableName, strings.Join(uniqueKeyColumns, ", "),
|
||||
strings.Join(uniqueKeyColumns, ", "), databaseName, originalTableName,
|
||||
rangeStartComparison, rangeEndComparison,
|
||||
strings.Join(uniqueKeyColumnAscending, ", "), chunkSize,
|
||||
strings.Join(uniqueKeyColumnDescending, ", "),
|
||||
)
|
||||
return query, nil
|
||||
}
|
||||
|
@ -176,3 +176,32 @@ func TestBuildRangeInsertPreparedQuery(t *testing.T) {
|
||||
test.S(t).ExpectEquals(normalizeQuery(query), normalizeQuery(expected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUniqueKeyRangeEndPreparedQuery(t *testing.T) {
|
||||
databaseName := "mydb"
|
||||
originalTableName := "tbl"
|
||||
chunkSize := 500
|
||||
{
|
||||
uniqueKeyColumns := []string{"name", "position"}
|
||||
|
||||
query, err := BuildUniqueKeyRangeEndPreparedQuery(databaseName, originalTableName, uniqueKeyColumns, chunkSize)
|
||||
test.S(t).ExpectNil(err)
|
||||
expected := `
|
||||
select /* gh-osc mydb.tbl */ name, position
|
||||
from (
|
||||
select
|
||||
name, position
|
||||
from
|
||||
mydb.tbl
|
||||
where ((name > ?) or (((name = ?)) AND (position > ?))) and ((name < ?) or (((name = ?)) AND (position < ?)) or ((name = ?) and (position = ?)))
|
||||
order by
|
||||
name asc, position asc
|
||||
limit 500
|
||||
) select_osc_chunk
|
||||
order by
|
||||
name desc, position desc
|
||||
limit 1
|
||||
`
|
||||
test.S(t).ExpectEquals(normalizeQuery(query), normalizeQuery(expected))
|
||||
}
|
||||
}
|
||||
|
45
go/sql/types.go
Normal file
45
go/sql/types.go
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2016 GitHub Inc.
|
||||
See https://github.com/github/gh-osc/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ColumnList makes for a named list of columns
|
||||
type ColumnList []string
|
||||
|
||||
// ParseColumnList parses a comma delimited list of column names
|
||||
func ParseColumnList(columns string) *ColumnList {
|
||||
result := ColumnList(strings.Split(columns, ","))
|
||||
return &result
|
||||
}
|
||||
|
||||
func (this *ColumnList) String() string {
|
||||
return strings.Join(*this, ",")
|
||||
}
|
||||
|
||||
func (this *ColumnList) Equals(other *ColumnList) bool {
|
||||
return reflect.DeepEqual(*this, *other)
|
||||
}
|
||||
|
||||
// UniqueKey is the combination of a key's name and columns
|
||||
type UniqueKey struct {
|
||||
Name string
|
||||
Columns ColumnList
|
||||
HasNullable bool
|
||||
}
|
||||
|
||||
// IsPrimary cehcks if this unique key is primary
|
||||
func (this *UniqueKey) IsPrimary() bool {
|
||||
return this.Name == "PRIMARY"
|
||||
}
|
||||
|
||||
func (this *UniqueKey) String() string {
|
||||
return fmt.Sprintf("%s: %s; has nullable: %+v", this.Name, this.Columns, this.HasNullable)
|
||||
}
|
8
vendor/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
8
vendor/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
10
vendor/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- tip
|
||||
|
||||
before_script:
|
||||
- mysql -e 'create database gotest;'
|
46
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
46
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
|
||||
|
||||
# If you are submitting a patch, please add your name or the name of the
|
||||
# organization which holds the copyright to this list in alphabetical order.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name <email address>
|
||||
# The email address is not required for organizations.
|
||||
# Please keep the list sorted.
|
||||
|
||||
|
||||
# Individual Persons
|
||||
|
||||
Aaron Hopkins <go-sql-driver at die.net>
|
||||
Arne Hormann <arnehormann at gmail.com>
|
||||
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||
Chris Moos <chris at tech9computers.com>
|
||||
DisposaBoy <disposaboy at dby.me>
|
||||
Frederick Mayle <frederickmayle at gmail.com>
|
||||
Gustavo Kristic <gkristic at gmail.com>
|
||||
Hanno Braun <mail at hannobraun.com>
|
||||
Henri Yandell <flamefew at gmail.com>
|
||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||
INADA Naoki <songofacandy at gmail.com>
|
||||
James Harr <james.harr at gmail.com>
|
||||
Jian Zhen <zhenjl at gmail.com>
|
||||
Joshua Prunier <joshua.prunier at gmail.com>
|
||||
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||
Kamil Dziedzic <kamil at klecza.pl>
|
||||
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||
Lucas Liu <extrafliu at gmail.com>
|
||||
Luke Scott <luke at webconnex.com>
|
||||
Michael Woolnough <michael.woolnough at gmail.com>
|
||||
Nicola Peduzzi <thenikso at gmail.com>
|
||||
Runrioter Wung <runrioter at gmail.com>
|
||||
Soroush Pour <me at soroushjp.com>
|
||||
Stan Putrya <root.vagner at gmail.com>
|
||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||
Xiuming Chen <cc at cxm.cc>
|
||||
Julien Lefevre <julien.lefevr at gmail.com>
|
||||
|
||||
# Organizations
|
||||
|
||||
Barracuda Networks, Inc.
|
||||
Google Inc.
|
||||
Stripe Inc.
|
92
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
92
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
## HEAD
|
||||
|
||||
Changes:
|
||||
|
||||
- Go 1.1 is no longer supported
|
||||
- Use decimals field from MySQL to format time types (#249)
|
||||
- Buffer optimizations (#269)
|
||||
- TLS ServerName defaults to the host (#283)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
|
||||
- Fixed handling of queries without columns and rows (#255)
|
||||
- Fixed a panic when SetKeepAlive() failed (#298)
|
||||
|
||||
New Features:
|
||||
- Support for returning table alias on Columns() (#289)
|
||||
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318)
|
||||
|
||||
|
||||
## Version 1.2 (2014-06-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- We switched back to a "rolling release". `go get` installs the current master branch again
|
||||
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
|
||||
- Exported errors to allow easy checking from application code
|
||||
- Enabled TCP Keepalives on TCP connections
|
||||
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
|
||||
- The DSN parser also checks for a missing separating slash
|
||||
- Faster binary date / datetime to string formatting
|
||||
- Also exported the MySQLWarning type
|
||||
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
|
||||
- writePacket() automatically writes the packet size to the header
|
||||
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
|
||||
|
||||
New Features:
|
||||
|
||||
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
|
||||
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
|
||||
- Logging of critical errors is configurable with `SetLogger`
|
||||
- Google CloudSQL support
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Allow more than 32 parameters in prepared statements
|
||||
- Various old_password fixes
|
||||
- Fixed TestConcurrent test to pass Go's race detection
|
||||
- Fixed appendLengthEncodedInteger for large numbers
|
||||
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
|
||||
|
||||
|
||||
## Version 1.1 (2013-11-02)
|
||||
|
||||
Changes:
|
||||
|
||||
- Go-MySQL-Driver now requires Go 1.1
|
||||
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
|
||||
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
|
||||
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
|
||||
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
|
||||
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
|
||||
- Optimized the buffer for reading
|
||||
- stmt.Query now caches column metadata
|
||||
- New Logo
|
||||
- Changed the copyright header to include all contributors
|
||||
- Improved the LOAD INFILE documentation
|
||||
- The driver struct is now exported to make the driver directly accessible
|
||||
- Refactored the driver tests
|
||||
- Added more benchmarks and moved all to a separate file
|
||||
- Other small refactoring
|
||||
|
||||
New Features:
|
||||
|
||||
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
|
||||
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
|
||||
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
|
||||
- Convert to DB timezone when inserting `time.Time`
|
||||
- Splitted packets (more than 16MB) are now merged correctly
|
||||
- Fixed false positive `io.EOF` errors when the data was fully read
|
||||
- Avoid panics on reuse of closed connections
|
||||
- Fixed empty string producing false nil values
|
||||
- Fixed sign byte for positive TIME fields
|
||||
|
||||
|
||||
## Version 1.0 (2013-05-14)
|
||||
|
||||
Initial Release
|
40
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
40
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
||||
|
||||
Please provide the following minimum information:
|
||||
* Your Go-MySQL-Driver version (or git SHA)
|
||||
* Your Go version (run `go version` in your console)
|
||||
* A detailed issue description
|
||||
* Error Log if present
|
||||
* If possible, a short example
|
||||
|
||||
|
||||
## Contributing Code
|
||||
|
||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
||||
Don't forget to add yourself to the AUTHORS file.
|
||||
|
||||
### Pull Requests Checklist
|
||||
|
||||
Please check the following points before submitting your pull request:
|
||||
- [x] Code compiles correctly
|
||||
- [x] Created tests, if possible
|
||||
- [x] All tests pass
|
||||
- [x] Extended the README / documentation, if necessary
|
||||
- [x] Added yourself to the AUTHORS file
|
||||
|
||||
### Code Review
|
||||
|
||||
Everyone is invited to review and comment on pull requests.
|
||||
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
||||
|
||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
||||
|
||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
||||
|
||||
## Development Ideas
|
||||
|
||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
386
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
386
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
@ -0,0 +1,386 @@
|
||||
# Go-MySQL-Driver
|
||||
|
||||
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
|
||||
|
||||
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
|
||||
|
||||
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
|
||||
|
||||
[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
|
||||
|
||||
---------------------------------------
|
||||
* [Features](#features)
|
||||
* [Requirements](#requirements)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [DSN (Data Source Name)](#dsn-data-source-name)
|
||||
* [Password](#password)
|
||||
* [Protocol](#protocol)
|
||||
* [Address](#address)
|
||||
* [Parameters](#parameters)
|
||||
* [Examples](#examples)
|
||||
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||
* [time.Time support](#timetime-support)
|
||||
* [Unicode support](#unicode-support)
|
||||
* [Testing / Development](#testing--development)
|
||||
* [License](#license)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Features
|
||||
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
||||
* Native Go implementation. No C-bindings, just pure Go
|
||||
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
|
||||
* Automatic handling of broken connections
|
||||
* Automatic Connection Pooling *(by database/sql package)*
|
||||
* Supports queries larger than 16MB
|
||||
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
|
||||
* Intelligent `LONG DATA` handling in prepared statements
|
||||
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
||||
* Optional `time.Time` parsing
|
||||
* Optional placeholder interpolation
|
||||
|
||||
## Requirements
|
||||
* Go 1.2 or higher
|
||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Installation
|
||||
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
|
||||
```bash
|
||||
$ go get github.com/go-sql-driver/mysql
|
||||
```
|
||||
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||
|
||||
## Usage
|
||||
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
|
||||
|
||||
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
||||
```go
|
||||
import "database/sql"
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
|
||||
db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
```
|
||||
|
||||
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
|
||||
|
||||
|
||||
### DSN (Data Source Name)
|
||||
|
||||
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
|
||||
```
|
||||
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
```
|
||||
|
||||
A DSN in its fullest form:
|
||||
```
|
||||
username:password@protocol(address)/dbname?param=value
|
||||
```
|
||||
|
||||
Except for the databasename, all values are optional. So the minimal DSN is:
|
||||
```
|
||||
/dbname
|
||||
```
|
||||
|
||||
If you do not want to preselect a database, leave `dbname` empty:
|
||||
```
|
||||
/
|
||||
```
|
||||
This has the same effect as an empty DSN string:
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
#### Password
|
||||
Passwords can consist of any character. Escaping is **not** necessary.
|
||||
|
||||
#### Protocol
|
||||
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
|
||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||
|
||||
#### Address
|
||||
For TCP and UDP networks, addresses have the form `host:port`.
|
||||
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||
|
||||
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
||||
|
||||
#### Parameters
|
||||
*Parameters are case-sensitive!*
|
||||
|
||||
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
|
||||
|
||||
##### `allowAllFiles`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
|
||||
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
|
||||
|
||||
##### `allowCleartextPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
|
||||
|
||||
##### `allowOldPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
|
||||
|
||||
##### `charset`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: none
|
||||
```
|
||||
|
||||
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
|
||||
|
||||
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
||||
Unless you need the fallback behavior, please use `collation` instead.
|
||||
|
||||
##### `collation`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: utf8_general_ci
|
||||
```
|
||||
|
||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||
|
||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||
|
||||
##### `clientFoundRows`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
|
||||
|
||||
##### `columnsWithAlias`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
|
||||
|
||||
```
|
||||
SELECT u.id FROM users as u
|
||||
```
|
||||
|
||||
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
|
||||
|
||||
##### `interpolateParams`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
|
||||
|
||||
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
|
||||
|
||||
##### `loc`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <escaped name>
|
||||
Default: UTC
|
||||
```
|
||||
|
||||
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
|
||||
|
||||
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
|
||||
|
||||
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
||||
|
||||
|
||||
##### `parseTime`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||
|
||||
|
||||
##### `strict`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`strict=true` enables the strict mode in which MySQL warnings are treated as errors.
|
||||
|
||||
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example.
|
||||
|
||||
|
||||
##### `timeout`
|
||||
|
||||
```
|
||||
Type: decimal number
|
||||
Default: OS default
|
||||
```
|
||||
|
||||
*Driver* side connection timeout. The value must be a string of decimal numbers, each with optional fraction and a unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
|
||||
|
||||
|
||||
##### `tls`
|
||||
|
||||
```
|
||||
Type: bool / string
|
||||
Valid Values: true, false, skip-verify, <name>
|
||||
Default: false
|
||||
```
|
||||
|
||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||
|
||||
|
||||
##### System Variables
|
||||
|
||||
All other parameters are interpreted as system variables:
|
||||
* `autocommit`: `"SET autocommit=<value>"`
|
||||
* [`time_zone`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `"SET time_zone=<value>"`
|
||||
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"`
|
||||
* `param`: `"SET <param>=<value>"`
|
||||
|
||||
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
|
||||
|
||||
#### Examples
|
||||
```
|
||||
user@unix(/path/to/socket)/dbname
|
||||
```
|
||||
|
||||
```
|
||||
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
|
||||
```
|
||||
|
||||
```
|
||||
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
|
||||
```
|
||||
|
||||
Use the [strict mode](#strict) but ignore notes:
|
||||
```
|
||||
user:password@/dbname?strict=true&sql_notes=false
|
||||
```
|
||||
|
||||
TCP via IPv6:
|
||||
```
|
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
TCP on a remote host, e.g. Amazon RDS:
|
||||
```
|
||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||
```
|
||||
|
||||
Google Cloud SQL on App Engine:
|
||||
```
|
||||
user@cloudsql(project-id:instance-name)/dbname
|
||||
```
|
||||
|
||||
TCP using default port (3306) on localhost:
|
||||
```
|
||||
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
|
||||
```
|
||||
|
||||
Use the default protocol (tcp) and host (localhost:3306):
|
||||
```
|
||||
user:password@/dbname
|
||||
```
|
||||
|
||||
No Database preselected:
|
||||
```
|
||||
user:password@/
|
||||
```
|
||||
|
||||
### `LOAD DATA LOCAL INFILE` support
|
||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||
```go
|
||||
import "github.com/go-sql-driver/mysql"
|
||||
```
|
||||
|
||||
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
|
||||
|
||||
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
|
||||
|
||||
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
||||
|
||||
|
||||
### `time.Time` support
|
||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
|
||||
|
||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||
|
||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||
|
||||
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
||||
|
||||
|
||||
### Unicode support
|
||||
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
|
||||
|
||||
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
||||
|
||||
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
||||
|
||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||
|
||||
|
||||
## Testing / Development
|
||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||
|
||||
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
|
||||
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
|
||||
|
||||
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## License
|
||||
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||
Mozilla summarizes the license scope as follows:
|
||||
> MPL: The copyleft applies to any files containing MPLed code.
|
||||
|
||||
|
||||
That means:
|
||||
* You can **use** the **unchanged** source code both in private and commercially
|
||||
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
|
||||
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
|
||||
|
||||
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
|
||||
|
||||
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
|
||||
|
19
vendor/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
19
vendor/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"appengine/cloudsql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDial("cloudsql", cloudsql.Dial)
|
||||
}
|
136
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
136
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import "io"
|
||||
|
||||
const defaultBufSize = 4096
|
||||
|
||||
// A buffer which is used for both reading and writing.
|
||||
// This is possible since communication on each connection is synchronous.
|
||||
// In other words, we can't write and read simultaneously on the same connection.
|
||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||
// Also highly optimized for this particular use case.
|
||||
type buffer struct {
|
||||
buf []byte
|
||||
rd io.Reader
|
||||
idx int
|
||||
length int
|
||||
}
|
||||
|
||||
func newBuffer(rd io.Reader) buffer {
|
||||
var b [defaultBufSize]byte
|
||||
return buffer{
|
||||
buf: b[:],
|
||||
rd: rd,
|
||||
}
|
||||
}
|
||||
|
||||
// fill reads into the buffer until at least _need_ bytes are in it
|
||||
func (b *buffer) fill(need int) error {
|
||||
n := b.length
|
||||
|
||||
// move existing data to the beginning
|
||||
if n > 0 && b.idx > 0 {
|
||||
copy(b.buf[0:n], b.buf[b.idx:])
|
||||
}
|
||||
|
||||
// grow buffer if necessary
|
||||
// TODO: let the buffer shrink again at some point
|
||||
// Maybe keep the org buf slice and swap back?
|
||||
if need > len(b.buf) {
|
||||
// Round up to the next multiple of the default size
|
||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||
copy(newBuf, b.buf)
|
||||
b.buf = newBuf
|
||||
}
|
||||
|
||||
b.idx = 0
|
||||
|
||||
for {
|
||||
nn, err := b.rd.Read(b.buf[n:])
|
||||
n += nn
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
if n < need {
|
||||
continue
|
||||
}
|
||||
b.length = n
|
||||
return nil
|
||||
|
||||
case io.EOF:
|
||||
if n >= need {
|
||||
b.length = n
|
||||
return nil
|
||||
}
|
||||
return io.ErrUnexpectedEOF
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns next N bytes from buffer.
|
||||
// The returned slice is only guaranteed to be valid until the next read
|
||||
func (b *buffer) readNext(need int) ([]byte, error) {
|
||||
if b.length < need {
|
||||
// refill
|
||||
if err := b.fill(need); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
offset := b.idx
|
||||
b.idx += need
|
||||
b.length -= need
|
||||
return b.buf[offset:b.idx], nil
|
||||
}
|
||||
|
||||
// returns a buffer with the requested size.
|
||||
// If possible, a slice from the existing buffer is returned.
|
||||
// Otherwise a bigger buffer is made.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeBuffer(length int) []byte {
|
||||
if b.length > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// test (cheap) general case first
|
||||
if length <= defaultBufSize || length <= cap(b.buf) {
|
||||
return b.buf[:length]
|
||||
}
|
||||
|
||||
if length < maxPacketSize {
|
||||
b.buf = make([]byte, length)
|
||||
return b.buf
|
||||
}
|
||||
return make([]byte, length)
|
||||
}
|
||||
|
||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
||||
// smaller than defaultBufSize
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
||||
if b.length == 0 {
|
||||
return b.buf[:length]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// takeCompleteBuffer returns the complete existing buffer.
|
||||
// This can be used if the necessary buffer size is unknown.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeCompleteBuffer() []byte {
|
||||
if b.length == 0 {
|
||||
return b.buf
|
||||
}
|
||||
return nil
|
||||
}
|
250
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
250
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const defaultCollation byte = 33 // utf8_general_ci
|
||||
|
||||
// A list of available collations mapped to the internal ID.
|
||||
// To update this map use the following MySQL query:
|
||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
||||
var collations = map[string]byte{
|
||||
"big5_chinese_ci": 1,
|
||||
"latin2_czech_cs": 2,
|
||||
"dec8_swedish_ci": 3,
|
||||
"cp850_general_ci": 4,
|
||||
"latin1_german1_ci": 5,
|
||||
"hp8_english_ci": 6,
|
||||
"koi8r_general_ci": 7,
|
||||
"latin1_swedish_ci": 8,
|
||||
"latin2_general_ci": 9,
|
||||
"swe7_swedish_ci": 10,
|
||||
"ascii_general_ci": 11,
|
||||
"ujis_japanese_ci": 12,
|
||||
"sjis_japanese_ci": 13,
|
||||
"cp1251_bulgarian_ci": 14,
|
||||
"latin1_danish_ci": 15,
|
||||
"hebrew_general_ci": 16,
|
||||
"tis620_thai_ci": 18,
|
||||
"euckr_korean_ci": 19,
|
||||
"latin7_estonian_cs": 20,
|
||||
"latin2_hungarian_ci": 21,
|
||||
"koi8u_general_ci": 22,
|
||||
"cp1251_ukrainian_ci": 23,
|
||||
"gb2312_chinese_ci": 24,
|
||||
"greek_general_ci": 25,
|
||||
"cp1250_general_ci": 26,
|
||||
"latin2_croatian_ci": 27,
|
||||
"gbk_chinese_ci": 28,
|
||||
"cp1257_lithuanian_ci": 29,
|
||||
"latin5_turkish_ci": 30,
|
||||
"latin1_german2_ci": 31,
|
||||
"armscii8_general_ci": 32,
|
||||
"utf8_general_ci": 33,
|
||||
"cp1250_czech_cs": 34,
|
||||
"ucs2_general_ci": 35,
|
||||
"cp866_general_ci": 36,
|
||||
"keybcs2_general_ci": 37,
|
||||
"macce_general_ci": 38,
|
||||
"macroman_general_ci": 39,
|
||||
"cp852_general_ci": 40,
|
||||
"latin7_general_ci": 41,
|
||||
"latin7_general_cs": 42,
|
||||
"macce_bin": 43,
|
||||
"cp1250_croatian_ci": 44,
|
||||
"utf8mb4_general_ci": 45,
|
||||
"utf8mb4_bin": 46,
|
||||
"latin1_bin": 47,
|
||||
"latin1_general_ci": 48,
|
||||
"latin1_general_cs": 49,
|
||||
"cp1251_bin": 50,
|
||||
"cp1251_general_ci": 51,
|
||||
"cp1251_general_cs": 52,
|
||||
"macroman_bin": 53,
|
||||
"utf16_general_ci": 54,
|
||||
"utf16_bin": 55,
|
||||
"utf16le_general_ci": 56,
|
||||
"cp1256_general_ci": 57,
|
||||
"cp1257_bin": 58,
|
||||
"cp1257_general_ci": 59,
|
||||
"utf32_general_ci": 60,
|
||||
"utf32_bin": 61,
|
||||
"utf16le_bin": 62,
|
||||
"binary": 63,
|
||||
"armscii8_bin": 64,
|
||||
"ascii_bin": 65,
|
||||
"cp1250_bin": 66,
|
||||
"cp1256_bin": 67,
|
||||
"cp866_bin": 68,
|
||||
"dec8_bin": 69,
|
||||
"greek_bin": 70,
|
||||
"hebrew_bin": 71,
|
||||
"hp8_bin": 72,
|
||||
"keybcs2_bin": 73,
|
||||
"koi8r_bin": 74,
|
||||
"koi8u_bin": 75,
|
||||
"latin2_bin": 77,
|
||||
"latin5_bin": 78,
|
||||
"latin7_bin": 79,
|
||||
"cp850_bin": 80,
|
||||
"cp852_bin": 81,
|
||||
"swe7_bin": 82,
|
||||
"utf8_bin": 83,
|
||||
"big5_bin": 84,
|
||||
"euckr_bin": 85,
|
||||
"gb2312_bin": 86,
|
||||
"gbk_bin": 87,
|
||||
"sjis_bin": 88,
|
||||
"tis620_bin": 89,
|
||||
"ucs2_bin": 90,
|
||||
"ujis_bin": 91,
|
||||
"geostd8_general_ci": 92,
|
||||
"geostd8_bin": 93,
|
||||
"latin1_spanish_ci": 94,
|
||||
"cp932_japanese_ci": 95,
|
||||
"cp932_bin": 96,
|
||||
"eucjpms_japanese_ci": 97,
|
||||
"eucjpms_bin": 98,
|
||||
"cp1250_polish_ci": 99,
|
||||
"utf16_unicode_ci": 101,
|
||||
"utf16_icelandic_ci": 102,
|
||||
"utf16_latvian_ci": 103,
|
||||
"utf16_romanian_ci": 104,
|
||||
"utf16_slovenian_ci": 105,
|
||||
"utf16_polish_ci": 106,
|
||||
"utf16_estonian_ci": 107,
|
||||
"utf16_spanish_ci": 108,
|
||||
"utf16_swedish_ci": 109,
|
||||
"utf16_turkish_ci": 110,
|
||||
"utf16_czech_ci": 111,
|
||||
"utf16_danish_ci": 112,
|
||||
"utf16_lithuanian_ci": 113,
|
||||
"utf16_slovak_ci": 114,
|
||||
"utf16_spanish2_ci": 115,
|
||||
"utf16_roman_ci": 116,
|
||||
"utf16_persian_ci": 117,
|
||||
"utf16_esperanto_ci": 118,
|
||||
"utf16_hungarian_ci": 119,
|
||||
"utf16_sinhala_ci": 120,
|
||||
"utf16_german2_ci": 121,
|
||||
"utf16_croatian_ci": 122,
|
||||
"utf16_unicode_520_ci": 123,
|
||||
"utf16_vietnamese_ci": 124,
|
||||
"ucs2_unicode_ci": 128,
|
||||
"ucs2_icelandic_ci": 129,
|
||||
"ucs2_latvian_ci": 130,
|
||||
"ucs2_romanian_ci": 131,
|
||||
"ucs2_slovenian_ci": 132,
|
||||
"ucs2_polish_ci": 133,
|
||||
"ucs2_estonian_ci": 134,
|
||||
"ucs2_spanish_ci": 135,
|
||||
"ucs2_swedish_ci": 136,
|
||||
"ucs2_turkish_ci": 137,
|
||||
"ucs2_czech_ci": 138,
|
||||
"ucs2_danish_ci": 139,
|
||||
"ucs2_lithuanian_ci": 140,
|
||||
"ucs2_slovak_ci": 141,
|
||||
"ucs2_spanish2_ci": 142,
|
||||
"ucs2_roman_ci": 143,
|
||||
"ucs2_persian_ci": 144,
|
||||
"ucs2_esperanto_ci": 145,
|
||||
"ucs2_hungarian_ci": 146,
|
||||
"ucs2_sinhala_ci": 147,
|
||||
"ucs2_german2_ci": 148,
|
||||
"ucs2_croatian_ci": 149,
|
||||
"ucs2_unicode_520_ci": 150,
|
||||
"ucs2_vietnamese_ci": 151,
|
||||
"ucs2_general_mysql500_ci": 159,
|
||||
"utf32_unicode_ci": 160,
|
||||
"utf32_icelandic_ci": 161,
|
||||
"utf32_latvian_ci": 162,
|
||||
"utf32_romanian_ci": 163,
|
||||
"utf32_slovenian_ci": 164,
|
||||
"utf32_polish_ci": 165,
|
||||
"utf32_estonian_ci": 166,
|
||||
"utf32_spanish_ci": 167,
|
||||
"utf32_swedish_ci": 168,
|
||||
"utf32_turkish_ci": 169,
|
||||
"utf32_czech_ci": 170,
|
||||
"utf32_danish_ci": 171,
|
||||
"utf32_lithuanian_ci": 172,
|
||||
"utf32_slovak_ci": 173,
|
||||
"utf32_spanish2_ci": 174,
|
||||
"utf32_roman_ci": 175,
|
||||
"utf32_persian_ci": 176,
|
||||
"utf32_esperanto_ci": 177,
|
||||
"utf32_hungarian_ci": 178,
|
||||
"utf32_sinhala_ci": 179,
|
||||
"utf32_german2_ci": 180,
|
||||
"utf32_croatian_ci": 181,
|
||||
"utf32_unicode_520_ci": 182,
|
||||
"utf32_vietnamese_ci": 183,
|
||||
"utf8_unicode_ci": 192,
|
||||
"utf8_icelandic_ci": 193,
|
||||
"utf8_latvian_ci": 194,
|
||||
"utf8_romanian_ci": 195,
|
||||
"utf8_slovenian_ci": 196,
|
||||
"utf8_polish_ci": 197,
|
||||
"utf8_estonian_ci": 198,
|
||||
"utf8_spanish_ci": 199,
|
||||
"utf8_swedish_ci": 200,
|
||||
"utf8_turkish_ci": 201,
|
||||
"utf8_czech_ci": 202,
|
||||
"utf8_danish_ci": 203,
|
||||
"utf8_lithuanian_ci": 204,
|
||||
"utf8_slovak_ci": 205,
|
||||
"utf8_spanish2_ci": 206,
|
||||
"utf8_roman_ci": 207,
|
||||
"utf8_persian_ci": 208,
|
||||
"utf8_esperanto_ci": 209,
|
||||
"utf8_hungarian_ci": 210,
|
||||
"utf8_sinhala_ci": 211,
|
||||
"utf8_german2_ci": 212,
|
||||
"utf8_croatian_ci": 213,
|
||||
"utf8_unicode_520_ci": 214,
|
||||
"utf8_vietnamese_ci": 215,
|
||||
"utf8_general_mysql500_ci": 223,
|
||||
"utf8mb4_unicode_ci": 224,
|
||||
"utf8mb4_icelandic_ci": 225,
|
||||
"utf8mb4_latvian_ci": 226,
|
||||
"utf8mb4_romanian_ci": 227,
|
||||
"utf8mb4_slovenian_ci": 228,
|
||||
"utf8mb4_polish_ci": 229,
|
||||
"utf8mb4_estonian_ci": 230,
|
||||
"utf8mb4_spanish_ci": 231,
|
||||
"utf8mb4_swedish_ci": 232,
|
||||
"utf8mb4_turkish_ci": 233,
|
||||
"utf8mb4_czech_ci": 234,
|
||||
"utf8mb4_danish_ci": 235,
|
||||
"utf8mb4_lithuanian_ci": 236,
|
||||
"utf8mb4_slovak_ci": 237,
|
||||
"utf8mb4_spanish2_ci": 238,
|
||||
"utf8mb4_roman_ci": 239,
|
||||
"utf8mb4_persian_ci": 240,
|
||||
"utf8mb4_esperanto_ci": 241,
|
||||
"utf8mb4_hungarian_ci": 242,
|
||||
"utf8mb4_sinhala_ci": 243,
|
||||
"utf8mb4_german2_ci": 244,
|
||||
"utf8mb4_croatian_ci": 245,
|
||||
"utf8mb4_unicode_520_ci": 246,
|
||||
"utf8mb4_vietnamese_ci": 247,
|
||||
}
|
||||
|
||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||
var unsafeCollations = map[byte]bool{
|
||||
1: true, // big5_chinese_ci
|
||||
13: true, // sjis_japanese_ci
|
||||
28: true, // gbk_chinese_ci
|
||||
84: true, // big5_bin
|
||||
86: true, // gb2312_bin
|
||||
87: true, // gbk_bin
|
||||
88: true, // sjis_bin
|
||||
95: true, // cp932_japanese_ci
|
||||
96: true, // cp932_bin
|
||||
}
|
403
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
403
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
@ -0,0 +1,403 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mysqlConn struct {
|
||||
buf buffer
|
||||
netConn net.Conn
|
||||
affectedRows uint64
|
||||
insertId uint64
|
||||
cfg *config
|
||||
maxPacketAllowed int
|
||||
maxWriteSize int
|
||||
flags clientFlag
|
||||
status statusFlag
|
||||
sequence uint8
|
||||
parseTime bool
|
||||
strict bool
|
||||
}
|
||||
|
||||
type config struct {
|
||||
user string
|
||||
passwd string
|
||||
net string
|
||||
addr string
|
||||
dbname string
|
||||
params map[string]string
|
||||
loc *time.Location
|
||||
tls *tls.Config
|
||||
timeout time.Duration
|
||||
collation uint8
|
||||
allowAllFiles bool
|
||||
allowOldPasswords bool
|
||||
allowCleartextPasswords bool
|
||||
clientFoundRows bool
|
||||
columnsWithAlias bool
|
||||
interpolateParams bool
|
||||
}
|
||||
|
||||
// Handles parameters set in DSN after the connection is established
|
||||
func (mc *mysqlConn) handleParams() (err error) {
|
||||
for param, val := range mc.cfg.params {
|
||||
switch param {
|
||||
// Charset
|
||||
case "charset":
|
||||
charsets := strings.Split(val, ",")
|
||||
for i := range charsets {
|
||||
// ignore errors here - a charset may not exist
|
||||
err = mc.exec("SET NAMES " + charsets[i])
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// time.Time parsing
|
||||
case "parseTime":
|
||||
var isBool bool
|
||||
mc.parseTime, isBool = readBool(val)
|
||||
if !isBool {
|
||||
return errors.New("Invalid Bool value: " + val)
|
||||
}
|
||||
|
||||
// Strict mode
|
||||
case "strict":
|
||||
var isBool bool
|
||||
mc.strict, isBool = readBool(val)
|
||||
if !isBool {
|
||||
return errors.New("Invalid Bool value: " + val)
|
||||
}
|
||||
|
||||
// Compression
|
||||
case "compress":
|
||||
err = errors.New("Compression not implemented yet")
|
||||
return
|
||||
|
||||
// System Vars
|
||||
default:
|
||||
err = mc.exec("SET " + param + "=" + val + "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
err := mc.exec("START TRANSACTION")
|
||||
if err == nil {
|
||||
return &mysqlTx{mc}, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Close() (err error) {
|
||||
// Makes Close idempotent
|
||||
if mc.netConn != nil {
|
||||
err = mc.writeCommandPacket(comQuit)
|
||||
if err == nil {
|
||||
err = mc.netConn.Close()
|
||||
} else {
|
||||
mc.netConn.Close()
|
||||
}
|
||||
mc.netConn = nil
|
||||
}
|
||||
|
||||
mc.cfg = nil
|
||||
mc.buf.rd = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmt := &mysqlStmt{
|
||||
mc: mc,
|
||||
}
|
||||
|
||||
// Read Result
|
||||
columnCount, err := stmt.readPrepareResultPacket()
|
||||
if err == nil {
|
||||
if stmt.paramCount > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if columnCount > 0 {
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
|
||||
buf := mc.buf.takeCompleteBuffer()
|
||||
if buf == nil {
|
||||
// can not take the buffer. Something must be wrong with the connection
|
||||
errLog.Print(ErrBusyBuffer)
|
||||
return "", driver.ErrBadConn
|
||||
}
|
||||
buf = buf[:0]
|
||||
argPos := 0
|
||||
|
||||
for i := 0; i < len(query); i++ {
|
||||
q := strings.IndexByte(query[i:], '?')
|
||||
if q == -1 {
|
||||
buf = append(buf, query[i:]...)
|
||||
break
|
||||
}
|
||||
buf = append(buf, query[i:i+q]...)
|
||||
i += q
|
||||
|
||||
arg := args[argPos]
|
||||
argPos++
|
||||
|
||||
if arg == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := arg.(type) {
|
||||
case int64:
|
||||
buf = strconv.AppendInt(buf, v, 10)
|
||||
case float64:
|
||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
buf = append(buf, '1')
|
||||
} else {
|
||||
buf = append(buf, '0')
|
||||
}
|
||||
case time.Time:
|
||||
if v.IsZero() {
|
||||
buf = append(buf, "'0000-00-00'"...)
|
||||
} else {
|
||||
v := v.In(mc.cfg.loc)
|
||||
v = v.Add(time.Nanosecond * 500) // To round under microsecond
|
||||
year := v.Year()
|
||||
year100 := year / 100
|
||||
year1 := year % 100
|
||||
month := v.Month()
|
||||
day := v.Day()
|
||||
hour := v.Hour()
|
||||
minute := v.Minute()
|
||||
second := v.Second()
|
||||
micro := v.Nanosecond() / 1000
|
||||
|
||||
buf = append(buf, []byte{
|
||||
'\'',
|
||||
digits10[year100], digits01[year100],
|
||||
digits10[year1], digits01[year1],
|
||||
'-',
|
||||
digits10[month], digits01[month],
|
||||
'-',
|
||||
digits10[day], digits01[day],
|
||||
' ',
|
||||
digits10[hour], digits01[hour],
|
||||
':',
|
||||
digits10[minute], digits01[minute],
|
||||
':',
|
||||
digits10[second], digits01[second],
|
||||
}...)
|
||||
|
||||
if micro != 0 {
|
||||
micro10000 := micro / 10000
|
||||
micro100 := micro / 100 % 100
|
||||
micro1 := micro % 100
|
||||
buf = append(buf, []byte{
|
||||
'.',
|
||||
digits10[micro10000], digits01[micro10000],
|
||||
digits10[micro100], digits01[micro100],
|
||||
digits10[micro1], digits01[micro1],
|
||||
}...)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case []byte:
|
||||
if v == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
} else {
|
||||
buf = append(buf, "_binary'"...)
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeBytesBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeBytesQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case string:
|
||||
buf = append(buf, '\'')
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeStringBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeStringQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
default:
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
|
||||
if len(buf)+4 > mc.maxPacketAllowed {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
}
|
||||
if argPos != len(args) {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.interpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
args = nil
|
||||
}
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
err := mc.exec(query)
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Internal function to execute commands
|
||||
func (mc *mysqlConn) exec(query string) error {
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil && resLen > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||
if mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.interpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try client-side prepare to reduce roundtrip
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
args = nil
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err == nil {
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen == 0 {
|
||||
// no columns, no more data
|
||||
return emptyRows{}, nil
|
||||
}
|
||||
// Columns
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
return rows, err
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gets the value of the given MySQL System Variable
|
||||
// The returned byte slice is only valid until the next read
|
||||
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||
// Send command
|
||||
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dest := make([]driver.Value, resLen)
|
||||
if err = rows.readRow(dest); err == nil {
|
||||
return dest[0].([]byte), mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
162
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
162
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const (
|
||||
minProtocolVersion byte = 10
|
||||
maxPacketSize = 1<<24 - 1
|
||||
timeFormat = "2006-01-02 15:04:05.999999"
|
||||
)
|
||||
|
||||
// MySQL constants documentation:
|
||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||
|
||||
const (
|
||||
iOK byte = 0x00
|
||||
iLocalInFile byte = 0xfb
|
||||
iEOF byte = 0xfe
|
||||
iERR byte = 0xff
|
||||
)
|
||||
|
||||
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
||||
type clientFlag uint32
|
||||
|
||||
const (
|
||||
clientLongPassword clientFlag = 1 << iota
|
||||
clientFoundRows
|
||||
clientLongFlag
|
||||
clientConnectWithDB
|
||||
clientNoSchema
|
||||
clientCompress
|
||||
clientODBC
|
||||
clientLocalFiles
|
||||
clientIgnoreSpace
|
||||
clientProtocol41
|
||||
clientInteractive
|
||||
clientSSL
|
||||
clientIgnoreSIGPIPE
|
||||
clientTransactions
|
||||
clientReserved
|
||||
clientSecureConn
|
||||
clientMultiStatements
|
||||
clientMultiResults
|
||||
clientPSMultiResults
|
||||
clientPluginAuth
|
||||
clientConnectAttrs
|
||||
clientPluginAuthLenEncClientData
|
||||
clientCanHandleExpiredPasswords
|
||||
clientSessionTrack
|
||||
clientDeprecateEOF
|
||||
)
|
||||
|
||||
const (
|
||||
comQuit byte = iota + 1
|
||||
comInitDB
|
||||
comQuery
|
||||
comFieldList
|
||||
comCreateDB
|
||||
comDropDB
|
||||
comRefresh
|
||||
comShutdown
|
||||
comStatistics
|
||||
comProcessInfo
|
||||
comConnect
|
||||
comProcessKill
|
||||
comDebug
|
||||
comPing
|
||||
comTime
|
||||
comDelayedInsert
|
||||
comChangeUser
|
||||
comBinlogDump
|
||||
comTableDump
|
||||
comConnectOut
|
||||
comRegisterSlave
|
||||
comStmtPrepare
|
||||
comStmtExecute
|
||||
comStmtSendLongData
|
||||
comStmtClose
|
||||
comStmtReset
|
||||
comSetOption
|
||||
comStmtFetch
|
||||
)
|
||||
|
||||
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
||||
const (
|
||||
fieldTypeDecimal byte = iota
|
||||
fieldTypeTiny
|
||||
fieldTypeShort
|
||||
fieldTypeLong
|
||||
fieldTypeFloat
|
||||
fieldTypeDouble
|
||||
fieldTypeNULL
|
||||
fieldTypeTimestamp
|
||||
fieldTypeLongLong
|
||||
fieldTypeInt24
|
||||
fieldTypeDate
|
||||
fieldTypeTime
|
||||
fieldTypeDateTime
|
||||
fieldTypeYear
|
||||
fieldTypeNewDate
|
||||
fieldTypeVarChar
|
||||
fieldTypeBit
|
||||
)
|
||||
const (
|
||||
fieldTypeNewDecimal byte = iota + 0xf6
|
||||
fieldTypeEnum
|
||||
fieldTypeSet
|
||||
fieldTypeTinyBLOB
|
||||
fieldTypeMediumBLOB
|
||||
fieldTypeLongBLOB
|
||||
fieldTypeBLOB
|
||||
fieldTypeVarString
|
||||
fieldTypeString
|
||||
fieldTypeGeometry
|
||||
)
|
||||
|
||||
type fieldFlag uint16
|
||||
|
||||
const (
|
||||
flagNotNULL fieldFlag = 1 << iota
|
||||
flagPriKey
|
||||
flagUniqueKey
|
||||
flagMultipleKey
|
||||
flagBLOB
|
||||
flagUnsigned
|
||||
flagZeroFill
|
||||
flagBinary
|
||||
flagEnum
|
||||
flagAutoIncrement
|
||||
flagTimestamp
|
||||
flagSet
|
||||
flagUnknown1
|
||||
flagUnknown2
|
||||
flagUnknown3
|
||||
flagUnknown4
|
||||
)
|
||||
|
||||
// http://dev.mysql.com/doc/internals/en/status-flags.html
|
||||
type statusFlag uint16
|
||||
|
||||
const (
|
||||
statusInTrans statusFlag = 1 << iota
|
||||
statusInAutocommit
|
||||
statusReserved // Not in documentation
|
||||
statusMoreResultsExists
|
||||
statusNoGoodIndexUsed
|
||||
statusNoIndexUsed
|
||||
statusCursorExists
|
||||
statusLastRowSent
|
||||
statusDbDropped
|
||||
statusNoBackslashEscapes
|
||||
statusMetadataChanged
|
||||
statusQueryWasSlow
|
||||
statusPsOutParams
|
||||
statusInTransReadonly
|
||||
statusSessionStateChanged
|
||||
)
|
149
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
149
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// The driver should be used via the database/sql package:
|
||||
//
|
||||
// import "database/sql"
|
||||
// import _ "github.com/go-sql-driver/mysql"
|
||||
//
|
||||
// db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
//
|
||||
// See https://github.com/go-sql-driver/mysql#usage for details
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"net"
|
||||
)
|
||||
|
||||
// This struct is exported to make the driver directly accessible.
|
||||
// In general the driver is used via the database/sql package.
|
||||
type MySQLDriver struct{}
|
||||
|
||||
// DialFunc is a function which can be used to establish the network connection.
|
||||
// Custom dial functions must be registered with RegisterDial
|
||||
type DialFunc func(addr string) (net.Conn, error)
|
||||
|
||||
var dials map[string]DialFunc
|
||||
|
||||
// RegisterDial registers a custom dial function. It can then be used by the
|
||||
// network address mynet(addr), where mynet is the registered new network.
|
||||
// addr is passed as a parameter to the dial function.
|
||||
func RegisterDial(net string, dial DialFunc) {
|
||||
if dials == nil {
|
||||
dials = make(map[string]DialFunc)
|
||||
}
|
||||
dials[net] = dial
|
||||
}
|
||||
|
||||
// Open new Connection.
|
||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||
// the DSN string is formated
|
||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||
var err error
|
||||
|
||||
// New mysqlConn
|
||||
mc := &mysqlConn{
|
||||
maxPacketAllowed: maxPacketSize,
|
||||
maxWriteSize: maxPacketSize - 1,
|
||||
}
|
||||
mc.cfg, err = parseDSN(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect to Server
|
||||
if dial, ok := dials[mc.cfg.net]; ok {
|
||||
mc.netConn, err = dial(mc.cfg.addr)
|
||||
} else {
|
||||
nd := net.Dialer{Timeout: mc.cfg.timeout}
|
||||
mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enable TCP Keepalives on TCP connections
|
||||
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||
if err := tc.SetKeepAlive(true); err != nil {
|
||||
// Don't send COM_QUIT before handshake.
|
||||
mc.netConn.Close()
|
||||
mc.netConn = nil
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mc.buf = newBuffer(mc.netConn)
|
||||
|
||||
// Reading Handshake Initialization Packet
|
||||
cipher, err := mc.readInitPacket()
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send Client Authentication Packet
|
||||
if err = mc.writeAuthPacket(cipher); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result Packet
|
||||
err = mc.readResultOK()
|
||||
if err != nil {
|
||||
// Retry with old authentication method, if allowed
|
||||
if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
|
||||
if err = mc.writeOldAuthPacket(cipher); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err = mc.readResultOK(); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else if mc.cfg != nil && mc.cfg.allowCleartextPasswords && err == ErrCleartextPassword {
|
||||
if err = mc.writeClearAuthPacket(); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err = mc.readResultOK(); err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get max allowed packet size
|
||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
mc.maxPacketAllowed = stringToInt(maxap) - 1
|
||||
if mc.maxPacketAllowed < maxPacketSize {
|
||||
mc.maxWriteSize = mc.maxPacketAllowed
|
||||
}
|
||||
|
||||
// Handle DSN Params
|
||||
err = mc.handleParams()
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mc, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
sql.Register("mysql", &MySQLDriver{})
|
||||
}
|
131
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
131
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Various errors the driver might return. Can change between driver versions.
|
||||
var (
|
||||
ErrInvalidConn = errors.New("Invalid Connection")
|
||||
ErrMalformPkt = errors.New("Malformed Packet")
|
||||
ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS")
|
||||
ErrOldPassword = errors.New("This user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
|
||||
ErrCleartextPassword = errors.New("This user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN.")
|
||||
ErrUnknownPlugin = errors.New("The authentication plugin is not supported.")
|
||||
ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+")
|
||||
ErrPktSync = errors.New("Commands out of sync. You can't run this command now")
|
||||
ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?")
|
||||
ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.")
|
||||
ErrBusyBuffer = errors.New("Busy buffer")
|
||||
)
|
||||
|
||||
var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
// Logger is used to log critical error messages.
|
||||
type Logger interface {
|
||||
Print(v ...interface{})
|
||||
}
|
||||
|
||||
// SetLogger is used to set the logger for critical errors.
|
||||
// The initial logger is os.Stderr.
|
||||
func SetLogger(logger Logger) error {
|
||||
if logger == nil {
|
||||
return errors.New("logger is nil")
|
||||
}
|
||||
errLog = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
// MySQLError is an error type which represents a single MySQL error
|
||||
type MySQLError struct {
|
||||
Number uint16
|
||||
Message string
|
||||
}
|
||||
|
||||
func (me *MySQLError) Error() string {
|
||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
||||
}
|
||||
|
||||
// MySQLWarnings is an error type which represents a group of one or more MySQL
|
||||
// warnings
|
||||
type MySQLWarnings []MySQLWarning
|
||||
|
||||
func (mws MySQLWarnings) Error() string {
|
||||
var msg string
|
||||
for i, warning := range mws {
|
||||
if i > 0 {
|
||||
msg += "\r\n"
|
||||
}
|
||||
msg += fmt.Sprintf(
|
||||
"%s %s: %s",
|
||||
warning.Level,
|
||||
warning.Code,
|
||||
warning.Message,
|
||||
)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// MySQLWarning is an error type which represents a single MySQL warning.
|
||||
// Warnings are returned in groups only. See MySQLWarnings
|
||||
type MySQLWarning struct {
|
||||
Level string
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) getWarnings() (err error) {
|
||||
rows, err := mc.Query("SHOW WARNINGS", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var warnings = MySQLWarnings{}
|
||||
var values = make([]driver.Value, 3)
|
||||
|
||||
for {
|
||||
err = rows.Next(values)
|
||||
switch err {
|
||||
case nil:
|
||||
warning := MySQLWarning{}
|
||||
|
||||
if raw, ok := values[0].([]byte); ok {
|
||||
warning.Level = string(raw)
|
||||
} else {
|
||||
warning.Level = fmt.Sprintf("%s", values[0])
|
||||
}
|
||||
if raw, ok := values[1].([]byte); ok {
|
||||
warning.Code = string(raw)
|
||||
} else {
|
||||
warning.Code = fmt.Sprintf("%s", values[1])
|
||||
}
|
||||
if raw, ok := values[2].([]byte); ok {
|
||||
warning.Message = string(raw)
|
||||
} else {
|
||||
warning.Message = fmt.Sprintf("%s", values[0])
|
||||
}
|
||||
|
||||
warnings = append(warnings, warning)
|
||||
|
||||
case io.EOF:
|
||||
return warnings
|
||||
|
||||
default:
|
||||
rows.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
182
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
182
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
fileRegister map[string]bool
|
||||
fileRegisterLock sync.RWMutex
|
||||
readerRegister map[string]func() io.Reader
|
||||
readerRegisterLock sync.RWMutex
|
||||
)
|
||||
|
||||
// RegisterLocalFile adds the given file to the file whitelist,
|
||||
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
|
||||
// Alternatively you can allow the use of all local files with
|
||||
// the DSN parameter 'allowAllFiles=true'
|
||||
//
|
||||
// filePath := "/home/gopher/data.csv"
|
||||
// mysql.RegisterLocalFile(filePath)
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterLocalFile(filePath string) {
|
||||
fileRegisterLock.Lock()
|
||||
// lazy map init
|
||||
if fileRegister == nil {
|
||||
fileRegister = make(map[string]bool)
|
||||
}
|
||||
|
||||
fileRegister[strings.Trim(filePath, `"`)] = true
|
||||
fileRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterLocalFile removes the given filepath from the whitelist.
|
||||
func DeregisterLocalFile(filePath string) {
|
||||
fileRegisterLock.Lock()
|
||||
delete(fileRegister, strings.Trim(filePath, `"`))
|
||||
fileRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// RegisterReaderHandler registers a handler function which is used
|
||||
// to receive a io.Reader.
|
||||
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
|
||||
// If the handler returns a io.ReadCloser Close() is called when the
|
||||
// request is finished.
|
||||
//
|
||||
// mysql.RegisterReaderHandler("data", func() io.Reader {
|
||||
// var csvReader io.Reader // Some Reader that returns CSV data
|
||||
// ... // Open Reader here
|
||||
// return csvReader
|
||||
// })
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterReaderHandler(name string, handler func() io.Reader) {
|
||||
readerRegisterLock.Lock()
|
||||
// lazy map init
|
||||
if readerRegister == nil {
|
||||
readerRegister = make(map[string]func() io.Reader)
|
||||
}
|
||||
|
||||
readerRegister[name] = handler
|
||||
readerRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterReaderHandler removes the ReaderHandler function with
|
||||
// the given name from the registry.
|
||||
func DeregisterReaderHandler(name string) {
|
||||
readerRegisterLock.Lock()
|
||||
delete(readerRegister, name)
|
||||
readerRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
func deferredClose(err *error, closer io.Closer) {
|
||||
closeErr := closer.Close()
|
||||
if *err == nil {
|
||||
*err = closeErr
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||
var rdr io.Reader
|
||||
var data []byte
|
||||
|
||||
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
|
||||
// The server might return an an absolute path. See issue #355.
|
||||
name = name[idx+8:]
|
||||
|
||||
readerRegisterLock.RLock()
|
||||
handler, inMap := readerRegister[name]
|
||||
readerRegisterLock.RUnlock()
|
||||
|
||||
if inMap {
|
||||
rdr = handler()
|
||||
if rdr != nil {
|
||||
data = make([]byte, 4+mc.maxWriteSize)
|
||||
|
||||
if cl, ok := rdr.(io.Closer); ok {
|
||||
defer deferredClose(&err, cl)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is <nil>", name)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is not registered", name)
|
||||
}
|
||||
} else { // File
|
||||
name = strings.Trim(name, `"`)
|
||||
fileRegisterLock.RLock()
|
||||
fr := fileRegister[name]
|
||||
fileRegisterLock.RUnlock()
|
||||
if mc.cfg.allowAllFiles || fr {
|
||||
var file *os.File
|
||||
var fi os.FileInfo
|
||||
|
||||
if file, err = os.Open(name); err == nil {
|
||||
defer deferredClose(&err, file)
|
||||
|
||||
// get file size
|
||||
if fi, err = file.Stat(); err == nil {
|
||||
rdr = file
|
||||
if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize {
|
||||
data = make([]byte, 4+fileSize)
|
||||
} else if fileSize <= mc.maxPacketAllowed {
|
||||
data = make([]byte, 4+mc.maxWriteSize)
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name)
|
||||
}
|
||||
}
|
||||
|
||||
// send content packets
|
||||
if err == nil {
|
||||
var n int
|
||||
for err == nil {
|
||||
n, err = rdr.Read(data[4:])
|
||||
if n > 0 {
|
||||
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// send empty packet (termination)
|
||||
if data == nil {
|
||||
data = make([]byte, 4)
|
||||
}
|
||||
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
|
||||
// read OK packet
|
||||
if err == nil {
|
||||
return mc.readResultOK()
|
||||
} else {
|
||||
mc.readPacket()
|
||||
}
|
||||
return err
|
||||
}
|
1182
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
1182
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
22
vendor/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlResult struct {
|
||||
affectedRows int64
|
||||
insertId int64
|
||||
}
|
||||
|
||||
func (res *mysqlResult) LastInsertId() (int64, error) {
|
||||
return res.insertId, nil
|
||||
}
|
||||
|
||||
func (res *mysqlResult) RowsAffected() (int64, error) {
|
||||
return res.affectedRows, nil
|
||||
}
|
106
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
106
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
)
|
||||
|
||||
type mysqlField struct {
|
||||
tableName string
|
||||
name string
|
||||
flags fieldFlag
|
||||
fieldType byte
|
||||
decimals byte
|
||||
}
|
||||
|
||||
type mysqlRows struct {
|
||||
mc *mysqlConn
|
||||
columns []mysqlField
|
||||
}
|
||||
|
||||
type binaryRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type textRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type emptyRows struct{}
|
||||
|
||||
func (rows *mysqlRows) Columns() []string {
|
||||
columns := make([]string, len(rows.columns))
|
||||
if rows.mc.cfg.columnsWithAlias {
|
||||
for i := range columns {
|
||||
if tableName := rows.columns[i].tableName; len(tableName) > 0 {
|
||||
columns[i] = tableName + "." + rows.columns[i].name
|
||||
} else {
|
||||
columns[i] = rows.columns[i].name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range columns {
|
||||
columns[i] = rows.columns[i].name
|
||||
}
|
||||
}
|
||||
return columns
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Close() error {
|
||||
mc := rows.mc
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Remove unread packets from stream
|
||||
err := mc.readUntilEOF()
|
||||
rows.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows *textRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows emptyRows) Columns() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rows emptyRows) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rows emptyRows) Next(dest []driver.Value) error {
|
||||
return io.EOF
|
||||
}
|
150
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
150
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type mysqlStmt struct {
|
||||
mc *mysqlConn
|
||||
id uint32
|
||||
paramCount int
|
||||
columns []mysqlField // cached from the first query
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Close() error {
|
||||
if stmt.mc == nil || stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
|
||||
stmt.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) NumInput() int {
|
||||
return stmt.paramCount
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
|
||||
return converter{}
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
if stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
err = mc.readUntilEOF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rows
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
if stmt.mc.netConn == nil {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := new(binaryRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
// If not cached, read them and cache them
|
||||
if stmt.columns == nil {
|
||||
rows.columns, err = mc.readColumns(resLen)
|
||||
stmt.columns = rows.columns
|
||||
} else {
|
||||
rows.columns = stmt.columns
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
type converter struct{}
|
||||
|
||||
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||
if driver.IsValue(v) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Ptr:
|
||||
// indirect pointers
|
||||
if rv.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
return c.ConvertValue(rv.Elem().Interface())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int(), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return int64(rv.Uint()), nil
|
||||
case reflect.Uint64:
|
||||
u64 := rv.Uint()
|
||||
if u64 >= 1<<63 {
|
||||
return strconv.FormatUint(u64, 10), nil
|
||||
}
|
||||
return int64(u64), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
|
||||
}
|
31
vendor/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
31
vendor/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlTx struct {
|
||||
mc *mysqlConn
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Commit() (err error) {
|
||||
if tx.mc == nil || tx.mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("COMMIT")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Rollback() (err error) {
|
||||
if tx.mc == nil || tx.mc.netConn == nil {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("ROLLBACK")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
973
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
973
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
@ -0,0 +1,973 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
||||
|
||||
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
|
||||
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
|
||||
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
|
||||
errInvalidDSNUnsafeCollation = errors.New("Invalid DSN: interpolateParams can be used with ascii, latin1, utf8 and utf8mb4 charset")
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsConfigRegister = make(map[string]*tls.Config)
|
||||
}
|
||||
|
||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||
// Use the key as a value in the DSN where tls=value.
|
||||
//
|
||||
// rootCertPool := x509.NewCertPool()
|
||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||
// log.Fatal("Failed to append PEM.")
|
||||
// }
|
||||
// clientCert := make([]tls.Certificate, 0, 1)
|
||||
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// clientCert = append(clientCert, certs)
|
||||
// mysql.RegisterTLSConfig("custom", &tls.Config{
|
||||
// RootCAs: rootCertPool,
|
||||
// Certificates: clientCert,
|
||||
// })
|
||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||
//
|
||||
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
||||
return fmt.Errorf("Key '%s' is reserved", key)
|
||||
}
|
||||
|
||||
tlsConfigRegister[key] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||
func DeregisterTLSConfig(key string) {
|
||||
delete(tlsConfigRegister, key)
|
||||
}
|
||||
|
||||
// parseDSN parses the DSN string to a config
|
||||
func parseDSN(dsn string) (cfg *config, err error) {
|
||||
// New config with some default values
|
||||
cfg = &config{
|
||||
loc: time.UTC,
|
||||
collation: defaultCollation,
|
||||
}
|
||||
|
||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||
foundSlash := false
|
||||
for i := len(dsn) - 1; i >= 0; i-- {
|
||||
if dsn[i] == '/' {
|
||||
foundSlash = true
|
||||
var j, k int
|
||||
|
||||
// left part is empty if i <= 0
|
||||
if i > 0 {
|
||||
// [username[:password]@][protocol[(address)]]
|
||||
// Find the last '@' in dsn[:i]
|
||||
for j = i; j >= 0; j-- {
|
||||
if dsn[j] == '@' {
|
||||
// username[:password]
|
||||
// Find the first ':' in dsn[:j]
|
||||
for k = 0; k < j; k++ {
|
||||
if dsn[k] == ':' {
|
||||
cfg.passwd = dsn[k+1 : j]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.user = dsn[:k]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [protocol[(address)]]
|
||||
// Find the first '(' in dsn[j+1:i]
|
||||
for k = j + 1; k < i; k++ {
|
||||
if dsn[k] == '(' {
|
||||
// dsn[i-1] must be == ')' if an address is specified
|
||||
if dsn[i-1] != ')' {
|
||||
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||
return nil, errInvalidDSNUnescaped
|
||||
}
|
||||
return nil, errInvalidDSNAddr
|
||||
}
|
||||
cfg.addr = dsn[k+1 : i-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.net = dsn[j+1 : k]
|
||||
}
|
||||
|
||||
// dbname[?param1=value1&...¶mN=valueN]
|
||||
// Find the first '?' in dsn[i+1:]
|
||||
for j = i + 1; j < len(dsn); j++ {
|
||||
if dsn[j] == '?' {
|
||||
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.dbname = dsn[i+1 : j]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundSlash && len(dsn) > 0 {
|
||||
return nil, errInvalidDSNNoSlash
|
||||
}
|
||||
|
||||
if cfg.interpolateParams && unsafeCollations[cfg.collation] {
|
||||
return nil, errInvalidDSNUnsafeCollation
|
||||
}
|
||||
|
||||
// Set default network if empty
|
||||
if cfg.net == "" {
|
||||
cfg.net = "tcp"
|
||||
}
|
||||
|
||||
// Set default address if empty
|
||||
if cfg.addr == "" {
|
||||
switch cfg.net {
|
||||
case "tcp":
|
||||
cfg.addr = "127.0.0.1:3306"
|
||||
case "unix":
|
||||
cfg.addr = "/tmp/mysql.sock"
|
||||
default:
|
||||
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseDSNParams parses the DSN "query string"
|
||||
// Values must be url.QueryEscape'ed
|
||||
func parseDSNParams(cfg *config, params string) (err error) {
|
||||
for _, v := range strings.Split(params, "&") {
|
||||
param := strings.SplitN(v, "=", 2)
|
||||
if len(param) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// cfg params
|
||||
switch value := param[1]; param[0] {
|
||||
|
||||
// Enable client side placeholder substitution
|
||||
case "interpolateParams":
|
||||
var isBool bool
|
||||
cfg.interpolateParams, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Disable INFILE whitelist / enable all files
|
||||
case "allowAllFiles":
|
||||
var isBool bool
|
||||
cfg.allowAllFiles, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Use cleartext authentication mode (MySQL 5.5.10+)
|
||||
case "allowCleartextPasswords":
|
||||
var isBool bool
|
||||
cfg.allowCleartextPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Use old authentication mode (pre MySQL 4.1)
|
||||
case "allowOldPasswords":
|
||||
var isBool bool
|
||||
cfg.allowOldPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Switch "rowsAffected" mode
|
||||
case "clientFoundRows":
|
||||
var isBool bool
|
||||
cfg.clientFoundRows, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Collation
|
||||
case "collation":
|
||||
collation, ok := collations[value]
|
||||
if !ok {
|
||||
// Note possibility for false negatives:
|
||||
// could be triggered although the collation is valid if the
|
||||
// collations map does not contain entries the server supports.
|
||||
err = errors.New("unknown collation")
|
||||
return
|
||||
}
|
||||
cfg.collation = collation
|
||||
break
|
||||
|
||||
case "columnsWithAlias":
|
||||
var isBool bool
|
||||
cfg.columnsWithAlias, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return fmt.Errorf("Invalid Bool value: %s", value)
|
||||
}
|
||||
|
||||
// Time Location
|
||||
case "loc":
|
||||
if value, err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
cfg.loc, err = time.LoadLocation(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Dial Timeout
|
||||
case "timeout":
|
||||
cfg.timeout, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TLS-Encryption
|
||||
case "tls":
|
||||
boolValue, isBool := readBool(value)
|
||||
if isBool {
|
||||
if boolValue {
|
||||
cfg.tls = &tls.Config{}
|
||||
}
|
||||
} else {
|
||||
if strings.ToLower(value) == "skip-verify" {
|
||||
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
|
||||
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
||||
host, _, err := net.SplitHostPort(cfg.addr)
|
||||
if err == nil {
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
}
|
||||
|
||||
cfg.tls = tlsConfig
|
||||
} else {
|
||||
return fmt.Errorf("Invalid value / unknown config name: %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// lazy init
|
||||
if cfg.params == nil {
|
||||
cfg.params = make(map[string]string)
|
||||
}
|
||||
|
||||
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the bool value of the input.
|
||||
// The 2nd return value indicates if the input was a valid bool value
|
||||
func readBool(input string) (value bool, valid bool) {
|
||||
switch input {
|
||||
case "1", "true", "TRUE", "True":
|
||||
return true, true
|
||||
case "0", "false", "FALSE", "False":
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Not a valid bool value
|
||||
return
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Authentication *
|
||||
******************************************************************************/
|
||||
|
||||
// Encrypt password using 4.1+ method
|
||||
func scramblePassword(scramble, password []byte) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stage1Hash = SHA1(password)
|
||||
crypt := sha1.New()
|
||||
crypt.Write(password)
|
||||
stage1 := crypt.Sum(nil)
|
||||
|
||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||
// inner Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(stage1)
|
||||
hash := crypt.Sum(nil)
|
||||
|
||||
// outer Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(scramble)
|
||||
crypt.Write(hash)
|
||||
scramble = crypt.Sum(nil)
|
||||
|
||||
// token = scrambleHash XOR stage1Hash
|
||||
for i := range scramble {
|
||||
scramble[i] ^= stage1[i]
|
||||
}
|
||||
return scramble
|
||||
}
|
||||
|
||||
// Encrypt password using pre 4.1 (old password) method
|
||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||
type myRnd struct {
|
||||
seed1, seed2 uint32
|
||||
}
|
||||
|
||||
const myRndMaxVal = 0x3FFFFFFF
|
||||
|
||||
// Pseudo random number generator
|
||||
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||
return &myRnd{
|
||||
seed1: seed1 % myRndMaxVal,
|
||||
seed2: seed2 % myRndMaxVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Tested to be equivalent to MariaDB's floating point variant
|
||||
// http://play.golang.org/p/QHvhd4qved
|
||||
// http://play.golang.org/p/RG0q4ElWDx
|
||||
func (r *myRnd) NextByte() byte {
|
||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||
|
||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||
}
|
||||
|
||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||
func pwHash(password []byte) (result [2]uint32) {
|
||||
var add uint32 = 7
|
||||
var tmp uint32
|
||||
|
||||
result[0] = 1345345333
|
||||
result[1] = 0x12345671
|
||||
|
||||
for _, c := range password {
|
||||
// skip spaces and tabs in password
|
||||
if c == ' ' || c == '\t' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmp = uint32(c)
|
||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||
result[1] += (result[1] << 8) ^ result[0]
|
||||
add += tmp
|
||||
}
|
||||
|
||||
// Remove sign bit (1<<31)-1)
|
||||
result[0] &= 0x7FFFFFFF
|
||||
result[1] &= 0x7FFFFFFF
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt password using insecure pre 4.1 method
|
||||
func scrambleOldPassword(scramble, password []byte) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scramble = scramble[:8]
|
||||
|
||||
hashPw := pwHash(password)
|
||||
hashSc := pwHash(scramble)
|
||||
|
||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||
|
||||
var out [8]byte
|
||||
for i := range out {
|
||||
out[i] = r.NextByte() + 64
|
||||
}
|
||||
|
||||
mask := r.NextByte()
|
||||
for i := range out {
|
||||
out[i] ^= mask
|
||||
}
|
||||
|
||||
return out[:]
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Time related utils *
|
||||
******************************************************************************/
|
||||
|
||||
// NullTime represents a time.Time that may be NULL.
|
||||
// NullTime implements the Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var nt NullTime
|
||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||
// ...
|
||||
// if nt.Valid {
|
||||
// // use nt.Time
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
// This NullTime implementation is not driver-specific
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||
// otherwise Scan fails.
|
||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||
if value == nil {
|
||||
nt.Time, nt.Valid = time.Time{}, false
|
||||
return
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
nt.Time, nt.Valid = v, true
|
||||
return
|
||||
case []byte:
|
||||
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
case string:
|
||||
nt.Time, err = parseDateTime(v, time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
}
|
||||
|
||||
nt.Valid = false
|
||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
||||
|
||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||
base := "0000-00-00 00:00:00.0000000"
|
||||
switch len(str) {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
|
||||
if str == base[:len(str)] {
|
||||
return
|
||||
}
|
||||
t, err = time.Parse(timeFormat[:len(str)], str)
|
||||
default:
|
||||
err = fmt.Errorf("Invalid Time-String: %s", str)
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust location
|
||||
if err == nil && loc != time.UTC {
|
||||
y, mo, d := t.Date()
|
||||
h, mi, s := t.Clock()
|
||||
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
|
||||
switch num {
|
||||
case 0:
|
||||
return time.Time{}, nil
|
||||
case 4:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
0, 0, 0, 0,
|
||||
loc,
|
||||
), nil
|
||||
case 7:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
0,
|
||||
loc,
|
||||
), nil
|
||||
case 11:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
|
||||
loc,
|
||||
), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
|
||||
}
|
||||
|
||||
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
|
||||
// if the DATE or DATETIME has the zero value.
|
||||
// It must never be changed.
|
||||
// The current behavior depends on database/sql copying the result.
|
||||
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||
|
||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||
|
||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
||||
// length expects the deterministic length of the zero value,
|
||||
// negative time and 100+ hours are automatically added if needed
|
||||
if len(src) == 0 {
|
||||
if justTime {
|
||||
return zeroDateTime[11 : 11+length], nil
|
||||
}
|
||||
return zeroDateTime[:length], nil
|
||||
}
|
||||
var dst []byte // return value
|
||||
var pt, p1, p2, p3 byte // current digit pair
|
||||
var zOffs byte // offset of value in zeroDateTime
|
||||
if justTime {
|
||||
switch length {
|
||||
case
|
||||
8, // time (can be up to 10 when negative and 100+ hours)
|
||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 8, 12:
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid TIME-packet length %d", len(src))
|
||||
}
|
||||
// +2 to enable negative time and 100+ hours
|
||||
dst = make([]byte, 0, length+2)
|
||||
if src[0] == 1 {
|
||||
dst = append(dst, '-')
|
||||
}
|
||||
if src[1] != 0 {
|
||||
hour := uint16(src[1])*24 + uint16(src[5])
|
||||
pt = byte(hour / 100)
|
||||
p1 = byte(hour - 100*uint16(pt))
|
||||
dst = append(dst, digits01[pt])
|
||||
} else {
|
||||
p1 = src[5]
|
||||
}
|
||||
zOffs = 11
|
||||
src = src[6:]
|
||||
} else {
|
||||
switch length {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 4, 7, 11:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s-packet length %d", t, len(src))
|
||||
}
|
||||
dst = make([]byte, 0, length)
|
||||
// start with the date
|
||||
year := binary.LittleEndian.Uint16(src[:2])
|
||||
pt = byte(year / 100)
|
||||
p1 = byte(year - 100*uint16(pt))
|
||||
p2, p3 = src[2], src[3]
|
||||
dst = append(dst,
|
||||
digits10[pt], digits01[pt],
|
||||
digits10[p1], digits01[p1], '-',
|
||||
digits10[p2], digits01[p2], '-',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length == 10 {
|
||||
return dst, nil
|
||||
}
|
||||
if len(src) == 4 {
|
||||
return append(dst, zeroDateTime[10:length]...), nil
|
||||
}
|
||||
dst = append(dst, ' ')
|
||||
p1 = src[4] // hour
|
||||
src = src[5:]
|
||||
}
|
||||
// p1 is 2-digit hour, src is after hour
|
||||
p2, p3 = src[0], src[1]
|
||||
dst = append(dst,
|
||||
digits10[p1], digits01[p1], ':',
|
||||
digits10[p2], digits01[p2], ':',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length <= byte(len(dst)) {
|
||||
return dst, nil
|
||||
}
|
||||
src = src[2:]
|
||||
if len(src) == 0 {
|
||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
||||
}
|
||||
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||
p1 = byte(microsecs / 10000)
|
||||
microsecs -= 10000 * uint32(p1)
|
||||
p2 = byte(microsecs / 100)
|
||||
microsecs -= 100 * uint32(p2)
|
||||
p3 = byte(microsecs)
|
||||
switch decimals := zOffs + length - 20; decimals {
|
||||
default:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3], digits01[p3],
|
||||
), nil
|
||||
case 1:
|
||||
return append(dst, '.',
|
||||
digits10[p1],
|
||||
), nil
|
||||
case 2:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
), nil
|
||||
case 3:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2],
|
||||
), nil
|
||||
case 4:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
), nil
|
||||
case 5:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3],
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Convert from and to bytes *
|
||||
******************************************************************************/
|
||||
|
||||
func uint64ToBytes(n uint64) []byte {
|
||||
return []byte{
|
||||
byte(n),
|
||||
byte(n >> 8),
|
||||
byte(n >> 16),
|
||||
byte(n >> 24),
|
||||
byte(n >> 32),
|
||||
byte(n >> 40),
|
||||
byte(n >> 48),
|
||||
byte(n >> 56),
|
||||
}
|
||||
}
|
||||
|
||||
func uint64ToString(n uint64) []byte {
|
||||
var a [20]byte
|
||||
i := 20
|
||||
|
||||
// U+0030 = 0
|
||||
// ...
|
||||
// U+0039 = 9
|
||||
|
||||
var q uint64
|
||||
for n >= 10 {
|
||||
i--
|
||||
q = n / 10
|
||||
a[i] = uint8(n-q*10) + 0x30
|
||||
n = q
|
||||
}
|
||||
|
||||
i--
|
||||
a[i] = uint8(n) + 0x30
|
||||
|
||||
return a[i:]
|
||||
}
|
||||
|
||||
// treats string value as unsigned integer representation
|
||||
func stringToInt(b []byte) int {
|
||||
val := 0
|
||||
for i := range b {
|
||||
val *= 10
|
||||
val += int(b[i] - 0x30)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// returns the string read as a bytes slice, wheter the value is NULL,
|
||||
// the number of bytes read and an error, in case the string is longer than
|
||||
// the input slice
|
||||
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||
// Get length
|
||||
num, isNull, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return b[n:n], isNull, n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return b[n-int(num) : n], false, n, nil
|
||||
}
|
||||
return nil, false, n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number of bytes skipped and an error, in case the string is
|
||||
// longer than the input slice
|
||||
func skipLengthEncodedString(b []byte) (int, error) {
|
||||
// Get length
|
||||
num, _, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return n, nil
|
||||
}
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number read, whether the value is NULL and the number of bytes read
|
||||
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||
// See issue #349
|
||||
if len(b) == 0 {
|
||||
return 0, true, 1
|
||||
}
|
||||
switch b[0] {
|
||||
|
||||
// 251: NULL
|
||||
case 0xfb:
|
||||
return 0, true, 1
|
||||
|
||||
// 252: value of following 2
|
||||
case 0xfc:
|
||||
return uint64(b[1]) | uint64(b[2])<<8, false, 3
|
||||
|
||||
// 253: value of following 3
|
||||
case 0xfd:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
|
||||
|
||||
// 254: value of following 8
|
||||
case 0xfe:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
|
||||
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
|
||||
uint64(b[7])<<48 | uint64(b[8])<<56,
|
||||
false, 9
|
||||
}
|
||||
|
||||
// 0-250: value of first byte
|
||||
return uint64(b[0]), false, 1
|
||||
}
|
||||
|
||||
// encodes a uint64 value and appends it to the given bytes slice
|
||||
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
|
||||
switch {
|
||||
case n <= 250:
|
||||
return append(b, byte(n))
|
||||
|
||||
case n <= 0xffff:
|
||||
return append(b, 0xfc, byte(n), byte(n>>8))
|
||||
|
||||
case n <= 0xffffff:
|
||||
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
|
||||
}
|
||||
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
|
||||
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
|
||||
}
|
||||
|
||||
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
|
||||
// If cap(buf) is not enough, reallocate new buffer.
|
||||
func reserveBuffer(buf []byte, appendSize int) []byte {
|
||||
newSize := len(buf) + appendSize
|
||||
if cap(buf) < newSize {
|
||||
// Grow buffer exponentially
|
||||
newBuf := make([]byte, len(buf)*2+appendSize)
|
||||
copy(newBuf, buf)
|
||||
buf = newBuf
|
||||
}
|
||||
return buf[:newSize]
|
||||
}
|
||||
|
||||
// escapeBytesBackslash escapes []byte with backslashes (\)
|
||||
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
|
||||
// characters, and turning others into specific escape sequences, such as
|
||||
// turning newlines into \n and null bytes into \0.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
|
||||
func escapeBytesBackslash(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
|
||||
func escapeStringBackslash(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos += 1
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
|
||||
// This escapes the contents of a string by doubling up any apostrophes that
|
||||
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
|
||||
// effect on the server.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
|
||||
func escapeBytesQuotes(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
|
||||
func escapeStringQuotes(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
258
vendor/github.com/outbrain/golib/log/log.go
generated
vendored
Normal file
258
vendor/github.com/outbrain/golib/log/log.go
generated
vendored
Normal file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
Copyright 2014 Outbrain Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel indicates the severity of a log entry
|
||||
type LogLevel int
|
||||
|
||||
func (this LogLevel) String() string {
|
||||
switch this {
|
||||
case FATAL:
|
||||
return "FATAL"
|
||||
case CRITICAL:
|
||||
return "CRITICAL"
|
||||
case ERROR:
|
||||
return "ERROR"
|
||||
case WARNING:
|
||||
return "WARNING"
|
||||
case NOTICE:
|
||||
return "NOTICE"
|
||||
case INFO:
|
||||
return "INFO"
|
||||
case DEBUG:
|
||||
return "DEBUG"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func LogLevelFromString(logLevelName string) (LogLevel, error) {
|
||||
switch logLevelName {
|
||||
case "FATAL":
|
||||
return FATAL, nil
|
||||
case "CRITICAL":
|
||||
return CRITICAL, nil
|
||||
case "ERROR":
|
||||
return ERROR, nil
|
||||
case "WARNING":
|
||||
return WARNING, nil
|
||||
case "NOTICE":
|
||||
return NOTICE, nil
|
||||
case "INFO":
|
||||
return INFO, nil
|
||||
case "DEBUG":
|
||||
return DEBUG, nil
|
||||
}
|
||||
return 0, fmt.Errorf("Unknown LogLevel name: %+v", logLevelName)
|
||||
}
|
||||
|
||||
const (
|
||||
FATAL LogLevel = iota
|
||||
CRITICAL
|
||||
ERROR
|
||||
WARNING
|
||||
NOTICE
|
||||
INFO
|
||||
DEBUG
|
||||
)
|
||||
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
// globalLogLevel indicates the global level filter for all logs (only entries with level equals or higher
|
||||
// than this value will be logged)
|
||||
var globalLogLevel LogLevel = DEBUG
|
||||
var printStackTrace bool = false
|
||||
|
||||
// syslogWriter is optional, and defaults to nil (disabled)
|
||||
var syslogLevel LogLevel = ERROR
|
||||
var syslogWriter *syslog.Writer
|
||||
|
||||
// SetPrintStackTrace enables/disables dumping the stack upon error logging
|
||||
func SetPrintStackTrace(shouldPrintStackTrace bool) {
|
||||
printStackTrace = shouldPrintStackTrace
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level. Only entries with level equals or higher than
|
||||
// this value will be logged
|
||||
func SetLevel(logLevel LogLevel) {
|
||||
globalLogLevel = logLevel
|
||||
}
|
||||
|
||||
// GetLevel returns current global log level
|
||||
func GetLevel() LogLevel {
|
||||
return globalLogLevel
|
||||
}
|
||||
|
||||
// EnableSyslogWriter enables, if possible, writes to syslog. These will execute _in addition_ to normal logging
|
||||
func EnableSyslogWriter(tag string) (err error) {
|
||||
syslogWriter, err = syslog.New(syslog.LOG_ERR, tag)
|
||||
if err != nil {
|
||||
syslogWriter = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetSyslogLevel sets the minimal syslog level. Only entries with level equals or higher than
|
||||
// this value will be logged. However, this is also capped by the global log level. That is,
|
||||
// messages with lower level than global-log-level will be discarded at any case.
|
||||
func SetSyslogLevel(logLevel LogLevel) {
|
||||
syslogLevel = logLevel
|
||||
}
|
||||
|
||||
// logFormattedEntry nicely formats and emits a log entry
|
||||
func logFormattedEntry(logLevel LogLevel, message string, args ...interface{}) string {
|
||||
if logLevel > globalLogLevel {
|
||||
return ""
|
||||
}
|
||||
msgArgs := fmt.Sprintf(message, args...)
|
||||
entryString := fmt.Sprintf("%s %s %s", time.Now().Format(TimeFormat), logLevel, msgArgs)
|
||||
fmt.Fprintln(os.Stderr, entryString)
|
||||
|
||||
if syslogWriter != nil {
|
||||
go func() error {
|
||||
if logLevel > syslogLevel {
|
||||
return nil
|
||||
}
|
||||
switch logLevel {
|
||||
case FATAL:
|
||||
return syslogWriter.Emerg(msgArgs)
|
||||
case CRITICAL:
|
||||
return syslogWriter.Crit(msgArgs)
|
||||
case ERROR:
|
||||
return syslogWriter.Err(msgArgs)
|
||||
case WARNING:
|
||||
return syslogWriter.Warning(msgArgs)
|
||||
case NOTICE:
|
||||
return syslogWriter.Notice(msgArgs)
|
||||
case INFO:
|
||||
return syslogWriter.Info(msgArgs)
|
||||
case DEBUG:
|
||||
return syslogWriter.Debug(msgArgs)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
return entryString
|
||||
}
|
||||
|
||||
// logEntry emits a formatted log entry
|
||||
func logEntry(logLevel LogLevel, message string, args ...interface{}) string {
|
||||
entryString := message
|
||||
for _, s := range args {
|
||||
entryString += fmt.Sprintf(" %s", s)
|
||||
}
|
||||
return logFormattedEntry(logLevel, entryString)
|
||||
}
|
||||
|
||||
// logErrorEntry emits a log entry based on given error object
|
||||
func logErrorEntry(logLevel LogLevel, err error) error {
|
||||
if err == nil {
|
||||
// No error
|
||||
return nil
|
||||
}
|
||||
entryString := fmt.Sprintf("%+v", err)
|
||||
logEntry(logLevel, entryString)
|
||||
if printStackTrace {
|
||||
debug.PrintStack()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Debug(message string, args ...interface{}) string {
|
||||
return logEntry(DEBUG, message, args...)
|
||||
}
|
||||
|
||||
func Debugf(message string, args ...interface{}) string {
|
||||
return logFormattedEntry(DEBUG, message, args...)
|
||||
}
|
||||
|
||||
func Info(message string, args ...interface{}) string {
|
||||
return logEntry(INFO, message, args...)
|
||||
}
|
||||
|
||||
func Infof(message string, args ...interface{}) string {
|
||||
return logFormattedEntry(INFO, message, args...)
|
||||
}
|
||||
|
||||
func Notice(message string, args ...interface{}) string {
|
||||
return logEntry(NOTICE, message, args...)
|
||||
}
|
||||
|
||||
func Noticef(message string, args ...interface{}) string {
|
||||
return logFormattedEntry(NOTICE, message, args...)
|
||||
}
|
||||
|
||||
func Warning(message string, args ...interface{}) error {
|
||||
return errors.New(logEntry(WARNING, message, args...))
|
||||
}
|
||||
|
||||
func Warningf(message string, args ...interface{}) error {
|
||||
return errors.New(logFormattedEntry(WARNING, message, args...))
|
||||
}
|
||||
|
||||
func Error(message string, args ...interface{}) error {
|
||||
return errors.New(logEntry(ERROR, message, args...))
|
||||
}
|
||||
|
||||
func Errorf(message string, args ...interface{}) error {
|
||||
return errors.New(logFormattedEntry(ERROR, message, args...))
|
||||
}
|
||||
|
||||
func Errore(err error) error {
|
||||
return logErrorEntry(ERROR, err)
|
||||
}
|
||||
|
||||
func Critical(message string, args ...interface{}) error {
|
||||
return errors.New(logEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
func Criticalf(message string, args ...interface{}) error {
|
||||
return errors.New(logFormattedEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
func Criticale(err error) error {
|
||||
return logErrorEntry(CRITICAL, err)
|
||||
}
|
||||
|
||||
// Fatal emits a FATAL level entry and exists the program
|
||||
func Fatal(message string, args ...interface{}) error {
|
||||
logEntry(FATAL, message, args...)
|
||||
os.Exit(1)
|
||||
return errors.New(logEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
// Fatalf emits a FATAL level entry and exists the program
|
||||
func Fatalf(message string, args ...interface{}) error {
|
||||
logFormattedEntry(FATAL, message, args...)
|
||||
os.Exit(1)
|
||||
return errors.New(logFormattedEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
// Fatale emits a FATAL level entry and exists the program
|
||||
func Fatale(err error) error {
|
||||
logErrorEntry(FATAL, err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
333
vendor/github.com/outbrain/golib/sqlutils/sqlutils.go
generated
vendored
Normal file
333
vendor/github.com/outbrain/golib/sqlutils/sqlutils.go
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
Copyright 2014 Outbrain Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sqlutils
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/outbrain/golib/log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RowMap represents one row in a result set. Its objective is to allow
|
||||
// for easy, typed getters by column name.
|
||||
type RowMap map[string]CellData
|
||||
|
||||
// Cell data is the result of a single (atomic) column in a single row
|
||||
type CellData sql.NullString
|
||||
|
||||
func (this *CellData) MarshalJSON() ([]byte, error) {
|
||||
if this.Valid {
|
||||
return json.Marshal(this.String)
|
||||
} else {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CellData) NullString() *sql.NullString {
|
||||
return (*sql.NullString)(this)
|
||||
}
|
||||
|
||||
// RowData is the result of a single row, in positioned array format
|
||||
type RowData []CellData
|
||||
|
||||
// MarshalJSON will marshal this map as JSON
|
||||
func (this *RowData) MarshalJSON() ([]byte, error) {
|
||||
cells := make([](*CellData), len(*this), len(*this))
|
||||
for i, val := range *this {
|
||||
d := CellData(val)
|
||||
cells[i] = &d
|
||||
}
|
||||
return json.Marshal(cells)
|
||||
}
|
||||
|
||||
// ResultData is an ordered row set of RowData
|
||||
type ResultData []RowData
|
||||
|
||||
var EmptyResultData = ResultData{}
|
||||
|
||||
func (this *RowMap) GetString(key string) string {
|
||||
return (*this)[key].String
|
||||
}
|
||||
|
||||
// GetStringD returns a string from the map, or a default value if the key does not exist
|
||||
func (this *RowMap) GetStringD(key string, def string) string {
|
||||
if cell, ok := (*this)[key]; ok {
|
||||
return cell.String
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (this *RowMap) GetInt64(key string) int64 {
|
||||
res, _ := strconv.ParseInt(this.GetString(key), 10, 0)
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetNullInt64(key string) sql.NullInt64 {
|
||||
i, err := strconv.ParseInt(this.GetString(key), 10, 0)
|
||||
if err == nil {
|
||||
return sql.NullInt64{Int64: i, Valid: true}
|
||||
} else {
|
||||
return sql.NullInt64{Valid: false}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *RowMap) GetInt(key string) int {
|
||||
res, _ := strconv.Atoi(this.GetString(key))
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetIntD(key string, def int) int {
|
||||
res, err := strconv.Atoi(this.GetString(key))
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetUint(key string) uint {
|
||||
res, _ := strconv.Atoi(this.GetString(key))
|
||||
return uint(res)
|
||||
}
|
||||
|
||||
func (this *RowMap) GetUintD(key string, def uint) uint {
|
||||
res, err := strconv.Atoi(this.GetString(key))
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return uint(res)
|
||||
}
|
||||
|
||||
func (this *RowMap) GetBool(key string) bool {
|
||||
return this.GetInt(key) != 0
|
||||
}
|
||||
|
||||
// knownDBs is a DB cache by uri
|
||||
var knownDBs map[string]*sql.DB = make(map[string]*sql.DB)
|
||||
var knownDBsMutex = &sync.Mutex{}
|
||||
|
||||
// GetDB returns a DB instance based on uri.
|
||||
// bool result indicates whether the DB was returned from cache; err
|
||||
func GetDB(mysql_uri string) (*sql.DB, bool, error) {
|
||||
knownDBsMutex.Lock()
|
||||
defer func() {
|
||||
knownDBsMutex.Unlock()
|
||||
}()
|
||||
|
||||
var exists bool
|
||||
if _, exists = knownDBs[mysql_uri]; !exists {
|
||||
if db, err := sql.Open("mysql", mysql_uri); err == nil {
|
||||
knownDBs[mysql_uri] = db
|
||||
} else {
|
||||
return db, exists, err
|
||||
}
|
||||
}
|
||||
return knownDBs[mysql_uri], exists, nil
|
||||
}
|
||||
|
||||
// RowToArray is a convenience function, typically not called directly, which maps a
|
||||
// single read database row into a NullString
|
||||
func RowToArray(rows *sql.Rows, columns []string) []CellData {
|
||||
buff := make([]interface{}, len(columns))
|
||||
data := make([]CellData, len(columns))
|
||||
for i, _ := range buff {
|
||||
buff[i] = data[i].NullString()
|
||||
}
|
||||
rows.Scan(buff...)
|
||||
return data
|
||||
}
|
||||
|
||||
// ScanRowsToArrays is a convenience function, typically not called directly, which maps rows
|
||||
// already read from the databse into arrays of NullString
|
||||
func ScanRowsToArrays(rows *sql.Rows, on_row func([]CellData) error) error {
|
||||
columns, _ := rows.Columns()
|
||||
for rows.Next() {
|
||||
arr := RowToArray(rows, columns)
|
||||
err := on_row(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rowToMap(row []CellData, columns []string) map[string]CellData {
|
||||
m := make(map[string]CellData)
|
||||
for k, data_col := range row {
|
||||
m[columns[k]] = data_col
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ScanRowsToMaps is a convenience function, typically not called directly, which maps rows
|
||||
// already read from the databse into RowMap entries.
|
||||
func ScanRowsToMaps(rows *sql.Rows, on_row func(RowMap) error) error {
|
||||
columns, _ := rows.Columns()
|
||||
err := ScanRowsToArrays(rows, func(arr []CellData) error {
|
||||
m := rowToMap(arr, columns)
|
||||
err := on_row(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// QueryRowsMap is a convenience function allowing querying a result set while poviding a callback
|
||||
// function activated per read row.
|
||||
func QueryRowsMap(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
|
||||
rows, err := db.Query(query, args...)
|
||||
defer rows.Close()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return log.Errore(err)
|
||||
}
|
||||
err = ScanRowsToMaps(rows, on_row)
|
||||
return err
|
||||
}
|
||||
|
||||
// queryResultData returns a raw array of rows for a given query, optionally reading and returning column names
|
||||
func queryResultData(db *sql.DB, query string, retrieveColumns bool, args ...interface{}) (ResultData, []string, error) {
|
||||
var err error
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
|
||||
columns := []string{}
|
||||
rows, err := db.Query(query, args...)
|
||||
defer rows.Close()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return EmptyResultData, columns, log.Errore(err)
|
||||
}
|
||||
if retrieveColumns {
|
||||
// Don't pay if you don't want to
|
||||
columns, _ = rows.Columns()
|
||||
}
|
||||
resultData := ResultData{}
|
||||
err = ScanRowsToArrays(rows, func(rowData []CellData) error {
|
||||
resultData = append(resultData, rowData)
|
||||
return nil
|
||||
})
|
||||
return resultData, columns, err
|
||||
}
|
||||
|
||||
// QueryResultData returns a raw array of rows
|
||||
func QueryResultData(db *sql.DB, query string, args ...interface{}) (ResultData, error) {
|
||||
resultData, _, err := queryResultData(db, query, false, args...)
|
||||
return resultData, err
|
||||
}
|
||||
|
||||
// QueryResultDataNamed returns a raw array of rows, with column names
|
||||
func QueryResultDataNamed(db *sql.DB, query string, args ...interface{}) (ResultData, []string, error) {
|
||||
return queryResultData(db, query, true, args...)
|
||||
}
|
||||
|
||||
// QueryRowsMapBuffered reads data from the database into a buffer, and only then applies the given function per row.
|
||||
// This allows the application to take its time with processing the data, albeit consuming as much memory as required by
|
||||
// the result set.
|
||||
func QueryRowsMapBuffered(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) error {
|
||||
resultData, columns, err := queryResultData(db, query, true, args...)
|
||||
if err != nil {
|
||||
// Already logged
|
||||
return err
|
||||
}
|
||||
for _, row := range resultData {
|
||||
err = on_row(rowToMap(row, columns))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecNoPrepare executes given query using given args on given DB, without using prepared statements.
|
||||
func ExecNoPrepare(db *sql.DB, query string, args ...interface{}) (sql.Result, error) {
|
||||
var err error
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("ExecNoPrepare unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
|
||||
var res sql.Result
|
||||
res, err = db.Exec(query, args...)
|
||||
if err != nil {
|
||||
log.Errore(err)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// ExecQuery executes given query using given args on given DB. It will safele prepare, execute and close
|
||||
// the statement.
|
||||
func execInternal(silent bool, db *sql.DB, query string, args ...interface{}) (sql.Result, error) {
|
||||
var err error
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("execInternal unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
|
||||
stmt, err := db.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
var res sql.Result
|
||||
res, err = stmt.Exec(args...)
|
||||
if err != nil && !silent {
|
||||
log.Errore(err)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Exec executes given query using given args on given DB. It will safele prepare, execute and close
|
||||
// the statement.
|
||||
func Exec(db *sql.DB, query string, args ...interface{}) (sql.Result, error) {
|
||||
return execInternal(false, db, query, args...)
|
||||
}
|
||||
|
||||
// ExecSilently acts like Exec but does not report any error
|
||||
func ExecSilently(db *sql.DB, query string, args ...interface{}) (sql.Result, error) {
|
||||
return execInternal(true, db, query, args...)
|
||||
}
|
||||
|
||||
func InClauseStringValues(terms []string) string {
|
||||
quoted := []string{}
|
||||
for _, s := range terms {
|
||||
quoted = append(quoted, fmt.Sprintf("'%s'", strings.Replace(s, ",", "''", -1)))
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
|
||||
// Convert variable length arguments into arguments array
|
||||
func Args(args ...interface{}) []interface{} {
|
||||
return args
|
||||
}
|
28
vendor/gopkg.in/gcfg.v1/LICENSE
generated
vendored
Normal file
28
vendor/gopkg.in/gcfg.v1/LICENSE
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go
|
||||
Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
4
vendor/gopkg.in/gcfg.v1/README
generated
vendored
Normal file
4
vendor/gopkg.in/gcfg.v1/README
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
Gcfg reads INI-style configuration files into Go structs;
|
||||
supports user-defined types and subsections.
|
||||
|
||||
Package docs: https://godoc.org/gopkg.in/gcfg.v1
|
118
vendor/gopkg.in/gcfg.v1/doc.go
generated
vendored
Normal file
118
vendor/gopkg.in/gcfg.v1/doc.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
// Package gcfg reads "INI-style" text-based configuration files with
|
||||
// "name=value" pairs grouped into sections (gcfg files).
|
||||
//
|
||||
// This package is still a work in progress; see the sections below for planned
|
||||
// changes.
|
||||
//
|
||||
// Syntax
|
||||
//
|
||||
// The syntax is based on that used by git config:
|
||||
// http://git-scm.com/docs/git-config#_syntax .
|
||||
// There are some (planned) differences compared to the git config format:
|
||||
// - improve data portability:
|
||||
// - must be encoded in UTF-8 (for now) and must not contain the 0 byte
|
||||
// - include and "path" type is not supported
|
||||
// (path type may be implementable as a user-defined type)
|
||||
// - internationalization
|
||||
// - section and variable names can contain unicode letters, unicode digits
|
||||
// (as defined in http://golang.org/ref/spec#Characters ) and hyphens
|
||||
// (U+002D), starting with a unicode letter
|
||||
// - disallow potentially ambiguous or misleading definitions:
|
||||
// - `[sec.sub]` format is not allowed (deprecated in gitconfig)
|
||||
// - `[sec ""]` is not allowed
|
||||
// - use `[sec]` for section name "sec" and empty subsection name
|
||||
// - (planned) within a single file, definitions must be contiguous for each:
|
||||
// - section: '[secA]' -> '[secB]' -> '[secA]' is an error
|
||||
// - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error
|
||||
// - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error
|
||||
//
|
||||
// Data structure
|
||||
//
|
||||
// The functions in this package read values into a user-defined struct.
|
||||
// Each section corresponds to a struct field in the config struct, and each
|
||||
// variable in a section corresponds to a data field in the section struct.
|
||||
// The mapping of each section or variable name to fields is done either based
|
||||
// on the "gcfg" struct tag or by matching the name of the section or variable,
|
||||
// ignoring case. In the latter case, hyphens '-' in section and variable names
|
||||
// correspond to underscores '_' in field names.
|
||||
// Fields must be exported; to use a section or variable name starting with a
|
||||
// letter that is neither upper- or lower-case, prefix the field name with 'X'.
|
||||
// (See https://code.google.com/p/go/issues/detail?id=5763#c4 .)
|
||||
//
|
||||
// For sections with subsections, the corresponding field in config must be a
|
||||
// map, rather than a struct, with string keys and pointer-to-struct values.
|
||||
// Values for subsection variables are stored in the map with the subsection
|
||||
// name used as the map key.
|
||||
// (Note that unlike section and variable names, subsection names are case
|
||||
// sensitive.)
|
||||
// When using a map, and there is a section with the same section name but
|
||||
// without a subsection name, its values are stored with the empty string used
|
||||
// as the key.
|
||||
//
|
||||
// The functions in this package panic if config is not a pointer to a struct,
|
||||
// or when a field is not of a suitable type (either a struct or a map with
|
||||
// string keys and pointer-to-struct values).
|
||||
//
|
||||
// Parsing of values
|
||||
//
|
||||
// The section structs in the config struct may contain single-valued or
|
||||
// multi-valued variables. Variables of unnamed slice type (that is, a type
|
||||
// starting with `[]`) are treated as multi-value; all others (including named
|
||||
// slice types) are treated as single-valued variables.
|
||||
//
|
||||
// Single-valued variables are handled based on the type as follows.
|
||||
// Unnamed pointer types (that is, types starting with `*`) are dereferenced,
|
||||
// and if necessary, a new instance is allocated.
|
||||
//
|
||||
// For types implementing the encoding.TextUnmarshaler interface, the
|
||||
// UnmarshalText method is used to set the value. Implementing this method is
|
||||
// the recommended way for parsing user-defined types.
|
||||
//
|
||||
// For fields of string kind, the value string is assigned to the field, after
|
||||
// unquoting and unescaping as needed.
|
||||
// For fields of bool kind, the field is set to true if the value is "true",
|
||||
// "yes", "on" or "1", and set to false if the value is "false", "no", "off" or
|
||||
// "0", ignoring case. In addition, single-valued bool fields can be specified
|
||||
// with a "blank" value (variable name without equals sign and value); in such
|
||||
// case the value is set to true.
|
||||
//
|
||||
// Predefined integer types [u]int(|8|16|32|64) and big.Int are parsed as
|
||||
// decimal or hexadecimal (if having '0x' prefix). (This is to prevent
|
||||
// unintuitively handling zero-padded numbers as octal.) Other types having
|
||||
// [u]int* as the underlying type, such as os.FileMode and uintptr allow
|
||||
// decimal, hexadecimal, or octal values.
|
||||
// Parsing mode for integer types can be overridden using the struct tag option
|
||||
// ",int=mode" where mode is a combination of the 'd', 'h', and 'o' characters
|
||||
// (each standing for decimal, hexadecimal, and octal, respectively.)
|
||||
//
|
||||
// All other types are parsed using fmt.Sscanf with the "%v" verb.
|
||||
//
|
||||
// For multi-valued variables, each individual value is parsed as above and
|
||||
// appended to the slice. If the first value is specified as a "blank" value
|
||||
// (variable name without equals sign and value), a new slice is allocated;
|
||||
// that is any values previously set in the slice will be ignored.
|
||||
//
|
||||
// The types subpackage for provides helpers for parsing "enum-like" and integer
|
||||
// types.
|
||||
//
|
||||
// TODO
|
||||
//
|
||||
// The following is a list of changes under consideration:
|
||||
// - documentation
|
||||
// - self-contained syntax documentation
|
||||
// - more practical examples
|
||||
// - move TODOs to issue tracker (eventually)
|
||||
// - syntax
|
||||
// - reconsider valid escape sequences
|
||||
// (gitconfig doesn't support \r in value, \t in subsection name, etc.)
|
||||
// - reading / parsing gcfg files
|
||||
// - define internal representation structure
|
||||
// - support multiple inputs (readers, strings, files)
|
||||
// - support declaring encoding (?)
|
||||
// - support varying fields sets for subsections (?)
|
||||
// - writing gcfg files
|
||||
// - error handling
|
||||
// - make error context accessible programmatically?
|
||||
// - limit input size?
|
||||
//
|
||||
package gcfg // import "gopkg.in/gcfg.v1"
|
132
vendor/gopkg.in/gcfg.v1/example_test.go
generated
vendored
Normal file
132
vendor/gopkg.in/gcfg.v1/example_test.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
package gcfg_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
import "gopkg.in/gcfg.v1"
|
||||
|
||||
func ExampleReadStringInto() {
|
||||
cfgStr := `; Comment line
|
||||
[section]
|
||||
name=value # comment`
|
||||
cfg := struct {
|
||||
Section struct {
|
||||
Name string
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Println(cfg.Section.Name)
|
||||
// Output: value
|
||||
}
|
||||
|
||||
func ExampleReadStringInto_bool() {
|
||||
cfgStr := `; Comment line
|
||||
[section]
|
||||
switch=on`
|
||||
cfg := struct {
|
||||
Section struct {
|
||||
Switch bool
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Println(cfg.Section.Switch)
|
||||
// Output: true
|
||||
}
|
||||
|
||||
func ExampleReadStringInto_hyphens() {
|
||||
cfgStr := `; Comment line
|
||||
[section-name]
|
||||
variable-name=value # comment`
|
||||
cfg := struct {
|
||||
Section_Name struct {
|
||||
Variable_Name string
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Println(cfg.Section_Name.Variable_Name)
|
||||
// Output: value
|
||||
}
|
||||
|
||||
func ExampleReadStringInto_tags() {
|
||||
cfgStr := `; Comment line
|
||||
[section]
|
||||
var-name=value # comment`
|
||||
cfg := struct {
|
||||
Section struct {
|
||||
FieldName string `gcfg:"var-name"`
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Println(cfg.Section.FieldName)
|
||||
// Output: value
|
||||
}
|
||||
|
||||
func ExampleReadStringInto_subsections() {
|
||||
cfgStr := `; Comment line
|
||||
[profile "A"]
|
||||
color = white
|
||||
|
||||
[profile "B"]
|
||||
color = black
|
||||
`
|
||||
cfg := struct {
|
||||
Profile map[string]*struct {
|
||||
Color string
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Printf("%s %s\n", cfg.Profile["A"].Color, cfg.Profile["B"].Color)
|
||||
// Output: white black
|
||||
}
|
||||
|
||||
func ExampleReadStringInto_multivalue() {
|
||||
cfgStr := `; Comment line
|
||||
[section]
|
||||
multi=value1
|
||||
multi=value2`
|
||||
cfg := struct {
|
||||
Section struct {
|
||||
Multi []string
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Println(cfg.Section.Multi)
|
||||
// Output: [value1 value2]
|
||||
}
|
||||
|
||||
func ExampleReadStringInto_unicode() {
|
||||
cfgStr := `; Comment line
|
||||
[甲]
|
||||
乙=丙 # comment`
|
||||
cfg := struct {
|
||||
X甲 struct {
|
||||
X乙 string
|
||||
}
|
||||
}{}
|
||||
err := gcfg.ReadStringInto(&cfg, cfgStr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse gcfg data: %s", err)
|
||||
}
|
||||
fmt.Println(cfg.X甲.X乙)
|
||||
// Output: 丙
|
||||
}
|
7
vendor/gopkg.in/gcfg.v1/go1_0.go
generated
vendored
Normal file
7
vendor/gopkg.in/gcfg.v1/go1_0.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// +build !go1.2
|
||||
|
||||
package gcfg
|
||||
|
||||
type textUnmarshaler interface {
|
||||
UnmarshalText(text []byte) error
|
||||
}
|
9
vendor/gopkg.in/gcfg.v1/go1_2.go
generated
vendored
Normal file
9
vendor/gopkg.in/gcfg.v1/go1_2.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// +build go1.2
|
||||
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
type textUnmarshaler encoding.TextUnmarshaler
|
63
vendor/gopkg.in/gcfg.v1/issues_test.go
generated
vendored
Normal file
63
vendor/gopkg.in/gcfg.v1/issues_test.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Config1 struct {
|
||||
Section struct {
|
||||
Int int
|
||||
BigInt big.Int
|
||||
}
|
||||
}
|
||||
|
||||
var testsIssue1 = []struct {
|
||||
cfg string
|
||||
typename string
|
||||
}{
|
||||
{"[section]\nint=X", "int"},
|
||||
{"[section]\nint=", "int"},
|
||||
{"[section]\nint=1A", "int"},
|
||||
{"[section]\nbigint=X", "big.Int"},
|
||||
{"[section]\nbigint=", "big.Int"},
|
||||
{"[section]\nbigint=1A", "big.Int"},
|
||||
}
|
||||
|
||||
// Value parse error should:
|
||||
// - include plain type name
|
||||
// - not include reflect internals
|
||||
func TestIssue1(t *testing.T) {
|
||||
for i, tt := range testsIssue1 {
|
||||
var c Config1
|
||||
err := ReadStringInto(&c, tt.cfg)
|
||||
switch {
|
||||
case err == nil:
|
||||
t.Errorf("%d fail: got ok; wanted error", i)
|
||||
case !strings.Contains(err.Error(), tt.typename):
|
||||
t.Errorf("%d fail: error message doesn't contain type name %q: %v",
|
||||
i, tt.typename, err)
|
||||
case strings.Contains(err.Error(), "reflect"):
|
||||
t.Errorf("%d fail: error message includes reflect internals: %v",
|
||||
i, err)
|
||||
default:
|
||||
t.Logf("%d pass: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type confIssue2 struct{ Main struct{ Foo string } }
|
||||
|
||||
var testsIssue2 = []readtest{
|
||||
{"[main]\n;\nfoo = bar\n", &confIssue2{struct{ Foo string }{"bar"}}, true},
|
||||
{"[main]\r\n;\r\nfoo = bar\r\n", &confIssue2{struct{ Foo string }{"bar"}}, true},
|
||||
}
|
||||
|
||||
func TestIssue2(t *testing.T) {
|
||||
for i, tt := range testsIssue2 {
|
||||
id := fmt.Sprintf("issue2:%d", i)
|
||||
testRead(t, id, tt)
|
||||
}
|
||||
}
|
188
vendor/gopkg.in/gcfg.v1/read.go
generated
vendored
Normal file
188
vendor/gopkg.in/gcfg.v1/read.go
generated
vendored
Normal file
@ -0,0 +1,188 @@
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
import (
|
||||
"gopkg.in/gcfg.v1/scanner"
|
||||
"gopkg.in/gcfg.v1/token"
|
||||
)
|
||||
|
||||
var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t'}
|
||||
|
||||
// no error: invalid literals should be caught by scanner
|
||||
func unquote(s string) string {
|
||||
u, q, esc := make([]rune, 0, len(s)), false, false
|
||||
for _, c := range s {
|
||||
if esc {
|
||||
uc, ok := unescape[c]
|
||||
switch {
|
||||
case ok:
|
||||
u = append(u, uc)
|
||||
fallthrough
|
||||
case !q && c == '\n':
|
||||
esc = false
|
||||
continue
|
||||
}
|
||||
panic("invalid escape sequence")
|
||||
}
|
||||
switch c {
|
||||
case '"':
|
||||
q = !q
|
||||
case '\\':
|
||||
esc = true
|
||||
default:
|
||||
u = append(u, c)
|
||||
}
|
||||
}
|
||||
if q {
|
||||
panic("missing end quote")
|
||||
}
|
||||
if esc {
|
||||
panic("invalid escape sequence")
|
||||
}
|
||||
return string(u)
|
||||
}
|
||||
|
||||
func readInto(config interface{}, fset *token.FileSet, file *token.File, src []byte) error {
|
||||
var s scanner.Scanner
|
||||
var errs scanner.ErrorList
|
||||
s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0)
|
||||
sect, sectsub := "", ""
|
||||
pos, tok, lit := s.Scan()
|
||||
errfn := func(msg string) error {
|
||||
return fmt.Errorf("%s: %s", fset.Position(pos), msg)
|
||||
}
|
||||
for {
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
switch tok {
|
||||
case token.EOF:
|
||||
return nil
|
||||
case token.EOL, token.COMMENT:
|
||||
pos, tok, lit = s.Scan()
|
||||
case token.LBRACK:
|
||||
pos, tok, lit = s.Scan()
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
if tok != token.IDENT {
|
||||
return errfn("expected section name")
|
||||
}
|
||||
sect, sectsub = lit, ""
|
||||
pos, tok, lit = s.Scan()
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
if tok == token.STRING {
|
||||
sectsub = unquote(lit)
|
||||
if sectsub == "" {
|
||||
return errfn("empty subsection name")
|
||||
}
|
||||
pos, tok, lit = s.Scan()
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
}
|
||||
if tok != token.RBRACK {
|
||||
if sectsub == "" {
|
||||
return errfn("expected subsection name or right bracket")
|
||||
}
|
||||
return errfn("expected right bracket")
|
||||
}
|
||||
pos, tok, lit = s.Scan()
|
||||
if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
|
||||
return errfn("expected EOL, EOF, or comment")
|
||||
}
|
||||
// If a section/subsection header was found, ensure a
|
||||
// container object is created, even if there are no
|
||||
// variables further down.
|
||||
err := set(config, sect, sectsub, "", true, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case token.IDENT:
|
||||
if sect == "" {
|
||||
return errfn("expected section header")
|
||||
}
|
||||
n := lit
|
||||
pos, tok, lit = s.Scan()
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, ""
|
||||
if !blank {
|
||||
if tok != token.ASSIGN {
|
||||
return errfn("expected '='")
|
||||
}
|
||||
pos, tok, lit = s.Scan()
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
if tok != token.STRING {
|
||||
return errfn("expected value")
|
||||
}
|
||||
v = unquote(lit)
|
||||
pos, tok, lit = s.Scan()
|
||||
if errs.Len() > 0 {
|
||||
return errs.Err()
|
||||
}
|
||||
if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
|
||||
return errfn("expected EOL, EOF, or comment")
|
||||
}
|
||||
}
|
||||
err := set(config, sect, sectsub, n, blank, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if sect == "" {
|
||||
return errfn("expected section header")
|
||||
}
|
||||
return errfn("expected section header or variable declaration")
|
||||
}
|
||||
}
|
||||
panic("never reached")
|
||||
}
|
||||
|
||||
// ReadInto reads gcfg formatted data from reader and sets the values into the
|
||||
// corresponding fields in config.
|
||||
func ReadInto(config interface{}, reader io.Reader) error {
|
||||
src, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
file := fset.AddFile("", fset.Base(), len(src))
|
||||
return readInto(config, fset, file, src)
|
||||
}
|
||||
|
||||
// ReadStringInto reads gcfg formatted data from str and sets the values into
|
||||
// the corresponding fields in config.
|
||||
func ReadStringInto(config interface{}, str string) error {
|
||||
r := strings.NewReader(str)
|
||||
return ReadInto(config, r)
|
||||
}
|
||||
|
||||
// ReadFileInto reads gcfg formatted data from the file filename and sets the
|
||||
// values into the corresponding fields in config.
|
||||
func ReadFileInto(config interface{}, filename string) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
src, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
file := fset.AddFile(filename, fset.Base(), len(src))
|
||||
return readInto(config, fset, file, src)
|
||||
}
|
338
vendor/gopkg.in/gcfg.v1/read_test.go
generated
vendored
Normal file
338
vendor/gopkg.in/gcfg.v1/read_test.go
generated
vendored
Normal file
@ -0,0 +1,338 @@
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
// 64 spaces
|
||||
sp64 = " "
|
||||
// 512 spaces
|
||||
sp512 = sp64 + sp64 + sp64 + sp64 + sp64 + sp64 + sp64 + sp64
|
||||
// 4096 spaces
|
||||
sp4096 = sp512 + sp512 + sp512 + sp512 + sp512 + sp512 + sp512 + sp512
|
||||
)
|
||||
|
||||
type cBasic struct {
|
||||
Section cBasicS1
|
||||
Hyphen_In_Section cBasicS2
|
||||
unexported cBasicS1
|
||||
Exported cBasicS3
|
||||
TagName cBasicS1 `gcfg:"tag-name"`
|
||||
}
|
||||
type cBasicS1 struct {
|
||||
Name string
|
||||
Int int
|
||||
PName *string
|
||||
}
|
||||
type cBasicS2 struct {
|
||||
Hyphen_In_Name string
|
||||
}
|
||||
type cBasicS3 struct {
|
||||
unexported string
|
||||
}
|
||||
|
||||
type nonMulti []string
|
||||
|
||||
type unmarshalable string
|
||||
|
||||
func (u *unmarshalable) UnmarshalText(text []byte) error {
|
||||
s := string(text)
|
||||
if s == "error" {
|
||||
return fmt.Errorf("%s", s)
|
||||
}
|
||||
*u = unmarshalable(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ textUnmarshaler = new(unmarshalable)
|
||||
|
||||
type cUni struct {
|
||||
X甲 cUniS1
|
||||
XSection cUniS2
|
||||
}
|
||||
type cUniS1 struct {
|
||||
X乙 string
|
||||
}
|
||||
type cUniS2 struct {
|
||||
XName string
|
||||
}
|
||||
|
||||
type cMulti struct {
|
||||
M1 cMultiS1
|
||||
M2 cMultiS2
|
||||
M3 cMultiS3
|
||||
}
|
||||
type cMultiS1 struct{ Multi []string }
|
||||
type cMultiS2 struct{ NonMulti nonMulti }
|
||||
type cMultiS3 struct{ PMulti *[]string }
|
||||
|
||||
type cSubs struct{ Sub map[string]*cSubsS1 }
|
||||
type cSubsS1 struct{ Name string }
|
||||
|
||||
type cBool struct{ Section cBoolS1 }
|
||||
type cBoolS1 struct{ Bool bool }
|
||||
|
||||
type cTxUnm struct{ Section cTxUnmS1 }
|
||||
type cTxUnmS1 struct{ Name unmarshalable }
|
||||
|
||||
type cNum struct {
|
||||
N1 cNumS1
|
||||
N2 cNumS2
|
||||
N3 cNumS3
|
||||
}
|
||||
type cNumS1 struct {
|
||||
Int int
|
||||
IntDHO int `gcfg:",int=dho"`
|
||||
Big *big.Int
|
||||
}
|
||||
type cNumS2 struct {
|
||||
MultiInt []int
|
||||
MultiBig []*big.Int
|
||||
}
|
||||
type cNumS3 struct{ FileMode os.FileMode }
|
||||
type readtest struct {
|
||||
gcfg string
|
||||
exp interface{}
|
||||
ok bool
|
||||
}
|
||||
|
||||
func newString(s string) *string { return &s }
|
||||
func newStringSlice(s ...string) *[]string { return &s }
|
||||
|
||||
var readtests = []struct {
|
||||
group string
|
||||
tests []readtest
|
||||
}{{"scanning", []readtest{
|
||||
{"[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
// hyphen in name
|
||||
{"[hyphen-in-section]\nhyphen-in-name=value", &cBasic{Hyphen_In_Section: cBasicS2{Hyphen_In_Name: "value"}}, true},
|
||||
// quoted string value
|
||||
{"[section]\nname=\"\"", &cBasic{Section: cBasicS1{Name: ""}}, true},
|
||||
{"[section]\nname=\" \"", &cBasic{Section: cBasicS1{Name: " "}}, true},
|
||||
{"[section]\nname=\"value\"", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname=\" value \"", &cBasic{Section: cBasicS1{Name: " value "}}, true},
|
||||
{"\n[section]\nname=\"va ; lue\"", &cBasic{Section: cBasicS1{Name: "va ; lue"}}, true},
|
||||
{"[section]\nname=\"val\" \"ue\"", &cBasic{Section: cBasicS1{Name: "val ue"}}, true},
|
||||
{"[section]\nname=\"value", &cBasic{}, false},
|
||||
// escape sequences
|
||||
{"[section]\nname=\"va\\\\lue\"", &cBasic{Section: cBasicS1{Name: "va\\lue"}}, true},
|
||||
{"[section]\nname=\"va\\\"lue\"", &cBasic{Section: cBasicS1{Name: "va\"lue"}}, true},
|
||||
{"[section]\nname=\"va\\nlue\"", &cBasic{Section: cBasicS1{Name: "va\nlue"}}, true},
|
||||
{"[section]\nname=\"va\\tlue\"", &cBasic{Section: cBasicS1{Name: "va\tlue"}}, true},
|
||||
{"\n[section]\nname=\\", &cBasic{}, false},
|
||||
{"\n[section]\nname=\\a", &cBasic{}, false},
|
||||
{"\n[section]\nname=\"val\\a\"", &cBasic{}, false},
|
||||
{"\n[section]\nname=val\\", &cBasic{}, false},
|
||||
{"\n[sub \"A\\\n\"]\nname=value", &cSubs{}, false},
|
||||
{"\n[sub \"A\\\t\"]\nname=value", &cSubs{}, false},
|
||||
// broken line
|
||||
{"[section]\nname=value \\\n value", &cBasic{Section: cBasicS1{Name: "value value"}}, true},
|
||||
{"[section]\nname=\"value \\\n value\"", &cBasic{}, false},
|
||||
}}, {"scanning:whitespace", []readtest{
|
||||
{" \n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{" [section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\t[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[ section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section ]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\n name=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname =value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname= value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname=value ", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\r\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\r\nname=value\r\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{";cmnt\r\n[section]\r\nname=value\r\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
// long lines
|
||||
{sp4096 + "[section]\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[" + sp4096 + "section]\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section" + sp4096 + "]\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]" + sp4096 + "\nname=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\n" + sp4096 + "name=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname" + sp4096 + "=value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname=" + sp4096 + "value\n", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname=value\n" + sp4096, &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
}}, {"scanning:comments", []readtest{
|
||||
{"; cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"# cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{" ; cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\t; cmnt\n[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section]; cmnt\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section] ; cmnt\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section]\nname=value; cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section]\nname=value ; cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section]\nname=\"value\" ; cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section]\nname=value ; \"cmnt", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"\n[section]\nname=\"va ; lue\" ; cmnt", &cBasic{Section: cBasicS1{Name: "va ; lue"}}, true},
|
||||
{"\n[section]\nname=; cmnt", &cBasic{Section: cBasicS1{Name: ""}}, true},
|
||||
}}, {"scanning:subsections", []readtest{
|
||||
{"\n[sub \"A\"]\nname=value", &cSubs{map[string]*cSubsS1{"A": &cSubsS1{"value"}}}, true},
|
||||
{"\n[sub \"b\"]\nname=value", &cSubs{map[string]*cSubsS1{"b": &cSubsS1{"value"}}}, true},
|
||||
{"\n[sub \"A\\\\\"]\nname=value", &cSubs{map[string]*cSubsS1{"A\\": &cSubsS1{"value"}}}, true},
|
||||
{"\n[sub \"A\\\"\"]\nname=value", &cSubs{map[string]*cSubsS1{"A\"": &cSubsS1{"value"}}}, true},
|
||||
}}, {"syntax", []readtest{
|
||||
// invalid line
|
||||
{"\n[section]\n=", &cBasic{}, false},
|
||||
// no section
|
||||
{"name=value", &cBasic{}, false},
|
||||
// empty section
|
||||
{"\n[]\nname=value", &cBasic{}, false},
|
||||
// empty subsection
|
||||
{"\n[sub \"\"]\nname=value", &cSubs{}, false},
|
||||
}}, {"setting", []readtest{
|
||||
{"[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
// pointer
|
||||
{"[section]", &cBasic{Section: cBasicS1{PName: nil}}, true},
|
||||
{"[section]\npname=value", &cBasic{Section: cBasicS1{PName: newString("value")}}, true},
|
||||
{"[m3]", &cMulti{M3: cMultiS3{PMulti: nil}}, true},
|
||||
{"[m3]\npmulti", &cMulti{M3: cMultiS3{PMulti: newStringSlice()}}, true},
|
||||
{"[m3]\npmulti=value", &cMulti{M3: cMultiS3{PMulti: newStringSlice("value")}}, true},
|
||||
{"[m3]\npmulti=value1\npmulti=value2", &cMulti{M3: cMultiS3{PMulti: newStringSlice("value1", "value2")}}, true},
|
||||
// section name not matched
|
||||
{"\n[nonexistent]\nname=value", &cBasic{}, false},
|
||||
// subsection name not matched
|
||||
{"\n[section \"nonexistent\"]\nname=value", &cBasic{}, false},
|
||||
// variable name not matched
|
||||
{"\n[section]\nnonexistent=value", &cBasic{}, false},
|
||||
// hyphen in name
|
||||
{"[hyphen-in-section]\nhyphen-in-name=value", &cBasic{Hyphen_In_Section: cBasicS2{Hyphen_In_Name: "value"}}, true},
|
||||
// ignore unexported fields
|
||||
{"[unexported]\nname=value", &cBasic{}, false},
|
||||
{"[exported]\nunexported=value", &cBasic{}, false},
|
||||
// 'X' prefix for non-upper/lower-case letters
|
||||
{"[甲]\n乙=丙", &cUni{X甲: cUniS1{X乙: "丙"}}, true},
|
||||
//{"[section]\nxname=value", &cBasic{XSection: cBasicS4{XName: "value"}}, false},
|
||||
//{"[xsection]\nname=value", &cBasic{XSection: cBasicS4{XName: "value"}}, false},
|
||||
// name specified as struct tag
|
||||
{"[tag-name]\nname=value", &cBasic{TagName: cBasicS1{Name: "value"}}, true},
|
||||
// empty subsections
|
||||
{"\n[sub \"A\"]\n[sub \"B\"]", &cSubs{map[string]*cSubsS1{"A": &cSubsS1{}, "B": &cSubsS1{}}}, true},
|
||||
}}, {"multivalue", []readtest{
|
||||
// unnamed slice type: treat as multi-value
|
||||
{"\n[m1]", &cMulti{M1: cMultiS1{}}, true},
|
||||
{"\n[m1]\nmulti=value", &cMulti{M1: cMultiS1{[]string{"value"}}}, true},
|
||||
{"\n[m1]\nmulti=value1\nmulti=value2", &cMulti{M1: cMultiS1{[]string{"value1", "value2"}}}, true},
|
||||
// "blank" empties multi-valued slice -- here same result as above
|
||||
{"\n[m1]\nmulti\nmulti=value1\nmulti=value2", &cMulti{M1: cMultiS1{[]string{"value1", "value2"}}}, true},
|
||||
// named slice type: do not treat as multi-value
|
||||
{"\n[m2]", &cMulti{}, true},
|
||||
{"\n[m2]\nmulti=value", &cMulti{}, false},
|
||||
{"\n[m2]\nmulti=value1\nmulti=value2", &cMulti{}, false},
|
||||
}}, {"type:string", []readtest{
|
||||
{"[section]\nname=value", &cBasic{Section: cBasicS1{Name: "value"}}, true},
|
||||
{"[section]\nname=", &cBasic{Section: cBasicS1{Name: ""}}, true},
|
||||
}}, {"type:bool", []readtest{
|
||||
// explicit values
|
||||
{"[section]\nbool=true", &cBool{cBoolS1{true}}, true},
|
||||
{"[section]\nbool=yes", &cBool{cBoolS1{true}}, true},
|
||||
{"[section]\nbool=on", &cBool{cBoolS1{true}}, true},
|
||||
{"[section]\nbool=1", &cBool{cBoolS1{true}}, true},
|
||||
{"[section]\nbool=tRuE", &cBool{cBoolS1{true}}, true},
|
||||
{"[section]\nbool=false", &cBool{cBoolS1{false}}, true},
|
||||
{"[section]\nbool=no", &cBool{cBoolS1{false}}, true},
|
||||
{"[section]\nbool=off", &cBool{cBoolS1{false}}, true},
|
||||
{"[section]\nbool=0", &cBool{cBoolS1{false}}, true},
|
||||
{"[section]\nbool=NO", &cBool{cBoolS1{false}}, true},
|
||||
// "blank" value handled as true
|
||||
{"[section]\nbool", &cBool{cBoolS1{true}}, true},
|
||||
// bool parse errors
|
||||
{"[section]\nbool=maybe", &cBool{}, false},
|
||||
{"[section]\nbool=t", &cBool{}, false},
|
||||
{"[section]\nbool=truer", &cBool{}, false},
|
||||
{"[section]\nbool=2", &cBool{}, false},
|
||||
{"[section]\nbool=-1", &cBool{}, false},
|
||||
}}, {"type:numeric", []readtest{
|
||||
{"[section]\nint=0", &cBasic{Section: cBasicS1{Int: 0}}, true},
|
||||
{"[section]\nint=1", &cBasic{Section: cBasicS1{Int: 1}}, true},
|
||||
{"[section]\nint=-1", &cBasic{Section: cBasicS1{Int: -1}}, true},
|
||||
{"[section]\nint=0.2", &cBasic{}, false},
|
||||
{"[section]\nint=1e3", &cBasic{}, false},
|
||||
// primitive [u]int(|8|16|32|64) and big.Int is parsed as dec or hex (not octal)
|
||||
{"[n1]\nint=010", &cNum{N1: cNumS1{Int: 10}}, true},
|
||||
{"[n1]\nint=0x10", &cNum{N1: cNumS1{Int: 0x10}}, true},
|
||||
{"[n1]\nbig=1", &cNum{N1: cNumS1{Big: big.NewInt(1)}}, true},
|
||||
{"[n1]\nbig=0x10", &cNum{N1: cNumS1{Big: big.NewInt(0x10)}}, true},
|
||||
{"[n1]\nbig=010", &cNum{N1: cNumS1{Big: big.NewInt(10)}}, true},
|
||||
{"[n2]\nmultiint=010", &cNum{N2: cNumS2{MultiInt: []int{10}}}, true},
|
||||
{"[n2]\nmultibig=010", &cNum{N2: cNumS2{MultiBig: []*big.Int{big.NewInt(10)}}}, true},
|
||||
// set parse mode for int types via struct tag
|
||||
{"[n1]\nintdho=010", &cNum{N1: cNumS1{IntDHO: 010}}, true},
|
||||
// octal allowed for named type
|
||||
{"[n3]\nfilemode=0777", &cNum{N3: cNumS3{FileMode: 0777}}, true},
|
||||
}}, {"type:textUnmarshaler", []readtest{
|
||||
{"[section]\nname=value", &cTxUnm{Section: cTxUnmS1{Name: "value"}}, true},
|
||||
{"[section]\nname=error", &cTxUnm{}, false},
|
||||
}},
|
||||
}
|
||||
|
||||
func TestReadStringInto(t *testing.T) {
|
||||
for _, tg := range readtests {
|
||||
for i, tt := range tg.tests {
|
||||
id := fmt.Sprintf("%s:%d", tg.group, i)
|
||||
testRead(t, id, tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadStringIntoMultiBlankPreset(t *testing.T) {
|
||||
tt := readtest{"\n[m1]\nmulti\nmulti=value1\nmulti=value2", &cMulti{M1: cMultiS1{[]string{"value1", "value2"}}}, true}
|
||||
cfg := &cMulti{M1: cMultiS1{[]string{"preset1", "preset2"}}}
|
||||
testReadInto(t, "multi:blank", tt, cfg)
|
||||
}
|
||||
|
||||
func testRead(t *testing.T, id string, tt readtest) {
|
||||
// get the type of the expected result
|
||||
restyp := reflect.TypeOf(tt.exp).Elem()
|
||||
// create a new instance to hold the actual result
|
||||
res := reflect.New(restyp).Interface()
|
||||
testReadInto(t, id, tt, res)
|
||||
}
|
||||
|
||||
func testReadInto(t *testing.T, id string, tt readtest, res interface{}) {
|
||||
err := ReadStringInto(res, tt.gcfg)
|
||||
if tt.ok {
|
||||
if err != nil {
|
||||
t.Errorf("%s fail: got error %v, wanted ok", id, err)
|
||||
return
|
||||
} else if !reflect.DeepEqual(res, tt.exp) {
|
||||
t.Errorf("%s fail: got value %#v, wanted value %#v", id, res, tt.exp)
|
||||
return
|
||||
}
|
||||
if !testing.Short() {
|
||||
t.Logf("%s pass: got value %#v", id, res)
|
||||
}
|
||||
} else { // !tt.ok
|
||||
if err == nil {
|
||||
t.Errorf("%s fail: got value %#v, wanted error", id, res)
|
||||
return
|
||||
}
|
||||
if !testing.Short() {
|
||||
t.Logf("%s pass: got error %v", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFileInto(t *testing.T) {
|
||||
res := &struct{ Section struct{ Name string } }{}
|
||||
err := ReadFileInto(res, "testdata/gcfg_test.gcfg")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if "value" != res.Section.Name {
|
||||
t.Errorf("got %q, wanted %q", res.Section.Name, "value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFileIntoUnicode(t *testing.T) {
|
||||
res := &struct{ X甲 struct{ X乙 string } }{}
|
||||
err := ReadFileInto(res, "testdata/gcfg_unicode_test.gcfg")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if "丙" != res.X甲.X乙 {
|
||||
t.Errorf("got %q, wanted %q", res.X甲.X乙, "丙")
|
||||
}
|
||||
}
|
121
vendor/gopkg.in/gcfg.v1/scanner/errors.go
generated
vendored
Normal file
121
vendor/gopkg.in/gcfg.v1/scanner/errors.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
import (
|
||||
"gopkg.in/gcfg.v1/token"
|
||||
)
|
||||
|
||||
// In an ErrorList, an error is represented by an *Error.
|
||||
// The position Pos, if valid, points to the beginning of
|
||||
// the offending token, and the error condition is described
|
||||
// by Msg.
|
||||
//
|
||||
type Error struct {
|
||||
Pos token.Position
|
||||
Msg string
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e Error) Error() string {
|
||||
if e.Pos.Filename != "" || e.Pos.IsValid() {
|
||||
// don't print "<unknown position>"
|
||||
// TODO(gri) reconsider the semantics of Position.IsValid
|
||||
return e.Pos.String() + ": " + e.Msg
|
||||
}
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
// ErrorList is a list of *Errors.
|
||||
// The zero value for an ErrorList is an empty ErrorList ready to use.
|
||||
//
|
||||
type ErrorList []*Error
|
||||
|
||||
// Add adds an Error with given position and error message to an ErrorList.
|
||||
func (p *ErrorList) Add(pos token.Position, msg string) {
|
||||
*p = append(*p, &Error{pos, msg})
|
||||
}
|
||||
|
||||
// Reset resets an ErrorList to no errors.
|
||||
func (p *ErrorList) Reset() { *p = (*p)[0:0] }
|
||||
|
||||
// ErrorList implements the sort Interface.
|
||||
func (p ErrorList) Len() int { return len(p) }
|
||||
func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func (p ErrorList) Less(i, j int) bool {
|
||||
e := &p[i].Pos
|
||||
f := &p[j].Pos
|
||||
if e.Filename < f.Filename {
|
||||
return true
|
||||
}
|
||||
if e.Filename == f.Filename {
|
||||
return e.Offset < f.Offset
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sort sorts an ErrorList. *Error entries are sorted by position,
|
||||
// other errors are sorted by error message, and before any *Error
|
||||
// entry.
|
||||
//
|
||||
func (p ErrorList) Sort() {
|
||||
sort.Sort(p)
|
||||
}
|
||||
|
||||
// RemoveMultiples sorts an ErrorList and removes all but the first error per line.
|
||||
func (p *ErrorList) RemoveMultiples() {
|
||||
sort.Sort(p)
|
||||
var last token.Position // initial last.Line is != any legal error line
|
||||
i := 0
|
||||
for _, e := range *p {
|
||||
if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line {
|
||||
last = e.Pos
|
||||
(*p)[i] = e
|
||||
i++
|
||||
}
|
||||
}
|
||||
(*p) = (*p)[0:i]
|
||||
}
|
||||
|
||||
// An ErrorList implements the error interface.
|
||||
func (p ErrorList) Error() string {
|
||||
switch len(p) {
|
||||
case 0:
|
||||
return "no errors"
|
||||
case 1:
|
||||
return p[0].Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1)
|
||||
}
|
||||
|
||||
// Err returns an error equivalent to this error list.
|
||||
// If the list is empty, Err returns nil.
|
||||
func (p ErrorList) Err() error {
|
||||
if len(p) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// PrintError is a utility function that prints a list of errors to w,
|
||||
// one error per line, if the err parameter is an ErrorList. Otherwise
|
||||
// it prints the err string.
|
||||
//
|
||||
func PrintError(w io.Writer, err error) {
|
||||
if list, ok := err.(ErrorList); ok {
|
||||
for _, e := range list {
|
||||
fmt.Fprintf(w, "%s\n", e)
|
||||
}
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(w, "%s\n", err)
|
||||
}
|
||||
}
|
46
vendor/gopkg.in/gcfg.v1/scanner/example_test.go
generated
vendored
Normal file
46
vendor/gopkg.in/gcfg.v1/scanner/example_test.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package scanner_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
import (
|
||||
"gopkg.in/gcfg.v1/scanner"
|
||||
"gopkg.in/gcfg.v1/token"
|
||||
)
|
||||
|
||||
func ExampleScanner_Scan() {
|
||||
// src is the input that we want to tokenize.
|
||||
src := []byte(`[profile "A"]
|
||||
color = blue ; Comment`)
|
||||
|
||||
// Initialize the scanner.
|
||||
var s scanner.Scanner
|
||||
fset := token.NewFileSet() // positions are relative to fset
|
||||
file := fset.AddFile("", fset.Base(), len(src)) // register input "file"
|
||||
s.Init(file, src, nil /* no error handler */, scanner.ScanComments)
|
||||
|
||||
// Repeated calls to Scan yield the token sequence found in the input.
|
||||
for {
|
||||
pos, tok, lit := s.Scan()
|
||||
if tok == token.EOF {
|
||||
break
|
||||
}
|
||||
fmt.Printf("%s\t%q\t%q\n", fset.Position(pos), tok, lit)
|
||||
}
|
||||
|
||||
// output:
|
||||
// 1:1 "[" ""
|
||||
// 1:2 "IDENT" "profile"
|
||||
// 1:10 "STRING" "\"A\""
|
||||
// 1:13 "]" ""
|
||||
// 1:14 "\n" ""
|
||||
// 2:1 "IDENT" "color"
|
||||
// 2:7 "=" ""
|
||||
// 2:9 "STRING" "blue"
|
||||
// 2:14 "COMMENT" "; Comment"
|
||||
}
|
342
vendor/gopkg.in/gcfg.v1/scanner/scanner.go
generated
vendored
Normal file
342
vendor/gopkg.in/gcfg.v1/scanner/scanner.go
generated
vendored
Normal file
@ -0,0 +1,342 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package scanner implements a scanner for gcfg configuration text.
|
||||
// It takes a []byte as source which can then be tokenized
|
||||
// through repeated calls to the Scan method.
|
||||
//
|
||||
// Note that the API for the scanner package may change to accommodate new
|
||||
// features or implementation changes in gcfg.
|
||||
//
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
import (
|
||||
"gopkg.in/gcfg.v1/token"
|
||||
)
|
||||
|
||||
// An ErrorHandler may be provided to Scanner.Init. If a syntax error is
|
||||
// encountered and a handler was installed, the handler is called with a
|
||||
// position and an error message. The position points to the beginning of
|
||||
// the offending token.
|
||||
//
|
||||
type ErrorHandler func(pos token.Position, msg string)
|
||||
|
||||
// A Scanner holds the scanner's internal state while processing
|
||||
// a given text. It can be allocated as part of another data
|
||||
// structure but must be initialized via Init before use.
|
||||
//
|
||||
type Scanner struct {
|
||||
// immutable state
|
||||
file *token.File // source file handle
|
||||
dir string // directory portion of file.Name()
|
||||
src []byte // source
|
||||
err ErrorHandler // error reporting; or nil
|
||||
mode Mode // scanning mode
|
||||
|
||||
// scanning state
|
||||
ch rune // current character
|
||||
offset int // character offset
|
||||
rdOffset int // reading offset (position after current character)
|
||||
lineOffset int // current line offset
|
||||
nextVal bool // next token is expected to be a value
|
||||
|
||||
// public state - ok to modify
|
||||
ErrorCount int // number of errors encountered
|
||||
}
|
||||
|
||||
// Read the next Unicode char into s.ch.
|
||||
// s.ch < 0 means end-of-file.
|
||||
//
|
||||
func (s *Scanner) next() {
|
||||
if s.rdOffset < len(s.src) {
|
||||
s.offset = s.rdOffset
|
||||
if s.ch == '\n' {
|
||||
s.lineOffset = s.offset
|
||||
s.file.AddLine(s.offset)
|
||||
}
|
||||
r, w := rune(s.src[s.rdOffset]), 1
|
||||
switch {
|
||||
case r == 0:
|
||||
s.error(s.offset, "illegal character NUL")
|
||||
case r >= 0x80:
|
||||
// not ASCII
|
||||
r, w = utf8.DecodeRune(s.src[s.rdOffset:])
|
||||
if r == utf8.RuneError && w == 1 {
|
||||
s.error(s.offset, "illegal UTF-8 encoding")
|
||||
}
|
||||
}
|
||||
s.rdOffset += w
|
||||
s.ch = r
|
||||
} else {
|
||||
s.offset = len(s.src)
|
||||
if s.ch == '\n' {
|
||||
s.lineOffset = s.offset
|
||||
s.file.AddLine(s.offset)
|
||||
}
|
||||
s.ch = -1 // eof
|
||||
}
|
||||
}
|
||||
|
||||
// A mode value is a set of flags (or 0).
|
||||
// They control scanner behavior.
|
||||
//
|
||||
type Mode uint
|
||||
|
||||
const (
|
||||
ScanComments Mode = 1 << iota // return comments as COMMENT tokens
|
||||
)
|
||||
|
||||
// Init prepares the scanner s to tokenize the text src by setting the
|
||||
// scanner at the beginning of src. The scanner uses the file set file
|
||||
// for position information and it adds line information for each line.
|
||||
// It is ok to re-use the same file when re-scanning the same file as
|
||||
// line information which is already present is ignored. Init causes a
|
||||
// panic if the file size does not match the src size.
|
||||
//
|
||||
// Calls to Scan will invoke the error handler err if they encounter a
|
||||
// syntax error and err is not nil. Also, for each error encountered,
|
||||
// the Scanner field ErrorCount is incremented by one. The mode parameter
|
||||
// determines how comments are handled.
|
||||
//
|
||||
// Note that Init may call err if there is an error in the first character
|
||||
// of the file.
|
||||
//
|
||||
func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) {
|
||||
// Explicitly initialize all fields since a scanner may be reused.
|
||||
if file.Size() != len(src) {
|
||||
panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src)))
|
||||
}
|
||||
s.file = file
|
||||
s.dir, _ = filepath.Split(file.Name())
|
||||
s.src = src
|
||||
s.err = err
|
||||
s.mode = mode
|
||||
|
||||
s.ch = ' '
|
||||
s.offset = 0
|
||||
s.rdOffset = 0
|
||||
s.lineOffset = 0
|
||||
s.ErrorCount = 0
|
||||
s.nextVal = false
|
||||
|
||||
s.next()
|
||||
}
|
||||
|
||||
func (s *Scanner) error(offs int, msg string) {
|
||||
if s.err != nil {
|
||||
s.err(s.file.Position(s.file.Pos(offs)), msg)
|
||||
}
|
||||
s.ErrorCount++
|
||||
}
|
||||
|
||||
func (s *Scanner) scanComment() string {
|
||||
// initial [;#] already consumed
|
||||
offs := s.offset - 1 // position of initial [;#]
|
||||
|
||||
for s.ch != '\n' && s.ch >= 0 {
|
||||
s.next()
|
||||
}
|
||||
return string(s.src[offs:s.offset])
|
||||
}
|
||||
|
||||
func isLetter(ch rune) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch >= 0x80 && unicode.IsLetter(ch)
|
||||
}
|
||||
|
||||
func isDigit(ch rune) bool {
|
||||
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
|
||||
}
|
||||
|
||||
func (s *Scanner) scanIdentifier() string {
|
||||
offs := s.offset
|
||||
for isLetter(s.ch) || isDigit(s.ch) || s.ch == '-' {
|
||||
s.next()
|
||||
}
|
||||
return string(s.src[offs:s.offset])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanEscape(val bool) {
|
||||
offs := s.offset
|
||||
ch := s.ch
|
||||
s.next() // always make progress
|
||||
switch ch {
|
||||
case '\\', '"':
|
||||
// ok
|
||||
case 'n', 't':
|
||||
if val {
|
||||
break // ok
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
s.error(offs, "unknown escape sequence")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) scanString() string {
|
||||
// '"' opening already consumed
|
||||
offs := s.offset - 1
|
||||
|
||||
for s.ch != '"' {
|
||||
ch := s.ch
|
||||
s.next()
|
||||
if ch == '\n' || ch < 0 {
|
||||
s.error(offs, "string not terminated")
|
||||
break
|
||||
}
|
||||
if ch == '\\' {
|
||||
s.scanEscape(false)
|
||||
}
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
return string(s.src[offs:s.offset])
|
||||
}
|
||||
|
||||
func stripCR(b []byte) []byte {
|
||||
c := make([]byte, len(b))
|
||||
i := 0
|
||||
for _, ch := range b {
|
||||
if ch != '\r' {
|
||||
c[i] = ch
|
||||
i++
|
||||
}
|
||||
}
|
||||
return c[:i]
|
||||
}
|
||||
|
||||
func (s *Scanner) scanValString() string {
|
||||
offs := s.offset
|
||||
|
||||
hasCR := false
|
||||
end := offs
|
||||
inQuote := false
|
||||
loop:
|
||||
for inQuote || s.ch >= 0 && s.ch != '\n' && s.ch != ';' && s.ch != '#' {
|
||||
ch := s.ch
|
||||
s.next()
|
||||
switch {
|
||||
case inQuote && ch == '\\':
|
||||
s.scanEscape(true)
|
||||
case !inQuote && ch == '\\':
|
||||
if s.ch == '\r' {
|
||||
hasCR = true
|
||||
s.next()
|
||||
}
|
||||
if s.ch != '\n' {
|
||||
s.error(offs, "unquoted '\\' must be followed by new line")
|
||||
break loop
|
||||
}
|
||||
s.next()
|
||||
case ch == '"':
|
||||
inQuote = !inQuote
|
||||
case ch == '\r':
|
||||
hasCR = true
|
||||
case ch < 0 || inQuote && ch == '\n':
|
||||
s.error(offs, "string not terminated")
|
||||
break loop
|
||||
}
|
||||
if inQuote || !isWhiteSpace(ch) {
|
||||
end = s.offset
|
||||
}
|
||||
}
|
||||
|
||||
lit := s.src[offs:end]
|
||||
if hasCR {
|
||||
lit = stripCR(lit)
|
||||
}
|
||||
|
||||
return string(lit)
|
||||
}
|
||||
|
||||
func isWhiteSpace(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || ch == '\r'
|
||||
}
|
||||
|
||||
func (s *Scanner) skipWhitespace() {
|
||||
for isWhiteSpace(s.ch) {
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
|
||||
// Scan scans the next token and returns the token position, the token,
|
||||
// and its literal string if applicable. The source end is indicated by
|
||||
// token.EOF.
|
||||
//
|
||||
// If the returned token is a literal (token.IDENT, token.STRING) or
|
||||
// token.COMMENT, the literal string has the corresponding value.
|
||||
//
|
||||
// If the returned token is token.ILLEGAL, the literal string is the
|
||||
// offending character.
|
||||
//
|
||||
// In all other cases, Scan returns an empty literal string.
|
||||
//
|
||||
// For more tolerant parsing, Scan will return a valid token if
|
||||
// possible even if a syntax error was encountered. Thus, even
|
||||
// if the resulting token sequence contains no illegal tokens,
|
||||
// a client may not assume that no error occurred. Instead it
|
||||
// must check the scanner's ErrorCount or the number of calls
|
||||
// of the error handler, if there was one installed.
|
||||
//
|
||||
// Scan adds line information to the file added to the file
|
||||
// set with Init. Token positions are relative to that file
|
||||
// and thus relative to the file set.
|
||||
//
|
||||
func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) {
|
||||
scanAgain:
|
||||
s.skipWhitespace()
|
||||
|
||||
// current token start
|
||||
pos = s.file.Pos(s.offset)
|
||||
|
||||
// determine token value
|
||||
switch ch := s.ch; {
|
||||
case s.nextVal:
|
||||
lit = s.scanValString()
|
||||
tok = token.STRING
|
||||
s.nextVal = false
|
||||
case isLetter(ch):
|
||||
lit = s.scanIdentifier()
|
||||
tok = token.IDENT
|
||||
default:
|
||||
s.next() // always make progress
|
||||
switch ch {
|
||||
case -1:
|
||||
tok = token.EOF
|
||||
case '\n':
|
||||
tok = token.EOL
|
||||
case '"':
|
||||
tok = token.STRING
|
||||
lit = s.scanString()
|
||||
case '[':
|
||||
tok = token.LBRACK
|
||||
case ']':
|
||||
tok = token.RBRACK
|
||||
case ';', '#':
|
||||
// comment
|
||||
lit = s.scanComment()
|
||||
if s.mode&ScanComments == 0 {
|
||||
// skip comment
|
||||
goto scanAgain
|
||||
}
|
||||
tok = token.COMMENT
|
||||
case '=':
|
||||
tok = token.ASSIGN
|
||||
s.nextVal = true
|
||||
default:
|
||||
s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch))
|
||||
tok = token.ILLEGAL
|
||||
lit = string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
417
vendor/gopkg.in/gcfg.v1/scanner/scanner_test.go
generated
vendored
Normal file
417
vendor/gopkg.in/gcfg.v1/scanner/scanner_test.go
generated
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
import (
|
||||
"gopkg.in/gcfg.v1/token"
|
||||
)
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
const /* class */ (
|
||||
special = iota
|
||||
literal
|
||||
operator
|
||||
)
|
||||
|
||||
func tokenclass(tok token.Token) int {
|
||||
switch {
|
||||
case tok.IsLiteral():
|
||||
return literal
|
||||
case tok.IsOperator():
|
||||
return operator
|
||||
}
|
||||
return special
|
||||
}
|
||||
|
||||
type elt struct {
|
||||
tok token.Token
|
||||
lit string
|
||||
class int
|
||||
pre string
|
||||
suf string
|
||||
}
|
||||
|
||||
var tokens = [...]elt{
|
||||
// Special tokens
|
||||
{token.COMMENT, "; a comment", special, "", "\n"},
|
||||
{token.COMMENT, "# a comment", special, "", "\n"},
|
||||
|
||||
// Operators and delimiters
|
||||
{token.ASSIGN, "=", operator, "", "value"},
|
||||
{token.LBRACK, "[", operator, "", ""},
|
||||
{token.RBRACK, "]", operator, "", ""},
|
||||
{token.EOL, "\n", operator, "", ""},
|
||||
|
||||
// Identifiers
|
||||
{token.IDENT, "foobar", literal, "", ""},
|
||||
{token.IDENT, "a۰۱۸", literal, "", ""},
|
||||
{token.IDENT, "foo६४", literal, "", ""},
|
||||
{token.IDENT, "bar9876", literal, "", ""},
|
||||
{token.IDENT, "foo-bar", literal, "", ""},
|
||||
{token.IDENT, "foo", literal, ";\n", ""},
|
||||
// String literals (subsection names)
|
||||
{token.STRING, `"foobar"`, literal, "", ""},
|
||||
{token.STRING, `"\""`, literal, "", ""},
|
||||
// String literals (values)
|
||||
{token.STRING, `"\n"`, literal, "=", ""},
|
||||
{token.STRING, `"foobar"`, literal, "=", ""},
|
||||
{token.STRING, `"foo\nbar"`, literal, "=", ""},
|
||||
{token.STRING, `"foo\"bar"`, literal, "=", ""},
|
||||
{token.STRING, `"foo\\bar"`, literal, "=", ""},
|
||||
{token.STRING, `"foobar"`, literal, "=", ""},
|
||||
{token.STRING, `"foobar"`, literal, "= ", ""},
|
||||
{token.STRING, `"foobar"`, literal, "=", "\n"},
|
||||
{token.STRING, `"foobar"`, literal, "=", ";"},
|
||||
{token.STRING, `"foobar"`, literal, "=", " ;"},
|
||||
{token.STRING, `"foobar"`, literal, "=", "#"},
|
||||
{token.STRING, `"foobar"`, literal, "=", " #"},
|
||||
{token.STRING, "foobar", literal, "=", ""},
|
||||
{token.STRING, "foobar", literal, "= ", ""},
|
||||
{token.STRING, "foobar", literal, "=", " "},
|
||||
{token.STRING, `"foo" "bar"`, literal, "=", " "},
|
||||
{token.STRING, "foo\\\nbar", literal, "=", ""},
|
||||
{token.STRING, "foo\\\r\nbar", literal, "=", ""},
|
||||
}
|
||||
|
||||
const whitespace = " \t \n\n\n" // to separate tokens
|
||||
|
||||
var source = func() []byte {
|
||||
var src []byte
|
||||
for _, t := range tokens {
|
||||
src = append(src, t.pre...)
|
||||
src = append(src, t.lit...)
|
||||
src = append(src, t.suf...)
|
||||
src = append(src, whitespace...)
|
||||
}
|
||||
return src
|
||||
}()
|
||||
|
||||
func newlineCount(s string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '\n' {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func checkPos(t *testing.T, lit string, p token.Pos, expected token.Position) {
|
||||
pos := fset.Position(p)
|
||||
if pos.Filename != expected.Filename {
|
||||
t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename)
|
||||
}
|
||||
if pos.Offset != expected.Offset {
|
||||
t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset)
|
||||
}
|
||||
if pos.Line != expected.Line {
|
||||
t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line)
|
||||
}
|
||||
if pos.Column != expected.Column {
|
||||
t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that calling Scan() provides the correct results.
|
||||
func TestScan(t *testing.T) {
|
||||
// make source
|
||||
src_linecount := newlineCount(string(source))
|
||||
whitespace_linecount := newlineCount(whitespace)
|
||||
|
||||
index := 0
|
||||
|
||||
// error handler
|
||||
eh := func(_ token.Position, msg string) {
|
||||
t.Errorf("%d: error handler called (msg = %s)", index, msg)
|
||||
}
|
||||
|
||||
// verify scan
|
||||
var s Scanner
|
||||
s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh, ScanComments)
|
||||
// epos is the expected position
|
||||
epos := token.Position{
|
||||
Filename: "",
|
||||
Offset: 0,
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
}
|
||||
for {
|
||||
pos, tok, lit := s.Scan()
|
||||
if lit == "" {
|
||||
// no literal value for non-literal tokens
|
||||
lit = tok.String()
|
||||
}
|
||||
e := elt{token.EOF, "", special, "", ""}
|
||||
if index < len(tokens) {
|
||||
e = tokens[index]
|
||||
}
|
||||
if tok == token.EOF {
|
||||
lit = "<EOF>"
|
||||
epos.Line = src_linecount
|
||||
epos.Column = 2
|
||||
}
|
||||
if e.pre != "" && strings.ContainsRune("=;#", rune(e.pre[0])) {
|
||||
epos.Column = 1
|
||||
checkPos(t, lit, pos, epos)
|
||||
var etok token.Token
|
||||
if e.pre[0] == '=' {
|
||||
etok = token.ASSIGN
|
||||
} else {
|
||||
etok = token.COMMENT
|
||||
}
|
||||
if tok != etok {
|
||||
t.Errorf("bad token for %q: got %q, expected %q", lit, tok, etok)
|
||||
}
|
||||
pos, tok, lit = s.Scan()
|
||||
}
|
||||
epos.Offset += len(e.pre)
|
||||
if tok != token.EOF {
|
||||
epos.Column = 1 + len(e.pre)
|
||||
}
|
||||
if e.pre != "" && e.pre[len(e.pre)-1] == '\n' {
|
||||
epos.Offset--
|
||||
epos.Column--
|
||||
checkPos(t, lit, pos, epos)
|
||||
if tok != token.EOL {
|
||||
t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.EOL)
|
||||
}
|
||||
epos.Line++
|
||||
epos.Offset++
|
||||
epos.Column = 1
|
||||
pos, tok, lit = s.Scan()
|
||||
}
|
||||
checkPos(t, lit, pos, epos)
|
||||
if tok != e.tok {
|
||||
t.Errorf("bad token for %q: got %q, expected %q", lit, tok, e.tok)
|
||||
}
|
||||
if e.tok.IsLiteral() {
|
||||
// no CRs in value string literals
|
||||
elit := e.lit
|
||||
if strings.ContainsRune(e.pre, '=') {
|
||||
elit = string(stripCR([]byte(elit)))
|
||||
epos.Offset += len(e.lit) - len(lit) // correct position
|
||||
}
|
||||
if lit != elit {
|
||||
t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit)
|
||||
}
|
||||
}
|
||||
if tokenclass(tok) != e.class {
|
||||
t.Errorf("bad class for %q: got %d, expected %d", lit, tokenclass(tok), e.class)
|
||||
}
|
||||
epos.Offset += len(lit) + len(e.suf) + len(whitespace)
|
||||
epos.Line += newlineCount(lit) + newlineCount(e.suf) + whitespace_linecount
|
||||
index++
|
||||
if tok == token.EOF {
|
||||
break
|
||||
}
|
||||
if e.suf == "value" {
|
||||
pos, tok, lit = s.Scan()
|
||||
if tok != token.STRING {
|
||||
t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.STRING)
|
||||
}
|
||||
} else if strings.ContainsRune(e.suf, ';') || strings.ContainsRune(e.suf, '#') {
|
||||
pos, tok, lit = s.Scan()
|
||||
if tok != token.COMMENT {
|
||||
t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.COMMENT)
|
||||
}
|
||||
}
|
||||
// skip EOLs
|
||||
for i := 0; i < whitespace_linecount+newlineCount(e.suf); i++ {
|
||||
pos, tok, lit = s.Scan()
|
||||
if tok != token.EOL {
|
||||
t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.EOL)
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.ErrorCount != 0 {
|
||||
t.Errorf("found %d errors", s.ErrorCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanValStringEOF(t *testing.T) {
|
||||
var s Scanner
|
||||
src := "= value"
|
||||
f := fset.AddFile("src", fset.Base(), len(src))
|
||||
s.Init(f, []byte(src), nil, 0)
|
||||
s.Scan() // =
|
||||
s.Scan() // value
|
||||
_, tok, _ := s.Scan() // EOF
|
||||
if tok != token.EOF {
|
||||
t.Errorf("bad token: got %s, expected %s", tok, token.EOF)
|
||||
}
|
||||
if s.ErrorCount > 0 {
|
||||
t.Error("scanning error")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that initializing the same scanner more then once works correctly.
|
||||
func TestInit(t *testing.T) {
|
||||
var s Scanner
|
||||
|
||||
// 1st init
|
||||
src1 := "\nname = value"
|
||||
f1 := fset.AddFile("src1", fset.Base(), len(src1))
|
||||
s.Init(f1, []byte(src1), nil, 0)
|
||||
if f1.Size() != len(src1) {
|
||||
t.Errorf("bad file size: got %d, expected %d", f1.Size(), len(src1))
|
||||
}
|
||||
s.Scan() // \n
|
||||
s.Scan() // name
|
||||
_, tok, _ := s.Scan() // =
|
||||
if tok != token.ASSIGN {
|
||||
t.Errorf("bad token: got %s, expected %s", tok, token.ASSIGN)
|
||||
}
|
||||
|
||||
// 2nd init
|
||||
src2 := "[section]"
|
||||
f2 := fset.AddFile("src2", fset.Base(), len(src2))
|
||||
s.Init(f2, []byte(src2), nil, 0)
|
||||
if f2.Size() != len(src2) {
|
||||
t.Errorf("bad file size: got %d, expected %d", f2.Size(), len(src2))
|
||||
}
|
||||
_, tok, _ = s.Scan() // [
|
||||
if tok != token.LBRACK {
|
||||
t.Errorf("bad token: got %s, expected %s", tok, token.LBRACK)
|
||||
}
|
||||
|
||||
if s.ErrorCount != 0 {
|
||||
t.Errorf("found %d errors", s.ErrorCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdErrorHandler(t *testing.T) {
|
||||
const src = "@\n" + // illegal character, cause an error
|
||||
"@ @\n" // two errors on the same line
|
||||
|
||||
var list ErrorList
|
||||
eh := func(pos token.Position, msg string) { list.Add(pos, msg) }
|
||||
|
||||
var s Scanner
|
||||
s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), eh, 0)
|
||||
for {
|
||||
if _, tok, _ := s.Scan(); tok == token.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) != s.ErrorCount {
|
||||
t.Errorf("found %d errors, expected %d", len(list), s.ErrorCount)
|
||||
}
|
||||
|
||||
if len(list) != 3 {
|
||||
t.Errorf("found %d raw errors, expected 3", len(list))
|
||||
PrintError(os.Stderr, list)
|
||||
}
|
||||
|
||||
list.Sort()
|
||||
if len(list) != 3 {
|
||||
t.Errorf("found %d sorted errors, expected 3", len(list))
|
||||
PrintError(os.Stderr, list)
|
||||
}
|
||||
|
||||
list.RemoveMultiples()
|
||||
if len(list) != 2 {
|
||||
t.Errorf("found %d one-per-line errors, expected 2", len(list))
|
||||
PrintError(os.Stderr, list)
|
||||
}
|
||||
}
|
||||
|
||||
type errorCollector struct {
|
||||
cnt int // number of errors encountered
|
||||
msg string // last error message encountered
|
||||
pos token.Position // last error position encountered
|
||||
}
|
||||
|
||||
func checkError(t *testing.T, src string, tok token.Token, pos int, err string) {
|
||||
var s Scanner
|
||||
var h errorCollector
|
||||
eh := func(pos token.Position, msg string) {
|
||||
h.cnt++
|
||||
h.msg = msg
|
||||
h.pos = pos
|
||||
}
|
||||
s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), eh, ScanComments)
|
||||
if src[0] == '=' {
|
||||
_, _, _ = s.Scan()
|
||||
}
|
||||
_, tok0, _ := s.Scan()
|
||||
_, tok1, _ := s.Scan()
|
||||
if tok0 != tok {
|
||||
t.Errorf("%q: got %s, expected %s", src, tok0, tok)
|
||||
}
|
||||
if tok1 != token.EOF {
|
||||
t.Errorf("%q: got %s, expected EOF", src, tok1)
|
||||
}
|
||||
cnt := 0
|
||||
if err != "" {
|
||||
cnt = 1
|
||||
}
|
||||
if h.cnt != cnt {
|
||||
t.Errorf("%q: got cnt %d, expected %d", src, h.cnt, cnt)
|
||||
}
|
||||
if h.msg != err {
|
||||
t.Errorf("%q: got msg %q, expected %q", src, h.msg, err)
|
||||
}
|
||||
if h.pos.Offset != pos {
|
||||
t.Errorf("%q: got offset %d, expected %d", src, h.pos.Offset, pos)
|
||||
}
|
||||
}
|
||||
|
||||
var errors = []struct {
|
||||
src string
|
||||
tok token.Token
|
||||
pos int
|
||||
err string
|
||||
}{
|
||||
{"\a", token.ILLEGAL, 0, "illegal character U+0007"},
|
||||
{"/", token.ILLEGAL, 0, "illegal character U+002F '/'"},
|
||||
{"_", token.ILLEGAL, 0, "illegal character U+005F '_'"},
|
||||
{`…`, token.ILLEGAL, 0, "illegal character U+2026 '…'"},
|
||||
{`""`, token.STRING, 0, ""},
|
||||
{`"`, token.STRING, 0, "string not terminated"},
|
||||
{"\"\n", token.STRING, 0, "string not terminated"},
|
||||
{`="`, token.STRING, 1, "string not terminated"},
|
||||
{"=\"\n", token.STRING, 1, "string not terminated"},
|
||||
{"=\\", token.STRING, 1, "unquoted '\\' must be followed by new line"},
|
||||
{"=\\\r", token.STRING, 1, "unquoted '\\' must be followed by new line"},
|
||||
{`"\z"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\a"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\b"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\f"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\r"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\t"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\v"`, token.STRING, 2, "unknown escape sequence"},
|
||||
{`"\0"`, token.STRING, 2, "unknown escape sequence"},
|
||||
}
|
||||
|
||||
func TestScanErrors(t *testing.T) {
|
||||
for _, e := range errors {
|
||||
checkError(t, e.src, e.tok, e.pos, e.err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkScan(b *testing.B) {
|
||||
b.StopTimer()
|
||||
fset := token.NewFileSet()
|
||||
file := fset.AddFile("", fset.Base(), len(source))
|
||||
var s Scanner
|
||||
b.StartTimer()
|
||||
for i := b.N - 1; i >= 0; i-- {
|
||||
s.Init(file, source, nil, ScanComments)
|
||||
for {
|
||||
_, tok, _ := s.Scan()
|
||||
if tok == token.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
293
vendor/gopkg.in/gcfg.v1/set.go
generated
vendored
Normal file
293
vendor/gopkg.in/gcfg.v1/set.go
generated
vendored
Normal file
@ -0,0 +1,293 @@
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gopkg.in/gcfg.v1/types"
|
||||
)
|
||||
|
||||
type tag struct {
|
||||
ident string
|
||||
intMode string
|
||||
}
|
||||
|
||||
func newTag(ts string) tag {
|
||||
t := tag{}
|
||||
s := strings.Split(ts, ",")
|
||||
t.ident = s[0]
|
||||
for _, tse := range s[1:] {
|
||||
if strings.HasPrefix(tse, "int=") {
|
||||
t.intMode = tse[len("int="):]
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func fieldFold(v reflect.Value, name string) (reflect.Value, tag) {
|
||||
var n string
|
||||
r0, _ := utf8.DecodeRuneInString(name)
|
||||
if unicode.IsLetter(r0) && !unicode.IsLower(r0) && !unicode.IsUpper(r0) {
|
||||
n = "X"
|
||||
}
|
||||
n += strings.Replace(name, "-", "_", -1)
|
||||
f, ok := v.Type().FieldByNameFunc(func(fieldName string) bool {
|
||||
if !v.FieldByName(fieldName).CanSet() {
|
||||
return false
|
||||
}
|
||||
f, _ := v.Type().FieldByName(fieldName)
|
||||
t := newTag(f.Tag.Get("gcfg"))
|
||||
if t.ident != "" {
|
||||
return strings.EqualFold(t.ident, name)
|
||||
}
|
||||
return strings.EqualFold(n, fieldName)
|
||||
})
|
||||
if !ok {
|
||||
return reflect.Value{}, tag{}
|
||||
}
|
||||
return v.FieldByName(f.Name), newTag(f.Tag.Get("gcfg"))
|
||||
}
|
||||
|
||||
type setter func(destp interface{}, blank bool, val string, t tag) error
|
||||
|
||||
var errUnsupportedType = fmt.Errorf("unsupported type")
|
||||
var errBlankUnsupported = fmt.Errorf("blank value not supported for type")
|
||||
|
||||
var setters = []setter{
|
||||
typeSetter, textUnmarshalerSetter, kindSetter, scanSetter,
|
||||
}
|
||||
|
||||
func textUnmarshalerSetter(d interface{}, blank bool, val string, t tag) error {
|
||||
dtu, ok := d.(textUnmarshaler)
|
||||
if !ok {
|
||||
return errUnsupportedType
|
||||
}
|
||||
if blank {
|
||||
return errBlankUnsupported
|
||||
}
|
||||
return dtu.UnmarshalText([]byte(val))
|
||||
}
|
||||
|
||||
func boolSetter(d interface{}, blank bool, val string, t tag) error {
|
||||
if blank {
|
||||
reflect.ValueOf(d).Elem().Set(reflect.ValueOf(true))
|
||||
return nil
|
||||
}
|
||||
b, err := types.ParseBool(val)
|
||||
if err == nil {
|
||||
reflect.ValueOf(d).Elem().Set(reflect.ValueOf(b))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func intMode(mode string) types.IntMode {
|
||||
var m types.IntMode
|
||||
if strings.ContainsAny(mode, "dD") {
|
||||
m |= types.Dec
|
||||
}
|
||||
if strings.ContainsAny(mode, "hH") {
|
||||
m |= types.Hex
|
||||
}
|
||||
if strings.ContainsAny(mode, "oO") {
|
||||
m |= types.Oct
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
var typeModes = map[reflect.Type]types.IntMode{
|
||||
reflect.TypeOf(int(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(int8(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(int16(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(int32(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(int64(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(uint(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(uint8(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(uint16(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(uint32(0)): types.Dec | types.Hex,
|
||||
reflect.TypeOf(uint64(0)): types.Dec | types.Hex,
|
||||
// use default mode (allow dec/hex/oct) for uintptr type
|
||||
reflect.TypeOf(big.Int{}): types.Dec | types.Hex,
|
||||
}
|
||||
|
||||
func intModeDefault(t reflect.Type) types.IntMode {
|
||||
m, ok := typeModes[t]
|
||||
if !ok {
|
||||
m = types.Dec | types.Hex | types.Oct
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func intSetter(d interface{}, blank bool, val string, t tag) error {
|
||||
if blank {
|
||||
return errBlankUnsupported
|
||||
}
|
||||
mode := intMode(t.intMode)
|
||||
if mode == 0 {
|
||||
mode = intModeDefault(reflect.TypeOf(d).Elem())
|
||||
}
|
||||
return types.ParseInt(d, val, mode)
|
||||
}
|
||||
|
||||
func stringSetter(d interface{}, blank bool, val string, t tag) error {
|
||||
if blank {
|
||||
return errBlankUnsupported
|
||||
}
|
||||
dsp, ok := d.(*string)
|
||||
if !ok {
|
||||
return errUnsupportedType
|
||||
}
|
||||
*dsp = val
|
||||
return nil
|
||||
}
|
||||
|
||||
var kindSetters = map[reflect.Kind]setter{
|
||||
reflect.String: stringSetter,
|
||||
reflect.Bool: boolSetter,
|
||||
reflect.Int: intSetter,
|
||||
reflect.Int8: intSetter,
|
||||
reflect.Int16: intSetter,
|
||||
reflect.Int32: intSetter,
|
||||
reflect.Int64: intSetter,
|
||||
reflect.Uint: intSetter,
|
||||
reflect.Uint8: intSetter,
|
||||
reflect.Uint16: intSetter,
|
||||
reflect.Uint32: intSetter,
|
||||
reflect.Uint64: intSetter,
|
||||
reflect.Uintptr: intSetter,
|
||||
}
|
||||
|
||||
var typeSetters = map[reflect.Type]setter{
|
||||
reflect.TypeOf(big.Int{}): intSetter,
|
||||
}
|
||||
|
||||
func typeSetter(d interface{}, blank bool, val string, tt tag) error {
|
||||
t := reflect.ValueOf(d).Type().Elem()
|
||||
setter, ok := typeSetters[t]
|
||||
if !ok {
|
||||
return errUnsupportedType
|
||||
}
|
||||
return setter(d, blank, val, tt)
|
||||
}
|
||||
|
||||
func kindSetter(d interface{}, blank bool, val string, tt tag) error {
|
||||
k := reflect.ValueOf(d).Type().Elem().Kind()
|
||||
setter, ok := kindSetters[k]
|
||||
if !ok {
|
||||
return errUnsupportedType
|
||||
}
|
||||
return setter(d, blank, val, tt)
|
||||
}
|
||||
|
||||
func scanSetter(d interface{}, blank bool, val string, tt tag) error {
|
||||
if blank {
|
||||
return errBlankUnsupported
|
||||
}
|
||||
return types.ScanFully(d, val, 'v')
|
||||
}
|
||||
|
||||
func set(cfg interface{}, sect, sub, name string, blank bool, value string) error {
|
||||
vPCfg := reflect.ValueOf(cfg)
|
||||
if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("config must be a pointer to a struct"))
|
||||
}
|
||||
vCfg := vPCfg.Elem()
|
||||
vSect, _ := fieldFold(vCfg, sect)
|
||||
if !vSect.IsValid() {
|
||||
return fmt.Errorf("invalid section: section %q", sect)
|
||||
}
|
||||
if vSect.Kind() == reflect.Map {
|
||||
vst := vSect.Type()
|
||||
if vst.Key().Kind() != reflect.String ||
|
||||
vst.Elem().Kind() != reflect.Ptr ||
|
||||
vst.Elem().Elem().Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("map field for section must have string keys and "+
|
||||
" pointer-to-struct values: section %q", sect))
|
||||
}
|
||||
if vSect.IsNil() {
|
||||
vSect.Set(reflect.MakeMap(vst))
|
||||
}
|
||||
k := reflect.ValueOf(sub)
|
||||
pv := vSect.MapIndex(k)
|
||||
if !pv.IsValid() {
|
||||
vType := vSect.Type().Elem().Elem()
|
||||
pv = reflect.New(vType)
|
||||
vSect.SetMapIndex(k, pv)
|
||||
}
|
||||
vSect = pv.Elem()
|
||||
} else if vSect.Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("field for section must be a map or a struct: "+
|
||||
"section %q", sect))
|
||||
} else if sub != "" {
|
||||
return fmt.Errorf("invalid subsection: "+
|
||||
"section %q subsection %q", sect, sub)
|
||||
}
|
||||
// Empty name is a special value, meaning that only the
|
||||
// section/subsection object is to be created, with no values set.
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
vVar, t := fieldFold(vSect, name)
|
||||
if !vVar.IsValid() {
|
||||
return fmt.Errorf("invalid variable: "+
|
||||
"section %q subsection %q variable %q", sect, sub, name)
|
||||
}
|
||||
// vVal is either single-valued var, or newly allocated value within multi-valued var
|
||||
var vVal reflect.Value
|
||||
// multi-value if unnamed slice type
|
||||
isMulti := vVar.Type().Name() == "" && vVar.Kind() == reflect.Slice ||
|
||||
vVar.Type().Name() == "" && vVar.Kind() == reflect.Ptr && vVar.Type().Elem().Name() == "" && vVar.Type().Elem().Kind() == reflect.Slice
|
||||
if isMulti && vVar.Kind() == reflect.Ptr {
|
||||
if vVar.IsNil() {
|
||||
vVar.Set(reflect.New(vVar.Type().Elem()))
|
||||
}
|
||||
vVar = vVar.Elem()
|
||||
}
|
||||
if isMulti && blank {
|
||||
vVar.Set(reflect.Zero(vVar.Type()))
|
||||
return nil
|
||||
}
|
||||
if isMulti {
|
||||
vVal = reflect.New(vVar.Type().Elem()).Elem()
|
||||
} else {
|
||||
vVal = vVar
|
||||
}
|
||||
isDeref := vVal.Type().Name() == "" && vVal.Type().Kind() == reflect.Ptr
|
||||
isNew := isDeref && vVal.IsNil()
|
||||
// vAddr is address of value to set (dereferenced & allocated as needed)
|
||||
var vAddr reflect.Value
|
||||
switch {
|
||||
case isNew:
|
||||
vAddr = reflect.New(vVal.Type().Elem())
|
||||
case isDeref && !isNew:
|
||||
vAddr = vVal
|
||||
default:
|
||||
vAddr = vVal.Addr()
|
||||
}
|
||||
vAddrI := vAddr.Interface()
|
||||
err, ok := error(nil), false
|
||||
for _, s := range setters {
|
||||
err = s(vAddrI, blank, value, t)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
if err != errUnsupportedType {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
// in case all setters returned errUnsupportedType
|
||||
return err
|
||||
}
|
||||
if isNew { // set reference if it was dereferenced and newly allocated
|
||||
vVal.Set(vAddr)
|
||||
}
|
||||
if isMulti { // append if multi-valued
|
||||
vVar.Set(reflect.Append(vVar, vVal))
|
||||
}
|
||||
return nil
|
||||
}
|
3
vendor/gopkg.in/gcfg.v1/testdata/gcfg_test.gcfg
generated
vendored
Normal file
3
vendor/gopkg.in/gcfg.v1/testdata/gcfg_test.gcfg
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
; Comment line
|
||||
[section]
|
||||
name=value # comment
|
3
vendor/gopkg.in/gcfg.v1/testdata/gcfg_unicode_test.gcfg
generated
vendored
Normal file
3
vendor/gopkg.in/gcfg.v1/testdata/gcfg_unicode_test.gcfg
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
; Comment line
|
||||
[甲]
|
||||
乙=丙 # comment
|
435
vendor/gopkg.in/gcfg.v1/token/position.go
generated
vendored
Normal file
435
vendor/gopkg.in/gcfg.v1/token/position.go
generated
vendored
Normal file
@ -0,0 +1,435 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// TODO(gri) consider making this a separate package outside the go directory.
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Positions
|
||||
|
||||
// Position describes an arbitrary source position
|
||||
// including the file, line, and column location.
|
||||
// A Position is valid if the line number is > 0.
|
||||
//
|
||||
type Position struct {
|
||||
Filename string // filename, if any
|
||||
Offset int // offset, starting at 0
|
||||
Line int // line number, starting at 1
|
||||
Column int // column number, starting at 1 (character count)
|
||||
}
|
||||
|
||||
// IsValid returns true if the position is valid.
|
||||
func (pos *Position) IsValid() bool { return pos.Line > 0 }
|
||||
|
||||
// String returns a string in one of several forms:
|
||||
//
|
||||
// file:line:column valid position with file name
|
||||
// line:column valid position without file name
|
||||
// file invalid position with file name
|
||||
// - invalid position without file name
|
||||
//
|
||||
func (pos Position) String() string {
|
||||
s := pos.Filename
|
||||
if pos.IsValid() {
|
||||
if s != "" {
|
||||
s += ":"
|
||||
}
|
||||
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
||||
}
|
||||
if s == "" {
|
||||
s = "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pos is a compact encoding of a source position within a file set.
|
||||
// It can be converted into a Position for a more convenient, but much
|
||||
// larger, representation.
|
||||
//
|
||||
// The Pos value for a given file is a number in the range [base, base+size],
|
||||
// where base and size are specified when adding the file to the file set via
|
||||
// AddFile.
|
||||
//
|
||||
// To create the Pos value for a specific source offset, first add
|
||||
// the respective file to the current file set (via FileSet.AddFile)
|
||||
// and then call File.Pos(offset) for that file. Given a Pos value p
|
||||
// for a specific file set fset, the corresponding Position value is
|
||||
// obtained by calling fset.Position(p).
|
||||
//
|
||||
// Pos values can be compared directly with the usual comparison operators:
|
||||
// If two Pos values p and q are in the same file, comparing p and q is
|
||||
// equivalent to comparing the respective source file offsets. If p and q
|
||||
// are in different files, p < q is true if the file implied by p was added
|
||||
// to the respective file set before the file implied by q.
|
||||
//
|
||||
type Pos int
|
||||
|
||||
// The zero value for Pos is NoPos; there is no file and line information
|
||||
// associated with it, and NoPos().IsValid() is false. NoPos is always
|
||||
// smaller than any other Pos value. The corresponding Position value
|
||||
// for NoPos is the zero value for Position.
|
||||
//
|
||||
const NoPos Pos = 0
|
||||
|
||||
// IsValid returns true if the position is valid.
|
||||
func (p Pos) IsValid() bool {
|
||||
return p != NoPos
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// File
|
||||
|
||||
// A File is a handle for a file belonging to a FileSet.
|
||||
// A File has a name, size, and line offset table.
|
||||
//
|
||||
type File struct {
|
||||
set *FileSet
|
||||
name string // file name as provided to AddFile
|
||||
base int // Pos value range for this file is [base...base+size]
|
||||
size int // file size as provided to AddFile
|
||||
|
||||
// lines and infos are protected by set.mutex
|
||||
lines []int
|
||||
infos []lineInfo
|
||||
}
|
||||
|
||||
// Name returns the file name of file f as registered with AddFile.
|
||||
func (f *File) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Base returns the base offset of file f as registered with AddFile.
|
||||
func (f *File) Base() int {
|
||||
return f.base
|
||||
}
|
||||
|
||||
// Size returns the size of file f as registered with AddFile.
|
||||
func (f *File) Size() int {
|
||||
return f.size
|
||||
}
|
||||
|
||||
// LineCount returns the number of lines in file f.
|
||||
func (f *File) LineCount() int {
|
||||
f.set.mutex.RLock()
|
||||
n := len(f.lines)
|
||||
f.set.mutex.RUnlock()
|
||||
return n
|
||||
}
|
||||
|
||||
// AddLine adds the line offset for a new line.
|
||||
// The line offset must be larger than the offset for the previous line
|
||||
// and smaller than the file size; otherwise the line offset is ignored.
|
||||
//
|
||||
func (f *File) AddLine(offset int) {
|
||||
f.set.mutex.Lock()
|
||||
if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size {
|
||||
f.lines = append(f.lines, offset)
|
||||
}
|
||||
f.set.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetLines sets the line offsets for a file and returns true if successful.
|
||||
// The line offsets are the offsets of the first character of each line;
|
||||
// for instance for the content "ab\nc\n" the line offsets are {0, 3}.
|
||||
// An empty file has an empty line offset table.
|
||||
// Each line offset must be larger than the offset for the previous line
|
||||
// and smaller than the file size; otherwise SetLines fails and returns
|
||||
// false.
|
||||
//
|
||||
func (f *File) SetLines(lines []int) bool {
|
||||
// verify validity of lines table
|
||||
size := f.size
|
||||
for i, offset := range lines {
|
||||
if i > 0 && offset <= lines[i-1] || size <= offset {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// set lines table
|
||||
f.set.mutex.Lock()
|
||||
f.lines = lines
|
||||
f.set.mutex.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// SetLinesForContent sets the line offsets for the given file content.
|
||||
func (f *File) SetLinesForContent(content []byte) {
|
||||
var lines []int
|
||||
line := 0
|
||||
for offset, b := range content {
|
||||
if line >= 0 {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
line = -1
|
||||
if b == '\n' {
|
||||
line = offset + 1
|
||||
}
|
||||
}
|
||||
|
||||
// set lines table
|
||||
f.set.mutex.Lock()
|
||||
f.lines = lines
|
||||
f.set.mutex.Unlock()
|
||||
}
|
||||
|
||||
// A lineInfo object describes alternative file and line number
|
||||
// information (such as provided via a //line comment in a .go
|
||||
// file) for a given file offset.
|
||||
type lineInfo struct {
|
||||
// fields are exported to make them accessible to gob
|
||||
Offset int
|
||||
Filename string
|
||||
Line int
|
||||
}
|
||||
|
||||
// AddLineInfo adds alternative file and line number information for
|
||||
// a given file offset. The offset must be larger than the offset for
|
||||
// the previously added alternative line info and smaller than the
|
||||
// file size; otherwise the information is ignored.
|
||||
//
|
||||
// AddLineInfo is typically used to register alternative position
|
||||
// information for //line filename:line comments in source files.
|
||||
//
|
||||
func (f *File) AddLineInfo(offset int, filename string, line int) {
|
||||
f.set.mutex.Lock()
|
||||
if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size {
|
||||
f.infos = append(f.infos, lineInfo{offset, filename, line})
|
||||
}
|
||||
f.set.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Pos returns the Pos value for the given file offset;
|
||||
// the offset must be <= f.Size().
|
||||
// f.Pos(f.Offset(p)) == p.
|
||||
//
|
||||
func (f *File) Pos(offset int) Pos {
|
||||
if offset > f.size {
|
||||
panic("illegal file offset")
|
||||
}
|
||||
return Pos(f.base + offset)
|
||||
}
|
||||
|
||||
// Offset returns the offset for the given file position p;
|
||||
// p must be a valid Pos value in that file.
|
||||
// f.Offset(f.Pos(offset)) == offset.
|
||||
//
|
||||
func (f *File) Offset(p Pos) int {
|
||||
if int(p) < f.base || int(p) > f.base+f.size {
|
||||
panic("illegal Pos value")
|
||||
}
|
||||
return int(p) - f.base
|
||||
}
|
||||
|
||||
// Line returns the line number for the given file position p;
|
||||
// p must be a Pos value in that file or NoPos.
|
||||
//
|
||||
func (f *File) Line(p Pos) int {
|
||||
// TODO(gri) this can be implemented much more efficiently
|
||||
return f.Position(p).Line
|
||||
}
|
||||
|
||||
func searchLineInfos(a []lineInfo, x int) int {
|
||||
return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1
|
||||
}
|
||||
|
||||
// info returns the file name, line, and column number for a file offset.
|
||||
func (f *File) info(offset int) (filename string, line, column int) {
|
||||
filename = f.name
|
||||
if i := searchInts(f.lines, offset); i >= 0 {
|
||||
line, column = i+1, offset-f.lines[i]+1
|
||||
}
|
||||
if len(f.infos) > 0 {
|
||||
// almost no files have extra line infos
|
||||
if i := searchLineInfos(f.infos, offset); i >= 0 {
|
||||
alt := &f.infos[i]
|
||||
filename = alt.Filename
|
||||
if i := searchInts(f.lines, alt.Offset); i >= 0 {
|
||||
line += alt.Line - i - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *File) position(p Pos) (pos Position) {
|
||||
offset := int(p) - f.base
|
||||
pos.Offset = offset
|
||||
pos.Filename, pos.Line, pos.Column = f.info(offset)
|
||||
return
|
||||
}
|
||||
|
||||
// Position returns the Position value for the given file position p;
|
||||
// p must be a Pos value in that file or NoPos.
|
||||
//
|
||||
func (f *File) Position(p Pos) (pos Position) {
|
||||
if p != NoPos {
|
||||
if int(p) < f.base || int(p) > f.base+f.size {
|
||||
panic("illegal Pos value")
|
||||
}
|
||||
pos = f.position(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// FileSet
|
||||
|
||||
// A FileSet represents a set of source files.
|
||||
// Methods of file sets are synchronized; multiple goroutines
|
||||
// may invoke them concurrently.
|
||||
//
|
||||
type FileSet struct {
|
||||
mutex sync.RWMutex // protects the file set
|
||||
base int // base offset for the next file
|
||||
files []*File // list of files in the order added to the set
|
||||
last *File // cache of last file looked up
|
||||
}
|
||||
|
||||
// NewFileSet creates a new file set.
|
||||
func NewFileSet() *FileSet {
|
||||
s := new(FileSet)
|
||||
s.base = 1 // 0 == NoPos
|
||||
return s
|
||||
}
|
||||
|
||||
// Base returns the minimum base offset that must be provided to
|
||||
// AddFile when adding the next file.
|
||||
//
|
||||
func (s *FileSet) Base() int {
|
||||
s.mutex.RLock()
|
||||
b := s.base
|
||||
s.mutex.RUnlock()
|
||||
return b
|
||||
|
||||
}
|
||||
|
||||
// AddFile adds a new file with a given filename, base offset, and file size
|
||||
// to the file set s and returns the file. Multiple files may have the same
|
||||
// name. The base offset must not be smaller than the FileSet's Base(), and
|
||||
// size must not be negative.
|
||||
//
|
||||
// Adding the file will set the file set's Base() value to base + size + 1
|
||||
// as the minimum base value for the next file. The following relationship
|
||||
// exists between a Pos value p for a given file offset offs:
|
||||
//
|
||||
// int(p) = base + offs
|
||||
//
|
||||
// with offs in the range [0, size] and thus p in the range [base, base+size].
|
||||
// For convenience, File.Pos may be used to create file-specific position
|
||||
// values from a file offset.
|
||||
//
|
||||
func (s *FileSet) AddFile(filename string, base, size int) *File {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
if base < s.base || size < 0 {
|
||||
panic("illegal base or size")
|
||||
}
|
||||
// base >= s.base && size >= 0
|
||||
f := &File{s, filename, base, size, []int{0}, nil}
|
||||
base += size + 1 // +1 because EOF also has a position
|
||||
if base < 0 {
|
||||
panic("token.Pos offset overflow (> 2G of source code in file set)")
|
||||
}
|
||||
// add the file to the file set
|
||||
s.base = base
|
||||
s.files = append(s.files, f)
|
||||
s.last = f
|
||||
return f
|
||||
}
|
||||
|
||||
// Iterate calls f for the files in the file set in the order they were added
|
||||
// until f returns false.
|
||||
//
|
||||
func (s *FileSet) Iterate(f func(*File) bool) {
|
||||
for i := 0; ; i++ {
|
||||
var file *File
|
||||
s.mutex.RLock()
|
||||
if i < len(s.files) {
|
||||
file = s.files[i]
|
||||
}
|
||||
s.mutex.RUnlock()
|
||||
if file == nil || !f(file) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func searchFiles(a []*File, x int) int {
|
||||
return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1
|
||||
}
|
||||
|
||||
func (s *FileSet) file(p Pos) *File {
|
||||
// common case: p is in last file
|
||||
if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size {
|
||||
return f
|
||||
}
|
||||
// p is not in last file - search all files
|
||||
if i := searchFiles(s.files, int(p)); i >= 0 {
|
||||
f := s.files[i]
|
||||
// f.base <= int(p) by definition of searchFiles
|
||||
if int(p) <= f.base+f.size {
|
||||
s.last = f
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// File returns the file that contains the position p.
|
||||
// If no such file is found (for instance for p == NoPos),
|
||||
// the result is nil.
|
||||
//
|
||||
func (s *FileSet) File(p Pos) (f *File) {
|
||||
if p != NoPos {
|
||||
s.mutex.RLock()
|
||||
f = s.file(p)
|
||||
s.mutex.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Position converts a Pos in the fileset into a general Position.
|
||||
func (s *FileSet) Position(p Pos) (pos Position) {
|
||||
if p != NoPos {
|
||||
s.mutex.RLock()
|
||||
if f := s.file(p); f != nil {
|
||||
pos = f.position(p)
|
||||
}
|
||||
s.mutex.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Helper functions
|
||||
|
||||
func searchInts(a []int, x int) int {
|
||||
// This function body is a manually inlined version of:
|
||||
//
|
||||
// return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1
|
||||
//
|
||||
// With better compiler optimizations, this may not be needed in the
|
||||
// future, but at the moment this change improves the go/printer
|
||||
// benchmark performance by ~30%. This has a direct impact on the
|
||||
// speed of gofmt and thus seems worthwhile (2011-04-29).
|
||||
// TODO(gri): Remove this when compilers have caught up.
|
||||
i, j := 0, len(a)
|
||||
for i < j {
|
||||
h := i + (j-i)/2 // avoid overflow when computing h
|
||||
// i ≤ h < j
|
||||
if a[h] <= x {
|
||||
i = h + 1
|
||||
} else {
|
||||
j = h
|
||||
}
|
||||
}
|
||||
return i - 1
|
||||
}
|
181
vendor/gopkg.in/gcfg.v1/token/position_test.go
generated
vendored
Normal file
181
vendor/gopkg.in/gcfg.v1/token/position_test.go
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func checkPos(t *testing.T, msg string, p, q Position) {
|
||||
if p.Filename != q.Filename {
|
||||
t.Errorf("%s: expected filename = %q; got %q", msg, q.Filename, p.Filename)
|
||||
}
|
||||
if p.Offset != q.Offset {
|
||||
t.Errorf("%s: expected offset = %d; got %d", msg, q.Offset, p.Offset)
|
||||
}
|
||||
if p.Line != q.Line {
|
||||
t.Errorf("%s: expected line = %d; got %d", msg, q.Line, p.Line)
|
||||
}
|
||||
if p.Column != q.Column {
|
||||
t.Errorf("%s: expected column = %d; got %d", msg, q.Column, p.Column)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoPos(t *testing.T) {
|
||||
if NoPos.IsValid() {
|
||||
t.Errorf("NoPos should not be valid")
|
||||
}
|
||||
var fset *FileSet
|
||||
checkPos(t, "nil NoPos", fset.Position(NoPos), Position{})
|
||||
fset = NewFileSet()
|
||||
checkPos(t, "fset NoPos", fset.Position(NoPos), Position{})
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
filename string
|
||||
source []byte // may be nil
|
||||
size int
|
||||
lines []int
|
||||
}{
|
||||
{"a", []byte{}, 0, []int{}},
|
||||
{"b", []byte("01234"), 5, []int{0}},
|
||||
{"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}},
|
||||
{"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}},
|
||||
{"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}},
|
||||
{"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}},
|
||||
{"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}},
|
||||
{"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}},
|
||||
}
|
||||
|
||||
func linecol(lines []int, offs int) (int, int) {
|
||||
prevLineOffs := 0
|
||||
for line, lineOffs := range lines {
|
||||
if offs < lineOffs {
|
||||
return line, offs - prevLineOffs + 1
|
||||
}
|
||||
prevLineOffs = lineOffs
|
||||
}
|
||||
return len(lines), offs - prevLineOffs + 1
|
||||
}
|
||||
|
||||
func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) {
|
||||
for offs := 0; offs < f.Size(); offs++ {
|
||||
p := f.Pos(offs)
|
||||
offs2 := f.Offset(p)
|
||||
if offs2 != offs {
|
||||
t.Errorf("%s, Offset: expected offset %d; got %d", f.Name(), offs, offs2)
|
||||
}
|
||||
line, col := linecol(lines, offs)
|
||||
msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p)
|
||||
checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col})
|
||||
checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col})
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestSource(size int, lines []int) []byte {
|
||||
src := make([]byte, size)
|
||||
for _, offs := range lines {
|
||||
if offs > 0 {
|
||||
src[offs-1] = '\n'
|
||||
}
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func TestPositions(t *testing.T) {
|
||||
const delta = 7 // a non-zero base offset increment
|
||||
fset := NewFileSet()
|
||||
for _, test := range tests {
|
||||
// verify consistency of test case
|
||||
if test.source != nil && len(test.source) != test.size {
|
||||
t.Errorf("%s: inconsistent test case: expected file size %d; got %d", test.filename, test.size, len(test.source))
|
||||
}
|
||||
|
||||
// add file and verify name and size
|
||||
f := fset.AddFile(test.filename, fset.Base()+delta, test.size)
|
||||
if f.Name() != test.filename {
|
||||
t.Errorf("expected filename %q; got %q", test.filename, f.Name())
|
||||
}
|
||||
if f.Size() != test.size {
|
||||
t.Errorf("%s: expected file size %d; got %d", f.Name(), test.size, f.Size())
|
||||
}
|
||||
if fset.File(f.Pos(0)) != f {
|
||||
t.Errorf("%s: f.Pos(0) was not found in f", f.Name())
|
||||
}
|
||||
|
||||
// add lines individually and verify all positions
|
||||
for i, offset := range test.lines {
|
||||
f.AddLine(offset)
|
||||
if f.LineCount() != i+1 {
|
||||
t.Errorf("%s, AddLine: expected line count %d; got %d", f.Name(), i+1, f.LineCount())
|
||||
}
|
||||
// adding the same offset again should be ignored
|
||||
f.AddLine(offset)
|
||||
if f.LineCount() != i+1 {
|
||||
t.Errorf("%s, AddLine: expected unchanged line count %d; got %d", f.Name(), i+1, f.LineCount())
|
||||
}
|
||||
verifyPositions(t, fset, f, test.lines[0:i+1])
|
||||
}
|
||||
|
||||
// add lines with SetLines and verify all positions
|
||||
if ok := f.SetLines(test.lines); !ok {
|
||||
t.Errorf("%s: SetLines failed", f.Name())
|
||||
}
|
||||
if f.LineCount() != len(test.lines) {
|
||||
t.Errorf("%s, SetLines: expected line count %d; got %d", f.Name(), len(test.lines), f.LineCount())
|
||||
}
|
||||
verifyPositions(t, fset, f, test.lines)
|
||||
|
||||
// add lines with SetLinesForContent and verify all positions
|
||||
src := test.source
|
||||
if src == nil {
|
||||
// no test source available - create one from scratch
|
||||
src = makeTestSource(test.size, test.lines)
|
||||
}
|
||||
f.SetLinesForContent(src)
|
||||
if f.LineCount() != len(test.lines) {
|
||||
t.Errorf("%s, SetLinesForContent: expected line count %d; got %d", f.Name(), len(test.lines), f.LineCount())
|
||||
}
|
||||
verifyPositions(t, fset, f, test.lines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLineInfo(t *testing.T) {
|
||||
fset := NewFileSet()
|
||||
f := fset.AddFile("foo", fset.Base(), 500)
|
||||
lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401}
|
||||
// add lines individually and provide alternative line information
|
||||
for _, offs := range lines {
|
||||
f.AddLine(offs)
|
||||
f.AddLineInfo(offs, "bar", 42)
|
||||
}
|
||||
// verify positions for all offsets
|
||||
for offs := 0; offs <= f.Size(); offs++ {
|
||||
p := f.Pos(offs)
|
||||
_, col := linecol(lines, offs)
|
||||
msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p)
|
||||
checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col})
|
||||
checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFiles(t *testing.T) {
|
||||
fset := NewFileSet()
|
||||
for i, test := range tests {
|
||||
fset.AddFile(test.filename, fset.Base(), test.size)
|
||||
j := 0
|
||||
fset.Iterate(func(f *File) bool {
|
||||
if f.Name() != tests[j].filename {
|
||||
t.Errorf("expected filename = %s; got %s", tests[j].filename, f.Name())
|
||||
}
|
||||
j++
|
||||
return true
|
||||
})
|
||||
if j != i+1 {
|
||||
t.Errorf("expected %d files; got %d", i+1, j)
|
||||
}
|
||||
}
|
||||
}
|
56
vendor/gopkg.in/gcfg.v1/token/serialize.go
generated
vendored
Normal file
56
vendor/gopkg.in/gcfg.v1/token/serialize.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package token
|
||||
|
||||
type serializedFile struct {
|
||||
// fields correspond 1:1 to fields with same (lower-case) name in File
|
||||
Name string
|
||||
Base int
|
||||
Size int
|
||||
Lines []int
|
||||
Infos []lineInfo
|
||||
}
|
||||
|
||||
type serializedFileSet struct {
|
||||
Base int
|
||||
Files []serializedFile
|
||||
}
|
||||
|
||||
// Read calls decode to deserialize a file set into s; s must not be nil.
|
||||
func (s *FileSet) Read(decode func(interface{}) error) error {
|
||||
var ss serializedFileSet
|
||||
if err := decode(&ss); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
s.base = ss.Base
|
||||
files := make([]*File, len(ss.Files))
|
||||
for i := 0; i < len(ss.Files); i++ {
|
||||
f := &ss.Files[i]
|
||||
files[i] = &File{s, f.Name, f.Base, f.Size, f.Lines, f.Infos}
|
||||
}
|
||||
s.files = files
|
||||
s.last = nil
|
||||
s.mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write calls encode to serialize the file set s.
|
||||
func (s *FileSet) Write(encode func(interface{}) error) error {
|
||||
var ss serializedFileSet
|
||||
|
||||
s.mutex.Lock()
|
||||
ss.Base = s.base
|
||||
files := make([]serializedFile, len(s.files))
|
||||
for i, f := range s.files {
|
||||
files[i] = serializedFile{f.name, f.base, f.size, f.lines, f.infos}
|
||||
}
|
||||
ss.Files = files
|
||||
s.mutex.Unlock()
|
||||
|
||||
return encode(ss)
|
||||
}
|
111
vendor/gopkg.in/gcfg.v1/token/serialize_test.go
generated
vendored
Normal file
111
vendor/gopkg.in/gcfg.v1/token/serialize_test.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// equal returns nil if p and q describe the same file set;
|
||||
// otherwise it returns an error describing the discrepancy.
|
||||
func equal(p, q *FileSet) error {
|
||||
if p == q {
|
||||
// avoid deadlock if p == q
|
||||
return nil
|
||||
}
|
||||
|
||||
// not strictly needed for the test
|
||||
p.mutex.Lock()
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if p.base != q.base {
|
||||
return fmt.Errorf("different bases: %d != %d", p.base, q.base)
|
||||
}
|
||||
|
||||
if len(p.files) != len(q.files) {
|
||||
return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files))
|
||||
}
|
||||
|
||||
for i, f := range p.files {
|
||||
g := q.files[i]
|
||||
if f.set != p {
|
||||
return fmt.Errorf("wrong fileset for %q", f.name)
|
||||
}
|
||||
if g.set != q {
|
||||
return fmt.Errorf("wrong fileset for %q", g.name)
|
||||
}
|
||||
if f.name != g.name {
|
||||
return fmt.Errorf("different filenames: %q != %q", f.name, g.name)
|
||||
}
|
||||
if f.base != g.base {
|
||||
return fmt.Errorf("different base for %q: %d != %d", f.name, f.base, g.base)
|
||||
}
|
||||
if f.size != g.size {
|
||||
return fmt.Errorf("different size for %q: %d != %d", f.name, f.size, g.size)
|
||||
}
|
||||
for j, l := range f.lines {
|
||||
m := g.lines[j]
|
||||
if l != m {
|
||||
return fmt.Errorf("different offsets for %q", f.name)
|
||||
}
|
||||
}
|
||||
for j, l := range f.infos {
|
||||
m := g.infos[j]
|
||||
if l.Offset != m.Offset || l.Filename != m.Filename || l.Line != m.Line {
|
||||
return fmt.Errorf("different infos for %q", f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we don't care about .last - it's just a cache
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSerialize(t *testing.T, p *FileSet) {
|
||||
var buf bytes.Buffer
|
||||
encode := func(x interface{}) error {
|
||||
return gob.NewEncoder(&buf).Encode(x)
|
||||
}
|
||||
if err := p.Write(encode); err != nil {
|
||||
t.Errorf("writing fileset failed: %s", err)
|
||||
return
|
||||
}
|
||||
q := NewFileSet()
|
||||
decode := func(x interface{}) error {
|
||||
return gob.NewDecoder(&buf).Decode(x)
|
||||
}
|
||||
if err := q.Read(decode); err != nil {
|
||||
t.Errorf("reading fileset failed: %s", err)
|
||||
return
|
||||
}
|
||||
if err := equal(p, q); err != nil {
|
||||
t.Errorf("filesets not identical: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerialization(t *testing.T) {
|
||||
p := NewFileSet()
|
||||
checkSerialize(t, p)
|
||||
// add some files
|
||||
for i := 0; i < 10; i++ {
|
||||
f := p.AddFile(fmt.Sprintf("file%d", i), p.Base()+i, i*100)
|
||||
checkSerialize(t, p)
|
||||
// add some lines and alternative file infos
|
||||
line := 1000
|
||||
for offs := 0; offs < f.Size(); offs += 40 + i {
|
||||
f.AddLine(offs)
|
||||
if offs%7 == 0 {
|
||||
f.AddLineInfo(offs, fmt.Sprintf("file%d", offs), line)
|
||||
line += 33
|
||||
}
|
||||
}
|
||||
checkSerialize(t, p)
|
||||
}
|
||||
}
|
83
vendor/gopkg.in/gcfg.v1/token/token.go
generated
vendored
Normal file
83
vendor/gopkg.in/gcfg.v1/token/token.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package token defines constants representing the lexical tokens of the gcfg
|
||||
// configuration syntax and basic operations on tokens (printing, predicates).
|
||||
//
|
||||
// Note that the API for the token package may change to accommodate new
|
||||
// features or implementation changes in gcfg.
|
||||
//
|
||||
package token
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Token is the set of lexical tokens of the gcfg configuration syntax.
|
||||
type Token int
|
||||
|
||||
// The list of tokens.
|
||||
const (
|
||||
// Special tokens
|
||||
ILLEGAL Token = iota
|
||||
EOF
|
||||
COMMENT
|
||||
|
||||
literal_beg
|
||||
// Identifiers and basic type literals
|
||||
// (these tokens stand for classes of literals)
|
||||
IDENT // section-name, variable-name
|
||||
STRING // "subsection-name", variable value
|
||||
literal_end
|
||||
|
||||
operator_beg
|
||||
// Operators and delimiters
|
||||
ASSIGN // =
|
||||
LBRACK // [
|
||||
RBRACK // ]
|
||||
EOL // \n
|
||||
operator_end
|
||||
)
|
||||
|
||||
var tokens = [...]string{
|
||||
ILLEGAL: "ILLEGAL",
|
||||
|
||||
EOF: "EOF",
|
||||
COMMENT: "COMMENT",
|
||||
|
||||
IDENT: "IDENT",
|
||||
STRING: "STRING",
|
||||
|
||||
ASSIGN: "=",
|
||||
LBRACK: "[",
|
||||
RBRACK: "]",
|
||||
EOL: "\n",
|
||||
}
|
||||
|
||||
// String returns the string corresponding to the token tok.
|
||||
// For operators and delimiters, the string is the actual token character
|
||||
// sequence (e.g., for the token ASSIGN, the string is "="). For all other
|
||||
// tokens the string corresponds to the token constant name (e.g. for the
|
||||
// token IDENT, the string is "IDENT").
|
||||
//
|
||||
func (tok Token) String() string {
|
||||
s := ""
|
||||
if 0 <= tok && tok < Token(len(tokens)) {
|
||||
s = tokens[tok]
|
||||
}
|
||||
if s == "" {
|
||||
s = "token(" + strconv.Itoa(int(tok)) + ")"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Predicates
|
||||
|
||||
// IsLiteral returns true for tokens corresponding to identifiers
|
||||
// and basic type literals; it returns false otherwise.
|
||||
//
|
||||
func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end }
|
||||
|
||||
// IsOperator returns true for tokens corresponding to operators and
|
||||
// delimiters; it returns false otherwise.
|
||||
//
|
||||
func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end }
|
23
vendor/gopkg.in/gcfg.v1/types/bool.go
generated
vendored
Normal file
23
vendor/gopkg.in/gcfg.v1/types/bool.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package types
|
||||
|
||||
// BoolValues defines the name and value mappings for ParseBool.
|
||||
var BoolValues = map[string]interface{}{
|
||||
"true": true, "yes": true, "on": true, "1": true,
|
||||
"false": false, "no": false, "off": false, "0": false,
|
||||
}
|
||||
|
||||
var boolParser = func() *EnumParser {
|
||||
ep := &EnumParser{}
|
||||
ep.AddVals(BoolValues)
|
||||
return ep
|
||||
}()
|
||||
|
||||
// ParseBool parses bool values according to the definitions in BoolValues.
|
||||
// Parsing is case-insensitive.
|
||||
func ParseBool(s string) (bool, error) {
|
||||
v, err := boolParser.Parse(s)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return v.(bool), nil
|
||||
}
|
4
vendor/gopkg.in/gcfg.v1/types/doc.go
generated
vendored
Normal file
4
vendor/gopkg.in/gcfg.v1/types/doc.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// Package types defines helpers for type conversions.
|
||||
//
|
||||
// The API for this package is not finalized yet.
|
||||
package types
|
44
vendor/gopkg.in/gcfg.v1/types/enum.go
generated
vendored
Normal file
44
vendor/gopkg.in/gcfg.v1/types/enum.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnumParser parses "enum" values; i.e. a predefined set of strings to
|
||||
// predefined values.
|
||||
type EnumParser struct {
|
||||
Type string // type name; if not set, use type of first value added
|
||||
CaseMatch bool // if true, matching of strings is case-sensitive
|
||||
// PrefixMatch bool
|
||||
vals map[string]interface{}
|
||||
}
|
||||
|
||||
// AddVals adds strings and values to an EnumParser.
|
||||
func (ep *EnumParser) AddVals(vals map[string]interface{}) {
|
||||
if ep.vals == nil {
|
||||
ep.vals = make(map[string]interface{})
|
||||
}
|
||||
for k, v := range vals {
|
||||
if ep.Type == "" {
|
||||
ep.Type = reflect.TypeOf(v).Name()
|
||||
}
|
||||
if !ep.CaseMatch {
|
||||
k = strings.ToLower(k)
|
||||
}
|
||||
ep.vals[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses the string and returns the value or an error.
|
||||
func (ep EnumParser) Parse(s string) (interface{}, error) {
|
||||
if !ep.CaseMatch {
|
||||
s = strings.ToLower(s)
|
||||
}
|
||||
v, ok := ep.vals[s]
|
||||
if !ok {
|
||||
return false, fmt.Errorf("failed to parse %s %#q", ep.Type, s)
|
||||
}
|
||||
return v, nil
|
||||
}
|
29
vendor/gopkg.in/gcfg.v1/types/enum_test.go
generated
vendored
Normal file
29
vendor/gopkg.in/gcfg.v1/types/enum_test.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnumParserBool(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
val string
|
||||
res bool
|
||||
ok bool
|
||||
}{
|
||||
{val: "tRuE", res: true, ok: true},
|
||||
{val: "False", res: false, ok: true},
|
||||
{val: "t", ok: false},
|
||||
} {
|
||||
b, err := ParseBool(tt.val)
|
||||
switch {
|
||||
case tt.ok && err != nil:
|
||||
t.Errorf("%q: got error %v, want %v", tt.val, err, tt.res)
|
||||
case !tt.ok && err == nil:
|
||||
t.Errorf("%q: got %v, want error", tt.val, b)
|
||||
case tt.ok && b != tt.res:
|
||||
t.Errorf("%q: got %v, want %v", tt.val, b, tt.res)
|
||||
default:
|
||||
t.Logf("%q: got %v, %v", tt.val, b, err)
|
||||
}
|
||||
}
|
||||
}
|
86
vendor/gopkg.in/gcfg.v1/types/int.go
generated
vendored
Normal file
86
vendor/gopkg.in/gcfg.v1/types/int.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An IntMode is a mode for parsing integer values, representing a set of
|
||||
// accepted bases.
|
||||
type IntMode uint8
|
||||
|
||||
// IntMode values for ParseInt; can be combined using binary or.
|
||||
const (
|
||||
Dec IntMode = 1 << iota
|
||||
Hex
|
||||
Oct
|
||||
)
|
||||
|
||||
// String returns a string representation of IntMode; e.g. `IntMode(Dec|Hex)`.
|
||||
func (m IntMode) String() string {
|
||||
var modes []string
|
||||
if m&Dec != 0 {
|
||||
modes = append(modes, "Dec")
|
||||
}
|
||||
if m&Hex != 0 {
|
||||
modes = append(modes, "Hex")
|
||||
}
|
||||
if m&Oct != 0 {
|
||||
modes = append(modes, "Oct")
|
||||
}
|
||||
return "IntMode(" + strings.Join(modes, "|") + ")"
|
||||
}
|
||||
|
||||
var errIntAmbig = fmt.Errorf("ambiguous integer value; must include '0' prefix")
|
||||
|
||||
func prefix0(val string) bool {
|
||||
return strings.HasPrefix(val, "0") || strings.HasPrefix(val, "-0")
|
||||
}
|
||||
|
||||
func prefix0x(val string) bool {
|
||||
return strings.HasPrefix(val, "0x") || strings.HasPrefix(val, "-0x")
|
||||
}
|
||||
|
||||
// ParseInt parses val using mode into intptr, which must be a pointer to an
|
||||
// integer kind type. Non-decimal value require prefix `0` or `0x` in the cases
|
||||
// when mode permits ambiguity of base; otherwise the prefix can be omitted.
|
||||
func ParseInt(intptr interface{}, val string, mode IntMode) error {
|
||||
val = strings.TrimSpace(val)
|
||||
verb := byte(0)
|
||||
switch mode {
|
||||
case Dec:
|
||||
verb = 'd'
|
||||
case Dec + Hex:
|
||||
if prefix0x(val) {
|
||||
verb = 'v'
|
||||
} else {
|
||||
verb = 'd'
|
||||
}
|
||||
case Dec + Oct:
|
||||
if prefix0(val) && !prefix0x(val) {
|
||||
verb = 'v'
|
||||
} else {
|
||||
verb = 'd'
|
||||
}
|
||||
case Dec + Hex + Oct:
|
||||
verb = 'v'
|
||||
case Hex:
|
||||
if prefix0x(val) {
|
||||
verb = 'v'
|
||||
} else {
|
||||
verb = 'x'
|
||||
}
|
||||
case Oct:
|
||||
verb = 'o'
|
||||
case Hex + Oct:
|
||||
if prefix0(val) {
|
||||
verb = 'v'
|
||||
} else {
|
||||
return errIntAmbig
|
||||
}
|
||||
}
|
||||
if verb == 0 {
|
||||
panic("unsupported mode")
|
||||
}
|
||||
return ScanFully(intptr, val, verb)
|
||||
}
|
67
vendor/gopkg.in/gcfg.v1/types/int_test.go
generated
vendored
Normal file
67
vendor/gopkg.in/gcfg.v1/types/int_test.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func elem(p interface{}) interface{} {
|
||||
return reflect.ValueOf(p).Elem().Interface()
|
||||
}
|
||||
|
||||
func TestParseInt(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
val string
|
||||
mode IntMode
|
||||
exp interface{}
|
||||
ok bool
|
||||
}{
|
||||
{"0", Dec, int(0), true},
|
||||
{"10", Dec, int(10), true},
|
||||
{"-10", Dec, int(-10), true},
|
||||
{"x", Dec, int(0), false},
|
||||
{"0xa", Hex, int(0xa), true},
|
||||
{"a", Hex, int(0xa), true},
|
||||
{"10", Hex, int(0x10), true},
|
||||
{"-0xa", Hex, int(-0xa), true},
|
||||
{"0x", Hex, int(0x0), true}, // Scanf doesn't require digit behind 0x
|
||||
{"-0x", Hex, int(0x0), true}, // Scanf doesn't require digit behind 0x
|
||||
{"-a", Hex, int(-0xa), true},
|
||||
{"-10", Hex, int(-0x10), true},
|
||||
{"x", Hex, int(0), false},
|
||||
{"10", Oct, int(010), true},
|
||||
{"010", Oct, int(010), true},
|
||||
{"-10", Oct, int(-010), true},
|
||||
{"-010", Oct, int(-010), true},
|
||||
{"10", Dec | Hex, int(10), true},
|
||||
{"010", Dec | Hex, int(10), true},
|
||||
{"0x10", Dec | Hex, int(0x10), true},
|
||||
{"10", Dec | Oct, int(10), true},
|
||||
{"010", Dec | Oct, int(010), true},
|
||||
{"0x10", Dec | Oct, int(0), false},
|
||||
{"10", Hex | Oct, int(0), false}, // need prefix to distinguish Hex/Oct
|
||||
{"010", Hex | Oct, int(010), true},
|
||||
{"0x10", Hex | Oct, int(0x10), true},
|
||||
{"10", Dec | Hex | Oct, int(10), true},
|
||||
{"010", Dec | Hex | Oct, int(010), true},
|
||||
{"0x10", Dec | Hex | Oct, int(0x10), true},
|
||||
} {
|
||||
typ := reflect.TypeOf(tt.exp)
|
||||
res := reflect.New(typ).Interface()
|
||||
err := ParseInt(res, tt.val, tt.mode)
|
||||
switch {
|
||||
case tt.ok && err != nil:
|
||||
t.Errorf("ParseInt(%v, %#v, %v): fail; got error %v, want ok",
|
||||
typ, tt.val, tt.mode, err)
|
||||
case !tt.ok && err == nil:
|
||||
t.Errorf("ParseInt(%v, %#v, %v): fail; got %v, want error",
|
||||
typ, tt.val, tt.mode, elem(res))
|
||||
case tt.ok && !reflect.DeepEqual(elem(res), tt.exp):
|
||||
t.Errorf("ParseInt(%v, %#v, %v): fail; got %v, want %v",
|
||||
typ, tt.val, tt.mode, elem(res), tt.exp)
|
||||
default:
|
||||
t.Logf("ParseInt(%v, %#v, %s): pass; got %v, error %v",
|
||||
typ, tt.val, tt.mode, elem(res), err)
|
||||
}
|
||||
}
|
||||
}
|
23
vendor/gopkg.in/gcfg.v1/types/scan.go
generated
vendored
Normal file
23
vendor/gopkg.in/gcfg.v1/types/scan.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ScanFully uses fmt.Sscanf with verb to fully scan val into ptr.
|
||||
func ScanFully(ptr interface{}, val string, verb byte) error {
|
||||
t := reflect.ValueOf(ptr).Elem().Type()
|
||||
// attempt to read extra bytes to make sure the value is consumed
|
||||
var b []byte
|
||||
n, err := fmt.Sscanf(val, "%"+string(verb)+"%s", ptr, &b)
|
||||
switch {
|
||||
case n < 1 || n == 1 && err != io.EOF:
|
||||
return fmt.Errorf("failed to parse %q as %v: %v", val, t, err)
|
||||
case n > 1:
|
||||
return fmt.Errorf("failed to parse %q as %v: extra characters %q", val, t, string(b))
|
||||
}
|
||||
// n == 1 && err == io.EOF
|
||||
return nil
|
||||
}
|
36
vendor/gopkg.in/gcfg.v1/types/scan_test.go
generated
vendored
Normal file
36
vendor/gopkg.in/gcfg.v1/types/scan_test.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScanFully(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
val string
|
||||
verb byte
|
||||
res interface{}
|
||||
ok bool
|
||||
}{
|
||||
{"a", 'v', int(0), false},
|
||||
{"0x", 'v', int(0), true},
|
||||
{"0x", 'd', int(0), false},
|
||||
} {
|
||||
d := reflect.New(reflect.TypeOf(tt.res)).Interface()
|
||||
err := ScanFully(d, tt.val, tt.verb)
|
||||
switch {
|
||||
case tt.ok && err != nil:
|
||||
t.Errorf("ScanFully(%T, %q, '%c'): want ok, got error %v",
|
||||
d, tt.val, tt.verb, err)
|
||||
case !tt.ok && err == nil:
|
||||
t.Errorf("ScanFully(%T, %q, '%c'): want error, got %v",
|
||||
d, tt.val, tt.verb, elem(d))
|
||||
case tt.ok && err == nil && !reflect.DeepEqual(tt.res, elem(d)):
|
||||
t.Errorf("ScanFully(%T, %q, '%c'): want %v, got %v",
|
||||
d, tt.val, tt.verb, tt.res, elem(d))
|
||||
default:
|
||||
t.Logf("ScanFully(%T, %q, '%c') = %v; *ptr==%v",
|
||||
d, tt.val, tt.verb, err, elem(d))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user