<commit_before>// Copyright 2015 The Cockroach Authors.
//
// 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. See the AUTHORS file
// for names of contributors.
//
// Author: Peter Mattis (peter@cockroachlabs.com)

package driver_test

import (
	"database/sql"
	"fmt"
	"log"
	"testing"
	"time"

	"github.com/cockroachdb/cockroach/server"
	"github.com/cockroachdb/cockroach/util/leaktest"
)

func setup(t *testing.T) (*server.TestServer, *sql.DB) {
	s := server.StartTestServer(nil)
	db, err := sql.Open("cockroach", "https://root@"+s.ServingAddr()+"?certs=test_certs")
	if err != nil {
		t.Fatal(err)
	}
	return s, db
}

func cleanup(s *server.TestServer, db *sql.DB) {
	_ = db.Close()
	s.Stop()
}

type resultSlice [][]*string

func (r resultSlice) String() string {
	results := make([][]string, len(r))

	for i, subSlice := range r {
		results[i] = make([]string, len(subSlice))
		for j, str := range subSlice {
			if str == nil {
				results[i][j] = "<NULL>"
			} else {
				results[i][j] = *str
			}
		}
	}

	return fmt.Sprintf("%s", results)
}

func asResultSlice(src [][]string) resultSlice {
	result := make(resultSlice, len(src))
	for i, subSlice := range src {
		result[i] = make([]*string, len(subSlice))
		for j := range subSlice {
			if subSlice[j] == "<NULL>" {
				result[i][j] = nil
			} else {
				result[i][j] = &subSlice[j]
			}
		}
	}
	return result
}

func readAll(t *testing.T, rows *sql.Rows) resultSlice {
	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		t.Fatal(err)
	}

	colStrs := make([]*string, len(cols))
	for i := range cols {
		colStrs[i] = &cols[i]
	}

	results := resultSlice{colStrs}

	for rows.Next() {
		strs := make([]*string, len(cols))
		vals := make([]interface{}, len(cols))
		for i := range vals {
			vals[i] = &strs[i]
		}
		if err := rows.Scan(vals...); err != nil {
			t.Fatal(err)
		}
		results = append(results, strs)
	}

	if err := rows.Err(); err != nil {
		t.Fatal(err)
	}

	return results
}

func verifyResults(expectedResults resultSlice, actualResults resultSlice) error {
	errMismatch := fmt.Errorf("expected: %s\nactual: %s\n", expectedResults, actualResults)

	if len(expectedResults) != len(actualResults) {
		return errMismatch
	}

	for i := 0; i < len(expectedResults); i++ {
		if len(expectedResults[i]) != len(actualResults[i]) {
			return errMismatch
		}

		for j := 0; j < len(expectedResults[i]); j++ {
			if expectedResults[i][j] == nil && actualResults[i][j] == nil {
				continue
			}
			if !(expectedResults[i][j] != nil && actualResults[i][j] != nil) {
				return errMismatch
			}
			if *expectedResults[i][j] != *actualResults[i][j] {
				return errMismatch
			}
		}
	}

	return nil
}

func TestPlaceholders(t *testing.T) {
	defer leaktest.AfterTest(t)
	s, db := setup(t)
	defer cleanup(s, db)

	timeVal := time.Date(2015, time.August, 30, 3, 34, 45, 345670000, time.UTC)
	intervalVal, err := time.ParseDuration("34h2s")
	if err != nil {
		log.Fatal(err)
	}

	if _, err := db.Exec(`CREATE DATABASE t`); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(`CREATE TABLE t.alltypes (a BIGINT PRIMARY KEY, b FLOAT, c TEXT, d BOOLEAN, e TIMESTAMP, f DATE, g INTERVAL)`); err != nil {
		t.Fatal(err)
	}
	// Insert values for all the different types.
	if _, err := db.Exec(`INSERT INTO t.alltypes (a, b, c, d, e, f, g) VALUES ($1, $2, $3, $4, $5, $5::DATE, $6::INTERVAL)`, 123, 3.4, "blah", true, timeVal, intervalVal); err != nil {
		t.Fatal(err)
	}
	// Insert a row with NULL values
	if _, err := db.Exec(`INSERT INTO t.alltypes (a, b, c, d, e, f, g) VALUES ($1, $2, $3, $4, $5, $6, $7)`, 456, nil, nil, nil, nil, nil, nil); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE a IN ($1)", 123); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE b IN ($1)", 3.4); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE c IN ($1)", "blah"); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE d IN ($1)", true); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE e IN ($1)", timeVal); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE f IN ($1::DATE)", timeVal); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE g IN ($1::INTERVAL)", intervalVal); err != nil {
		t.Fatal(err)
	}
	if rows, err := db.Query("SELECT * FROM t.alltypes"); err != nil {
		t.Fatal(err)
	} else {
		results := readAll(t, rows)
		expectedResults := asResultSlice([][]string{
			{"a", "b", "c", "d", "e", "f", "g"},
			{"123", "3.4", "blah", "true", "2015-08-30 03:34:45.34567+00:00", "2015-08-30", "34h0m2s"},
			{"456", "<NULL>", "<NULL>", "<NULL>", "<NULL>", "<NULL>", "<NULL>"},
		})
		if err := verifyResults(expectedResults, results); err != nil {
			t.Fatal(err)
		}
	}
	// Delete a row using a placeholder param.
	if _, err := db.Exec(`DELETE FROM t.alltypes WHERE a IN ($1)`, 123); err != nil {
		t.Fatal(err)
	}
	if rows, err := db.Query("SELECT * FROM t.alltypes"); err != nil {
		t.Fatal(err)
	} else {
		results := readAll(t, rows)
		expectedResults := asResultSlice([][]string{
			{"a", "b", "c", "d", "e", "f", "g"},
			{"456", "<NULL>", "<NULL>", "<NULL>", "<NULL>", "<NULL>", "<NULL>"},
		})
		if err := verifyResults(expectedResults, results); err != nil {
			t.Fatal(err)
		}
	}
}

func TestConnectionSettings(t *testing.T) {
	defer leaktest.AfterTest(t)
	s := server.StartTestServer(nil)
	url := "https://root@" + s.ServingAddr() + "?certs=test_certs"
	// Create a new Database called t.
	db, err := sql.Open("cockroach", url)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(`CREATE DATABASE t`); err != nil {
		t.Fatal(err)
	}
	if err := db.Close(); err != nil {
		t.Fatal(err)
	}
	// Open a new db client against the server with
	// a fresh set of connections with the new settings.
	// We create a database 't' above because you cannot
	// use sql: "SET DATABASE = t" without the database
	// existing.
	url += "&database=t"
	if db, err = sql.Open("cockroach", url); err != nil {
		t.Fatal(err)
	}
	defer cleanup(s, db)
	if _, err := db.Exec(`CREATE TABLE kv (k CHAR PRIMARY KEY, v CHAR)`); err != nil {
		t.Fatal(err)
	}
	numTxs := 5
	txs := make([]*sql.Tx, 0, numTxs)
	for i := 0; i < numTxs; i++ {
		// Each transaction gets its own connection.
		if tx, err := db.Begin(); err != nil {
			t.Fatal(err)
		} else {
			txs = append(txs, tx)
		}
	}
	for _, tx := range txs {
		// Settings work!
		if _, err := tx.Exec(`SELECT * from kv`); err != nil {
			t.Fatal(err)
		}
		if err := tx.Commit(); err != nil {
			t.Fatal(err)
		}
	}
}

func TestInsecure(t *testing.T) {
	defer leaktest.AfterTest(t)
	// Start test server in insecure mode.
	s := &server.TestServer{}
	s.Ctx = server.NewTestContext()
	s.Ctx.Insecure = true
	if err := s.Start(); err != nil {
		t.Fatalf("Could not start server: %v", err)
	}
	defer s.Stop()

	// We can't attempt a connection through HTTPS since the client just retries forever.
	// DB connection using plain HTTP.
	db, err := sql.Open("cockroach", "http://root@"+s.ServingAddr())
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = db.Close()
	}()
	if _, err := db.Exec(`SELECT 1`); err != nil {
		t.Fatal(err)
	}
}
<commit_msg>sql/driver: use sql.NullString in test<commit_after>// Copyright 2015 The Cockroach Authors.
//
// 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. See the AUTHORS file
// for names of contributors.
//
// Author: Peter Mattis (peter@cockroachlabs.com)

package driver_test

import (
	"database/sql"
	"reflect"
	"testing"
	"time"

	"github.com/cockroachdb/cockroach/server"
	"github.com/cockroachdb/cockroach/util/leaktest"
)

func setup(t *testing.T) (*server.TestServer, *sql.DB) {
	s := server.StartTestServer(nil)
	db, err := sql.Open("cockroach", "https://root@"+s.ServingAddr()+"?certs=test_certs")
	if err != nil {
		t.Fatal(err)
	}
	return s, db
}

func cleanup(s *server.TestServer, db *sql.DB) {
	_ = db.Close()
	s.Stop()
}

func readAll(rows *sql.Rows) ([][]string, error) {
	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		return nil, err
	}

	vals := make([]interface{}, len(cols))
	for i := range vals {
		vals[i] = new(sql.NullString)
	}
	var results [][]string
	for rows.Next() {
		if err := rows.Scan(vals...); err != nil {
			return nil, err
		}
		rowStrings := make([]string, 0, len(cols))
		for _, v := range vals {
			nullStr := v.(*sql.NullString)
			if nullStr.Valid {
				rowStrings = append(rowStrings, nullStr.String)
			} else {
				rowStrings = append(rowStrings, "NULL")
			}
		}
		results = append(results, rowStrings)
	}

	if err := rows.Err(); err != nil {
		return nil, err
	}

	return results, nil
}

func TestPlaceholders(t *testing.T) {
	defer leaktest.AfterTest(t)
	s, db := setup(t)
	defer cleanup(s, db)

	timeVal := time.Date(2015, time.August, 30, 3, 34, 45, 345670000, time.UTC)
	intervalVal, err := time.ParseDuration("34h2s")
	if err != nil {
		t.Fatal(err)
	}

	if _, err := db.Exec(`CREATE DATABASE t`); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(`CREATE TABLE t.alltypes (a BIGINT PRIMARY KEY, b FLOAT, c TEXT, d BOOLEAN, e TIMESTAMP, f DATE, g INTERVAL)`); err != nil {
		t.Fatal(err)
	}
	if rows, err := db.Query("SELECT * FROM t.alltypes"); err != nil {
		t.Fatal(err)
	} else {
		defer rows.Close()

		cols, err := rows.Columns()
		if err != nil {
			t.Fatal(err)
		}

		if expected := []string{"a", "b", "c", "d", "e", "f", "g"}; !reflect.DeepEqual(cols, expected) {
			t.Errorf("got unexpected columns:\n%s\nexpected:\n%s", cols, expected)
		}
	}

	// Insert values for all the different types.
	if _, err := db.Exec(`INSERT INTO t.alltypes (a, b, c, d, e, f, g) VALUES ($1, $2, $3, $4, $5, $5::DATE, $6::INTERVAL)`, 123, 3.4, "blah", true, timeVal, intervalVal); err != nil {
		t.Fatal(err)
	}
	// Insert a row with NULL values
	if _, err := db.Exec(`INSERT INTO t.alltypes (a, b, c, d, e, f, g) VALUES ($1, $2, $3, $4, $5, $6, $7)`, 456, nil, nil, nil, nil, nil, nil); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE a IN ($1)", 123); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE b IN ($1)", 3.4); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE c IN ($1)", "blah"); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE d IN ($1)", true); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE e IN ($1)", timeVal); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE f IN ($1::DATE)", timeVal); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Query("SELECT a, b FROM t.alltypes WHERE g IN ($1::INTERVAL)", intervalVal); err != nil {
		t.Fatal(err)
	}
	if rows, err := db.Query("SELECT * FROM t.alltypes"); err != nil {
		t.Fatal(err)
	} else {
		if results, err := readAll(rows); err != nil {
			t.Fatal(err)
		} else {
			expected := [][]string{
				{"123", "3.4", "blah", "true", "2015-08-30 03:34:45.34567+00:00", "2015-08-30", "34h0m2s"},
				{"456", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL"},
			}
			if !reflect.DeepEqual(results, expected) {
				t.Errorf("got unexpected results:\n%s\nexpected:\n%s", results, expected)
			}
		}
	}
	// Delete a row using a placeholder param.
	if _, err := db.Exec(`DELETE FROM t.alltypes WHERE a IN ($1)`, 123); err != nil {
		t.Fatal(err)
	}
	if rows, err := db.Query("SELECT * FROM t.alltypes"); err != nil {
		t.Fatal(err)
	} else {
		if results, err := readAll(rows); err != nil {
			t.Fatal(err)
		} else {
			expected := [][]string{
				{"456", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL"},
			}
			if !reflect.DeepEqual(results, expected) {
				t.Errorf("got unexpected results:\n%s\nexpected:\n%s", results, expected)
			}
		}
	}
}

func TestConnectionSettings(t *testing.T) {
	defer leaktest.AfterTest(t)
	s := server.StartTestServer(nil)
	url := "https://root@" + s.ServingAddr() + "?certs=test_certs"
	// Create a new Database called t.
	db, err := sql.Open("cockroach", url)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(`CREATE DATABASE t`); err != nil {
		t.Fatal(err)
	}
	if err := db.Close(); err != nil {
		t.Fatal(err)
	}
	// Open a new db client against the server with
	// a fresh set of connections with the new settings.
	// We create a database 't' above because you cannot
	// use sql: "SET DATABASE = t" without the database
	// existing.
	url += "&database=t"
	if db, err = sql.Open("cockroach", url); err != nil {
		t.Fatal(err)
	}
	defer cleanup(s, db)
	if _, err := db.Exec(`CREATE TABLE kv (k CHAR PRIMARY KEY, v CHAR)`); err != nil {
		t.Fatal(err)
	}
	numTxs := 5
	txs := make([]*sql.Tx, 0, numTxs)
	for i := 0; i < numTxs; i++ {
		// Each transaction gets its own connection.
		if tx, err := db.Begin(); err != nil {
			t.Fatal(err)
		} else {
			txs = append(txs, tx)
		}
	}
	for _, tx := range txs {
		// Settings work!
		if _, err := tx.Exec(`SELECT * from kv`); err != nil {
			t.Fatal(err)
		}
		if err := tx.Commit(); err != nil {
			t.Fatal(err)
		}
	}
}

func TestInsecure(t *testing.T) {
	defer leaktest.AfterTest(t)
	// Start test server in insecure mode.
	s := &server.TestServer{}
	s.Ctx = server.NewTestContext()
	s.Ctx.Insecure = true
	if err := s.Start(); err != nil {
		t.Fatalf("Could not start server: %v", err)
	}
	defer s.Stop()

	// We can't attempt a connection through HTTPS since the client just retries forever.
	// DB connection using plain HTTP.
	db, err := sql.Open("cockroach", "http://root@"+s.ServingAddr())
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = db.Close()
	}()
	if _, err := db.Exec(`SELECT 1`); err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package operation

import (
	"fmt"
	"net/url"

	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/util"
)

type SubmitResult struct {
	FileName string `json:"fileName,omitempty"`
	FileUrl  string `json:"fileUrl,omitempty"`
	Fid      string `json:"fid,omitempty"`
	Size     uint32 `json:"size,omitempty"`
	Error    string `json:"error,omitempty"`
}

func RegisterFile(filer string, path string, fileId string, secret security.Secret) error {
	// TODO: jwt need to be used
	_ = security.GenJwt(secret, fileId)

	values := make(url.Values)
	values.Add("path", path)
	values.Add("fileId", fileId)
	_, err := util.Post("http://"+filer+"/admin/register", values)
	if err != nil {
		return fmt.Errorf("Failed to register path:%s on filer:%s to file id:%s", path, filer, fileId)
	}
	return nil
}
<commit_msg>fix package name<commit_after>package filer

import (
	"fmt"
	"net/url"

	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/util"
)

type SubmitResult struct {
	FileName string `json:"fileName,omitempty"`
	FileUrl  string `json:"fileUrl,omitempty"`
	Fid      string `json:"fid,omitempty"`
	Size     uint32 `json:"size,omitempty"`
	Error    string `json:"error,omitempty"`
}

func RegisterFile(filer string, path string, fileId string, secret security.Secret) error {
	// TODO: jwt need to be used
	_ = security.GenJwt(secret, fileId)

	values := make(url.Values)
	values.Add("path", path)
	values.Add("fileId", fileId)
	_, err := util.Post("http://"+filer+"/admin/register", values)
	if err != nil {
		return fmt.Errorf("Failed to register path:%s on filer:%s to file id:%s", path, filer, fileId)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
   Hockeypuck - OpenPGP key server
   Copyright (C) 2012-2014  Casey Marshall

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, version 3.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package storage

import (
	"errors"
	"fmt"
	"io"
	"time"

	"gopkg.in/errgo.v1"

	"hockeypuck/openpgp"
)

var ErrKeyNotFound = errors.New("key not found")

func IsNotFound(err error) bool {
	return err == ErrKeyNotFound
}

type Keyring struct {
	*openpgp.PrimaryKey

	CTime time.Time
	MTime time.Time
}

// Storage defines the API that is needed to implement a complete storage
// backend for an HKP service.
type Storage interface {
	io.Closer
	Queryer
	Updater
	Notifier
}

// Queryer defines the storage API for search and retrieval of public key material.
type Queryer interface {

	// MatchMD5 returns the matching RFingerprint IDs for the given public key MD5 hashes.
	// The MD5 is calculated using the "SKS method".
	MatchMD5([]string) ([]string, error)

	// Resolve returns the matching RFingerprint IDs for the given public key IDs.
	// Key IDs are typically short (8 hex digits), long (16 digits) or full (40 digits).
	// Matches are made against key IDs and subkey IDs.
	Resolve([]string) ([]string, error)

	// MatchKeyword returns the matching RFingerprint IDs for the given keyword search.
	// The keyword search is storage dependant and results may vary among
	// different implementations.
	MatchKeyword([]string) ([]string, error)

	// ModifiedSince returns matching RFingerprint IDs for keyrings modified
	// since the given time.
	ModifiedSince(time.Time) ([]string, error)

	// FetchKeys returns the public key material matching the given RFingerprint slice.
	FetchKeys([]string) ([]*openpgp.PrimaryKey, error)

	// FetchKeyrings returns the keyring records matching the given RFingerprint slice.
	FetchKeyrings([]string) ([]*Keyring, error)
}

// Inserter defines the storage API for inserting key material.
type Inserter interface {

	// Insert inserts new public keys if they are not already stored. If they
	// are, then nothing is changed.
	Insert([]*openpgp.PrimaryKey) (int, error)
}

// Updater defines the storage API for writing key material.
type Updater interface {
	Inserter

	// Update updates the stored PrimaryKey with the given contents, if the current
	// contents of the key in storage matches the given digest. If it does not
	// match, the update should be retried again later.
	Update(pubkey *openpgp.PrimaryKey, priorID string, priorMD5 string) error
}

type Notifier interface {
	// Subscribe registers a key change callback function.
	Subscribe(func(KeyChange) error)

	// Notify invokes all registered callbacks with a key change notification.
	Notify(change KeyChange) error

	// RenotifyAll() invokes all registered callbacks with KeyAdded notifications
	// for each key in the Storage.
	RenotifyAll() error
}

type KeyChange interface {
	InsertDigests() []string
	RemoveDigests() []string
}

type KeyAdded struct {
	ID     string
	Digest string
}

func (ka KeyAdded) InsertDigests() []string {
	return []string{ka.Digest}
}

func (ka KeyAdded) RemoveDigests() []string {
	return nil
}

func (ka KeyAdded) String() string {
	return fmt.Sprintf("key 0x%s with hash %s added", ka.ID, ka.Digest)
}

type KeyReplaced struct {
	OldID     string
	OldDigest string
	NewID     string
	NewDigest string
}

func (kr KeyReplaced) InsertDigests() []string {
	return []string{kr.NewDigest}
}

func (kr KeyReplaced) RemoveDigests() []string {
	return []string{kr.OldDigest}
}

func (kr KeyReplaced) String() string {
	return fmt.Sprintf("key 0x%s with hash %s replaced key 0x%s with hash %s", kr.NewID, kr.NewDigest, kr.OldID, kr.OldDigest)
}

type KeyNotChanged struct{}

func (knc KeyNotChanged) InsertDigests() []string { return nil }

func (knc KeyNotChanged) RemoveDigests() []string { return nil }

func (knc KeyNotChanged) String() string {
	return "key not changed"
}

type InsertError struct {
	Duplicates []*openpgp.PrimaryKey
	Errors     []error
}

func (err InsertError) Error() string {
	return fmt.Sprintf("%d duplicates, %d errors", len(err.Duplicates), len(err.Errors))
}

func Duplicates(err error) []*openpgp.PrimaryKey {
	insertErr, ok := err.(InsertError)
	if !ok {
		return nil
	}
	return insertErr.Duplicates
}

func firstMatch(results []*openpgp.PrimaryKey, match string) (*openpgp.PrimaryKey, error) {
	for _, key := range results {
		if key.RFingerprint == match {
			return key, nil
		}
	}
	return nil, ErrKeyNotFound
}

func UpsertKey(storage Storage, pubkey *openpgp.PrimaryKey) (kc KeyChange, err error) {
	var lastKey *openpgp.PrimaryKey
	lastKeys, err := storage.FetchKeys([]string{pubkey.RFingerprint})
	if err == nil {
		// match primary fingerprint -- someone might have reused a subkey somewhere
		lastKey, err = firstMatch(lastKeys, pubkey.RFingerprint)
	}
	if IsNotFound(err) {
		_, err = storage.Insert([]*openpgp.PrimaryKey{pubkey})
		if err != nil {
			return nil, errgo.Mask(err)
		}
		return KeyAdded{ID: pubkey.KeyID(), Digest: pubkey.MD5}, nil
	} else if err != nil {
		return nil, errgo.Mask(err)
	}

	if pubkey.UUID != lastKey.UUID {
		return nil, errgo.Newf("upsert key %q lookup failed, found mismatch %q", pubkey.UUID, lastKey.UUID)
	}
	lastID := lastKey.KeyID()
	lastMD5 := lastKey.MD5
	err = openpgp.Merge(lastKey, pubkey)
	if err != nil {
		return nil, errgo.Mask(err)
	}
	if lastMD5 != lastKey.MD5 {
		err = storage.Update(lastKey, lastID, lastMD5)
		if err != nil {
			return nil, errgo.Mask(err)
		}
		return KeyReplaced{OldID: lastID, OldDigest: lastMD5, NewID: lastKey.KeyID(), NewDigest: lastKey.MD5}, nil
	}
	return KeyNotChanged{}, nil
}
<commit_msg>identify the key when reporting KeyNotChanged<commit_after>/*
   Hockeypuck - OpenPGP key server
   Copyright (C) 2012-2014  Casey Marshall

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, version 3.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package storage

import (
	"errors"
	"fmt"
	"io"
	"time"

	"gopkg.in/errgo.v1"

	"hockeypuck/openpgp"
)

var ErrKeyNotFound = errors.New("key not found")

func IsNotFound(err error) bool {
	return err == ErrKeyNotFound
}

type Keyring struct {
	*openpgp.PrimaryKey

	CTime time.Time
	MTime time.Time
}

// Storage defines the API that is needed to implement a complete storage
// backend for an HKP service.
type Storage interface {
	io.Closer
	Queryer
	Updater
	Notifier
}

// Queryer defines the storage API for search and retrieval of public key material.
type Queryer interface {

	// MatchMD5 returns the matching RFingerprint IDs for the given public key MD5 hashes.
	// The MD5 is calculated using the "SKS method".
	MatchMD5([]string) ([]string, error)

	// Resolve returns the matching RFingerprint IDs for the given public key IDs.
	// Key IDs are typically short (8 hex digits), long (16 digits) or full (40 digits).
	// Matches are made against key IDs and subkey IDs.
	Resolve([]string) ([]string, error)

	// MatchKeyword returns the matching RFingerprint IDs for the given keyword search.
	// The keyword search is storage dependant and results may vary among
	// different implementations.
	MatchKeyword([]string) ([]string, error)

	// ModifiedSince returns matching RFingerprint IDs for keyrings modified
	// since the given time.
	ModifiedSince(time.Time) ([]string, error)

	// FetchKeys returns the public key material matching the given RFingerprint slice.
	FetchKeys([]string) ([]*openpgp.PrimaryKey, error)

	// FetchKeyrings returns the keyring records matching the given RFingerprint slice.
	FetchKeyrings([]string) ([]*Keyring, error)
}

// Inserter defines the storage API for inserting key material.
type Inserter interface {

	// Insert inserts new public keys if they are not already stored. If they
	// are, then nothing is changed.
	Insert([]*openpgp.PrimaryKey) (int, error)
}

// Updater defines the storage API for writing key material.
type Updater interface {
	Inserter

	// Update updates the stored PrimaryKey with the given contents, if the current
	// contents of the key in storage matches the given digest. If it does not
	// match, the update should be retried again later.
	Update(pubkey *openpgp.PrimaryKey, priorID string, priorMD5 string) error
}

type Notifier interface {
	// Subscribe registers a key change callback function.
	Subscribe(func(KeyChange) error)

	// Notify invokes all registered callbacks with a key change notification.
	Notify(change KeyChange) error

	// RenotifyAll() invokes all registered callbacks with KeyAdded notifications
	// for each key in the Storage.
	RenotifyAll() error
}

type KeyChange interface {
	InsertDigests() []string
	RemoveDigests() []string
}

type KeyAdded struct {
	ID     string
	Digest string
}

func (ka KeyAdded) InsertDigests() []string {
	return []string{ka.Digest}
}

func (ka KeyAdded) RemoveDigests() []string {
	return nil
}

func (ka KeyAdded) String() string {
	return fmt.Sprintf("key 0x%s with hash %s added", ka.ID, ka.Digest)
}

type KeyReplaced struct {
	OldID     string
	OldDigest string
	NewID     string
	NewDigest string
}

func (kr KeyReplaced) InsertDigests() []string {
	return []string{kr.NewDigest}
}

func (kr KeyReplaced) RemoveDigests() []string {
	return []string{kr.OldDigest}
}

func (kr KeyReplaced) String() string {
	return fmt.Sprintf("key 0x%s with hash %s replaced key 0x%s with hash %s", kr.NewID, kr.NewDigest, kr.OldID, kr.OldDigest)
}

type KeyNotChanged struct {
	ID     string
	Digest string
}

func (knc KeyNotChanged) InsertDigests() []string { return nil }

func (knc KeyNotChanged) RemoveDigests() []string { return nil }

func (knc KeyNotChanged) String() string {
	return fmt.Sprintf("key 0x%s with hash %s not changed", knc.ID, knc.Digest)
}

type InsertError struct {
	Duplicates []*openpgp.PrimaryKey
	Errors     []error
}

func (err InsertError) Error() string {
	return fmt.Sprintf("%d duplicates, %d errors", len(err.Duplicates), len(err.Errors))
}

func Duplicates(err error) []*openpgp.PrimaryKey {
	insertErr, ok := err.(InsertError)
	if !ok {
		return nil
	}
	return insertErr.Duplicates
}

func firstMatch(results []*openpgp.PrimaryKey, match string) (*openpgp.PrimaryKey, error) {
	for _, key := range results {
		if key.RFingerprint == match {
			return key, nil
		}
	}
	return nil, ErrKeyNotFound
}

func UpsertKey(storage Storage, pubkey *openpgp.PrimaryKey) (kc KeyChange, err error) {
	var lastKey *openpgp.PrimaryKey
	lastKeys, err := storage.FetchKeys([]string{pubkey.RFingerprint})
	if err == nil {
		// match primary fingerprint -- someone might have reused a subkey somewhere
		lastKey, err = firstMatch(lastKeys, pubkey.RFingerprint)
	}
	if IsNotFound(err) {
		_, err = storage.Insert([]*openpgp.PrimaryKey{pubkey})
		if err != nil {
			return nil, errgo.Mask(err)
		}
		return KeyAdded{ID: pubkey.KeyID(), Digest: pubkey.MD5}, nil
	} else if err != nil {
		return nil, errgo.Mask(err)
	}

	if pubkey.UUID != lastKey.UUID {
		return nil, errgo.Newf("upsert key %q lookup failed, found mismatch %q", pubkey.UUID, lastKey.UUID)
	}
	lastID := lastKey.KeyID()
	lastMD5 := lastKey.MD5
	err = openpgp.Merge(lastKey, pubkey)
	if err != nil {
		return nil, errgo.Mask(err)
	}
	if lastMD5 != lastKey.MD5 {
		err = storage.Update(lastKey, lastID, lastMD5)
		if err != nil {
			return nil, errgo.Mask(err)
		}
		return KeyReplaced{OldID: lastID, OldDigest: lastMD5, NewID: lastKey.KeyID(), NewDigest: lastKey.MD5}, nil
	}
	return KeyNotChanged{ID: lastID, Digest: lastMD5}, nil
}
<|endoftext|>


<commit_before>package worker_test

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/concourse/concourse/worker/workerfakes"

	"code.cloudfoundry.org/lager/lagertest"
	"github.com/concourse/concourse/atc/worker/gclient"
	"github.com/concourse/concourse/worker"
	"github.com/onsi/gomega/ghttp"

	"github.com/onsi/gomega/gbytes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Container Sweeper", func() {
	const (
	 sweepInterval              = 50 * time.Millisecond
	 maxInFlight                =  uint16(1)
	 gardenClientTimeoutRequest = 5 * time.Millisecond
	)

	var (
		garden *ghttp.Server

		testLogger = lagertest.NewTestLogger("container-sweeper")

		fakeTSAClient workerfakes.FakeTSAClient
		gardenClient  gclient.Client

		sweeper *worker.ContainerSweeper

		osSignal  chan os.Signal
		exited    chan struct{}

	)

	BeforeEach(func() {
		garden = ghttp.NewServer()

		osSignal = make(chan os.Signal)
		exited = make(chan struct{})

		gardenAddr := fmt.Sprintf("http://%s", garden.Addr())
		gardenClient = gclient.BasicGardenClientWithRequestTimeout(testLogger, gardenClientTimeoutRequest, gardenAddr)

		fakeTSAClient = workerfakes.FakeTSAClient{}
		fakeTSAClient.ReportContainersReturns(nil)

		sweeper = worker.NewContainerSweeper(testLogger, sweepInterval, &fakeTSAClient, gardenClient, maxInFlight)

	})

	JustBeforeEach(func() {
		go func() {
			_ = sweeper.Run(osSignal, make(chan struct{}))
			close(exited)
		}()
	})

	AfterEach(func() {
		close(osSignal)
		<-exited
		garden.Close()
	})

	Context("when garden doesn't respond on DELETE", func() {
		var (
			gardenContext context.Context
			gardenCancel  context.CancelFunc
		)

		BeforeEach(func() {
			gardenContext, gardenCancel = context.WithCancel(context.Background())
			garden.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-1"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-2"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-3"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-4"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
			)
			// First GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(0, []string{"some-handle-1", "some-handle-2"}, nil)
			// Second GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(1, []string{"some-handle-3", "some-handle-4"}, nil)

			garden.AllowUnhandledRequests = true

		})
		AfterEach(func() {
			gardenCancel()

		})

		It("request to garden times out eventually", func() {
			Eventually(testLogger.Buffer()).Should(gbytes.Say("failed-to-destroy-container\".*net/http: request canceled \\(Client.Timeout exceeded while awaiting headers\\)"))
		})
		It("sweeper continues ticking and GC'ing", func() {
			// ensure all 4 DELETEs are issues over 2 successive ticks
			Eventually(func() []string {
				// Gather all containers deleted
				var deleteRequestPaths []string
				for _, req := range garden.ReceivedRequests() {
					if req.Method == http.MethodDelete {
						deleteRequestPaths = append(deleteRequestPaths, req.RequestURI)
					}
				}
				return deleteRequestPaths
			}).Should(ConsistOf(
				"/containers/some-handle-1",
				"/containers/some-handle-2",
				"/containers/some-handle-3",
				"/containers/some-handle-4"))

			// Check calls to TSA for containers to destroy > 1
			Expect(fakeTSAClient.ContainersToDestroyCallCount()).To(BeNumerically(">=", 2))
		})
	})

})
<commit_msg>container sweeper nit<commit_after>package worker_test

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/concourse/concourse/worker/workerfakes"

	"code.cloudfoundry.org/lager/lagertest"
	"github.com/concourse/concourse/atc/worker/gclient"
	"github.com/concourse/concourse/worker"
	"github.com/onsi/gomega/ghttp"

	"github.com/onsi/gomega/gbytes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Container Sweeper", func() {
	const (
	 sweepInterval              = 50 * time.Millisecond
	 maxInFlight                =  uint16(1)
	 gardenClientTimeoutRequest = 5 * time.Millisecond
	)

	var (
		garden *ghttp.Server

		testLogger = lagertest.NewTestLogger("container-sweeper")

		fakeTSAClient workerfakes.FakeTSAClient
		gardenClient  gclient.Client

		sweeper *worker.ContainerSweeper

		osSignal  chan os.Signal
		exited    chan struct{}

	)

	BeforeEach(func() {
		garden = ghttp.NewServer()

		osSignal = make(chan os.Signal)
		exited = make(chan struct{})

		gardenAddr := fmt.Sprintf("http://%s", garden.Addr())
		gardenClient = gclient.BasicGardenClientWithRequestTimeout(testLogger, gardenClientTimeoutRequest, gardenAddr)

		fakeTSAClient = workerfakes.FakeTSAClient{}
		fakeTSAClient.ReportContainersReturns(nil)

		sweeper = worker.NewContainerSweeper(testLogger, sweepInterval, &fakeTSAClient, gardenClient, maxInFlight)

	})

	JustBeforeEach(func() {
		go func() {
			_ = sweeper.Run(osSignal, make(chan struct{}))
			close(exited)
		}()
	})

	AfterEach(func() {
		close(osSignal)
		<-exited
		garden.Close()
	})

	Context("when garden doesn't respond on DELETE", func() {
		var (
			gardenContext context.Context
			gardenCancel  context.CancelFunc
		)

		BeforeEach(func() {
			gardenContext, gardenCancel = context.WithCancel(context.Background())
			garden.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-1"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-2"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-3"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-4"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
			)
			// First GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(0, []string{"some-handle-1", "some-handle-2"}, nil)
			// Second GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(1, []string{"some-handle-3", "some-handle-4"}, nil)

			garden.AllowUnhandledRequests = true

		})
		AfterEach(func() {
			gardenCancel()
		})

		It("request to garden times out eventually", func() {
			Eventually(testLogger.Buffer()).Should(gbytes.Say("failed-to-destroy-container\".*net/http: request canceled \\(Client.Timeout exceeded while awaiting headers\\)"))
		})
		It("sweeper continues ticking and GC'ing", func() {
			// ensure all 4 DELETEs are issues over 2 successive ticks
			Eventually(func() []string {
				// Gather all containers deleted
				var deleteRequestPaths []string
				for _, req := range garden.ReceivedRequests() {
					if req.Method == http.MethodDelete {
						deleteRequestPaths = append(deleteRequestPaths, req.RequestURI)
					}
				}
				return deleteRequestPaths
			}).Should(ConsistOf(
				"/containers/some-handle-1",
				"/containers/some-handle-2",
				"/containers/some-handle-3",
				"/containers/some-handle-4"))

			// Check calls to TSA for containers to destroy > 1
			Expect(fakeTSAClient.ContainersToDestroyCallCount()).To(BeNumerically(">=", 2))
		})
	})

})
<|endoftext|>


<commit_before>package dockerfile

import (
	"bytes"
	"reflect"
	"testing"
)

func assertEmitted(expect string, statement Statement, t *testing.T) {
	var buf bytes.Buffer

	statement.Emit(&buf)
	actual := buf.String()

	if !reflect.DeepEqual(expect, actual) {
		t.Errorf("Items not equal:\nexpected %q\nhave     %q\n", expect, actual)
	}
}

func Test_emitting_from(t *testing.T) {
	assertEmitted("FROM debian:jessie\n\n", &From{Line: 1, Image: "debian:jessie"}, t)
}
<commit_msg>Add emitting tests<commit_after>package dockerfile

import (
	"bytes"
	"reflect"
	"testing"
)

func assertEmitted(expect string, statement Statement, t *testing.T) {
	var buf bytes.Buffer

	statement.Emit(&buf)
	actual := buf.String()

	if !reflect.DeepEqual(expect, actual) {
		t.Errorf("Items not equal:\nexpected %q\nhave     %q\n", expect, actual)
	}
}

func Test_emitting_from(t *testing.T) {
	assertEmitted("FROM debian:jessie\n\n", &From{Line: 1, Image: "debian:jessie"}, t)
}

func Test_emitting_comment(t *testing.T) {
	assertEmitted("# Test\n", &Comment{Line: 1, Lines: "Test"}, t)
}

func Test_emitting_multiline_comment(t *testing.T) {
	assertEmitted("# One\n# Two\n# Three\n", &Comment{Line: 1, Lines: "One\nTwo\nThree"}, t)
}

func Test_emitting_run(t *testing.T) {
	assertEmitted("RUN apt-get update\n\n", &Run{Line: 1, Command: "apt-get update"}, t)
}

func Test_emitting_multiline_run(t *testing.T) {
	assertEmitted("RUN apt-get -y install\\\n  doget\n\n", &Run{Line: 1, Command: "apt-get -y install\n  doget"}, t)
}
<|endoftext|>


<commit_before>package build

import (
	"testing"

	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestExtWindows(t *testing.T) {
	assert.Equal(t, extFor("windows"), ".exe")
}

func TestExtOthers(t *testing.T) {
	assert.Empty(t, extFor("linux"))
}

func TestNameFor(t *testing.T) {
	assert := assert.New(t)

	var config = config.Project{
		Archive: config.Archive{
			NameTemplate: "{{.Binary}}_{{.Os}}_{{.Arch}}_{{.Version}}",
			Replacements: map[string]string{
				"darwin": "Darwin",
				"amd64":  "x86_64",
			},
		},
		Build: config.Build{
			Binary: "test",
		},
	}
	var ctx = &context.Context{
		Config: config,
		Git: context.GitInfo{
			CurrentTag: "v1.2.3",
		},
	}

	name, err := nameFor(ctx, "darwin", "amd64")
	assert.NoError(err)
	assert.Equal("test_Darwin_x86_64_v1.2.3", name)
}
<commit_msg>added more build pipe test<commit_after>package build

import (
	"testing"

	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestExtWindows(t *testing.T) {
	assert.Equal(t, ".exe", extFor("windows"))
}

func TestExtOthers(t *testing.T) {
	assert.Empty(t, "", extFor("linux"))
}

func TestNameFor(t *testing.T) {
	assert := assert.New(t)

	var config = config.Project{
		Archive: config.Archive{
			NameTemplate: "{{.Binary}}_{{.Os}}_{{.Arch}}_{{.Version}}",
			Replacements: map[string]string{
				"darwin": "Darwin",
				"amd64":  "x86_64",
			},
		},
		Build: config.Build{
			Binary: "test",
		},
	}
	var ctx = &context.Context{
		Config: config,
		Git: context.GitInfo{
			CurrentTag: "v1.2.3",
		},
	}

	name, err := nameFor(ctx, "darwin", "amd64")
	assert.NoError(err)
	assert.Equal("test_Darwin_x86_64_v1.2.3", name)
}

func TestInvalidNameTemplate(t *testing.T) {
	assert := assert.New(t)

	var config = config.Project{
		Archive: config.Archive{
			NameTemplate: "{{.Binaryyy}}_{{.Os}}_{{.Arch}}_{{.Version}}",
		},
		Build: config.Build{
			Binary: "test",
		},
	}
	var ctx = &context.Context{
		Config: config,
		Git: context.GitInfo{
			CurrentTag: "v1.2.3",
		},
	}

	_, err := nameFor(ctx, "darwin", "amd64")
	assert.Error(err)
}
<|endoftext|>


<commit_before>package frontmatter

import (
	"log"
	"reflect"
	"sort"
	"strings"

	"github.com/hacdias/caddy-hugo/utils"
	"github.com/spf13/cast"
	"github.com/spf13/hugo/parser"
)

const mainName = "#MAIN#"

var mainTitle = ""

// Pretty creates a new FrontMatter object
func Pretty(content []byte) (interface{}, string, error) {
	frontType := parser.DetectFrontMatter(rune(content[0]))
	front, err := frontType.Parse(content)

	if err != nil {
		return []string{}, mainTitle, err
	}

	object := new(frontmatter)
	object.Type = "object"
	object.Name = mainName

	return rawToPretty(front, object), mainTitle, nil
}

type frontmatter struct {
	Name     string
	Title    string
	Content  interface{}
	Type     string
	HTMLType string
	Parent   *frontmatter
}

func rawToPretty(config interface{}, parent *frontmatter) interface{} {
	objects := []*frontmatter{}
	arrays := []*frontmatter{}
	fields := []*frontmatter{}

	cnf := map[string]interface{}{}

	if reflect.TypeOf(config) == reflect.TypeOf(map[interface{}]interface{}{}) {
		for key, value := range config.(map[interface{}]interface{}) {
			cnf[key.(string)] = value
		}
	} else if reflect.TypeOf(config) == reflect.TypeOf([]interface{}{}) {
		for key, value := range config.([]interface{}) {
			cnf[string(key)] = value
		}
	} else {
		cnf = config.(map[string]interface{})
	}

	for name, element := range cnf {
		if utils.IsMap(element) {
			objects = append(objects, handleObjects(element, parent, name))
		} else if utils.IsSlice(element) {
			arrays = append(arrays, handleArrays(element, parent, name))
		} else {
			if name == "title" && parent.Name == mainName {
				mainTitle = element.(string)
			}

			fields = append(fields, handleFlatValues(element, parent, name))
		}
	}

	sort.Sort(sortByTitle(objects))
	sort.Sort(sortByTitle(arrays))
	sort.Sort(sortByTitle(fields))

	settings := []*frontmatter{}
	settings = append(settings, fields...)
	settings = append(settings, arrays...)
	settings = append(settings, objects...)
	return settings
}

type sortByTitle []*frontmatter

func (f sortByTitle) Len() int      { return len(f) }
func (f sortByTitle) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f sortByTitle) Less(i, j int) bool {
	return strings.ToLower(f[i].Name) < strings.ToLower(f[j].Name)
}

func handleObjects(content interface{}, parent *frontmatter, name string) *frontmatter {
	c := new(frontmatter)
	c.Parent = parent
	c.Type = "object"
	c.Title = name

	if parent.Name == mainName {
		c.Name = c.Title
	} else {
		c.Name = parent.Name + "[" + c.Title + "]"
	}

	c.Content = rawToPretty(content, c)
	return c
}

func handleArrays(content interface{}, parent *frontmatter, name string) *frontmatter {
	c := new(frontmatter)
	c.Parent = parent
	c.Type = "array"
	c.Title = name

	if parent.Name == mainName {
		c.Name = name
	} else {
		c.Name = parent.Name + "[" + name + "]"
	}

	c.Content = rawToPretty(content, c)
	return c
}

func handleFlatValues(content interface{}, parent *frontmatter, name string) *frontmatter {
	c := new(frontmatter)
	c.Parent = parent

	switch reflect.ValueOf(content).Kind() {
	case reflect.Bool:
		c.Type = "boolean"
	case reflect.Int, reflect.Float32, reflect.Float64:
		c.Type = "number"
	default:
		c.Type = "string"
	}

	c.Content = content

	switch strings.ToLower(name) {
	case "description":
		c.HTMLType = "textarea"
	case "date", "publishdate":
		c.HTMLType = "datetime"
		c.Content = cast.ToTime(content)
	default:
		c.HTMLType = "text"
	}

	if parent.Type == "array" {
		c.Name = parent.Name + "[]"
		c.Title = content.(string)
	} else if parent.Type == "object" {
		c.Title = name
		c.Name = parent.Name + "[" + name + "]"

		if parent.Name == mainName {
			c.Name = name
		}
	} else {
		log.Panic("Parent type not allowed in handleFlatValues.")
	}

	return c
}
<commit_msg>fix bug related to objects within arrays / frontmatter<commit_after>package frontmatter

import (
	"log"
	"reflect"
	"sort"
	"strings"

	"github.com/hacdias/caddy-hugo/utils"
	"github.com/spf13/cast"
	"github.com/spf13/hugo/parser"
)

const mainName = "#MAIN#"

var mainTitle = ""

// Pretty creates a new FrontMatter object
func Pretty(content []byte) (interface{}, string, error) {
	frontType := parser.DetectFrontMatter(rune(content[0]))
	front, err := frontType.Parse(content)

	if err != nil {
		return []string{}, mainTitle, err
	}

	object := new(frontmatter)
	object.Type = "object"
	object.Name = mainName

	return rawToPretty(front, object), mainTitle, nil
}

type frontmatter struct {
	Name     string
	Title    string
	Content  interface{}
	Type     string
	HTMLType string
	Parent   *frontmatter
}

func rawToPretty(config interface{}, parent *frontmatter) interface{} {
	objects := []*frontmatter{}
	arrays := []*frontmatter{}
	fields := []*frontmatter{}

	cnf := map[string]interface{}{}

	if reflect.TypeOf(config) == reflect.TypeOf(map[interface{}]interface{}{}) {
		for key, value := range config.(map[interface{}]interface{}) {
			cnf[key.(string)] = value
		}
	} else if reflect.TypeOf(config) == reflect.TypeOf([]interface{}{}) {
		for key, value := range config.([]interface{}) {
			cnf[string(key)] = value
		}
	} else {
		cnf = config.(map[string]interface{})
	}

	for name, element := range cnf {
		if utils.IsMap(element) {
			objects = append(objects, handleObjects(element, parent, name))
		} else if utils.IsSlice(element) {
			arrays = append(arrays, handleArrays(element, parent, name))
		} else {
			if name == "title" && parent.Name == mainName {
				mainTitle = element.(string)
			}

			fields = append(fields, handleFlatValues(element, parent, name))
		}
	}

	sort.Sort(sortByTitle(objects))
	sort.Sort(sortByTitle(arrays))
	sort.Sort(sortByTitle(fields))

	settings := []*frontmatter{}
	settings = append(settings, fields...)
	settings = append(settings, arrays...)
	settings = append(settings, objects...)
	return settings
}

type sortByTitle []*frontmatter

func (f sortByTitle) Len() int      { return len(f) }
func (f sortByTitle) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f sortByTitle) Less(i, j int) bool {
	return strings.ToLower(f[i].Name) < strings.ToLower(f[j].Name)
}

func handleObjects(content interface{}, parent *frontmatter, name string) *frontmatter {
	c := new(frontmatter)
	c.Parent = parent
	c.Type = "object"
	c.Title = name

	if parent.Name == mainName {
		c.Name = c.Title
	} else if parent.Type == "array" {
		c.Name = parent.Name + "[]"
	} else {
		c.Name = parent.Name + "[" + c.Title + "]"
	}

	c.Content = rawToPretty(content, c)
	return c
}

func handleArrays(content interface{}, parent *frontmatter, name string) *frontmatter {
	c := new(frontmatter)
	c.Parent = parent
	c.Type = "array"
	c.Title = name

	if parent.Name == mainName {
		c.Name = name
	} else {
		c.Name = parent.Name + "[" + name + "]"
	}

	c.Content = rawToPretty(content, c)
	return c
}

func handleFlatValues(content interface{}, parent *frontmatter, name string) *frontmatter {
	c := new(frontmatter)
	c.Parent = parent

	switch reflect.ValueOf(content).Kind() {
	case reflect.Bool:
		c.Type = "boolean"
	case reflect.Int, reflect.Float32, reflect.Float64:
		c.Type = "number"
	default:
		c.Type = "string"
	}

	c.Content = content

	switch strings.ToLower(name) {
	case "description":
		c.HTMLType = "textarea"
	case "date", "publishdate":
		c.HTMLType = "datetime"
		c.Content = cast.ToTime(content)
	default:
		c.HTMLType = "text"
	}

	if parent.Type == "array" {
		c.Name = parent.Name + "[]"
		c.Title = content.(string)
	} else if parent.Type == "object" {
		c.Title = name
		c.Name = parent.Name + "[" + name + "]"

		if parent.Name == mainName {
			c.Name = name
		}
	} else {
		log.Panic("Parent type not allowed in handleFlatValues.")
	}

	return c
}
<|endoftext|>


<commit_before>package http

import (
	"context"
	"fmt"
	"net/http"
	nethttp "net/http"
	"net/url"

	"github.com/cloudevents/sdk-go/pkg/binding"
)

// Sender implements binding.Sender wrapping a nethttp.Client and a target URL
type Sender struct {
	// Client is the HTTP client used to send events as HTTP requests
	Client *http.Client
	// Target is the URL to send event requests to.
	Target *url.URL

	transformers binding.TransformerFactories
}

func (s *Sender) Send(ctx context.Context, m binding.Message) (err error) {
	defer func() { _ = m.Finish(err) }()
	if s.Client == nil || s.Target == nil {
		return fmt.Errorf("not initialized: %#v", s)
	}

	var req *http.Request
	req, err = http.NewRequest("POST", s.Target.String(), nil)
	if err != nil {
		return
	}
	req = req.WithContext(ctx)

	if err = EncodeHttpRequest(ctx, m, req, s.transformers); err != nil {
		return
	}
	resp, err := s.Client.Do(req)
	if err != nil {
		return
	}
	if resp.StatusCode/100 != 2 {
		return fmt.Errorf("%d %s", resp.StatusCode, nethttp.StatusText(resp.StatusCode))
	}
	return
}

// Returns a binding.Sender wrapping a nethttp.Client and a target URL
func NewSender(client *http.Client, target *url.URL, options ...SenderOptionFunc) binding.Sender {
	s := &Sender{Client: client, Target: target, transformers: make(binding.TransformerFactories, 0)}
	for _, o := range options {
		o(s)
	}
	return s
}
<commit_msg>adding the http bindings requester (#344)<commit_after>package http

import (
	"context"
	"fmt"
	"net/http"
	nethttp "net/http"
	"net/url"

	"github.com/cloudevents/sdk-go/pkg/binding"
)

// Sender implements binding.Sender wrapping a nethttp.Client and a target URL
type Sender struct {
	// Client is the HTTP client used to send events as HTTP requests
	Client *http.Client
	// Target is the URL to send event requests to.
	Target *url.URL

	transformers binding.TransformerFactories
}

func NewSender(client *http.Client, target *url.URL, options ...SenderOptionFunc) binding.Sender {
	s := &Sender{Client: client, Target: target, transformers: make(binding.TransformerFactories, 0)}
	for _, o := range options {
		o(s)
	}
	return s
}

// Confirm Sender implements binding.Requester
var _ binding.Requester = (*Sender)(nil)

// Send implements binding.Sender
func (s *Sender) Send(ctx context.Context, m binding.Message) (err error) {
	defer func() { _ = m.Finish(err) }()
	if s.Client == nil || s.Target == nil {
		return fmt.Errorf("not initialized: %#v", s)
	}

	var req *http.Request
	req, err = http.NewRequest("POST", s.Target.String(), nil)
	if err != nil {
		return
	}
	req = req.WithContext(ctx)

	if err = EncodeHttpRequest(ctx, m, req, s.transformers); err != nil {
		return
	}
	resp, err := s.Client.Do(req)
	if err != nil {
		return
	}
	if resp.StatusCode/100 != 2 {
		return fmt.Errorf("%d %s", resp.StatusCode, nethttp.StatusText(resp.StatusCode))
	}
	return
}

// Request implements binding.Requester
func (r *Sender) Request(ctx context.Context, m binding.Message) (binding.Message, error) {
	var err error
	defer func() { _ = m.Finish(err) }()
	if r.Client == nil || r.Target == nil {
		return nil, fmt.Errorf("not initialized: %#v", r)
	}

	var req *http.Request
	req, err = http.NewRequest("POST", r.Target.String(), nil)
	if err != nil {
		return nil, err
	}
	req = req.WithContext(ctx)

	if err = EncodeHttpRequest(ctx, m, req, r.transformers); err != nil {
		return nil, err
	}
	resp, err := r.Client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("%d %s", resp.StatusCode, nethttp.StatusText(resp.StatusCode))
	}

	return NewMessageFromHttpResponse(resp), nil
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//

package gdb

import (
    "fmt"
    "errors"
    "strings"
    "reflect"
    "database/sql"
    "gitee.com/johng/gf/g/util/gconv"
    "gitee.com/johng/gf/g/util/gutil"
)

// 关闭链接
func (db *Db) Close() error {
    if db.master != nil {
        if err := db.master.Close(); err == nil {
            db.master = nil
        } else {
            return err
        }
    }
    if db.slave != nil {
        if err := db.slave.Close(); err == nil {
            db.slave = nil
        } else {
            return err
        }
    }
    return nil
}

// 数据库sql查询操作，主要执行查询
func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) {
    fmt.Println(query)
    fmt.Println(args)
    p         := db.link.handleSqlBeforeExec(&query)
    rows, err := db.slave.Query(*p, args ...)
    err        = db.formatError(err, p, args...)
    if err == nil {
        return rows, nil
    }
    return nil, err
}

// 执行一条sql，并返回执行情况，主要用于非查询操作
func (db *Db) Exec(query string, args ...interface{}) (sql.Result, error) {
    p      := db.link.handleSqlBeforeExec(&query)
    r, err := db.master.Exec(*p, args ...)
    err     = db.formatError(err, p, args...)
    return r, err
}

// 格式化错误信息
func (db *Db) formatError(err error, query *string, args ...interface{}) error {
    if err != nil {
        errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
        errstr += fmt.Sprintf("DB QUERY: %s\n", *query)
        if len(args) > 0 {
            errstr += fmt.Sprintf("DB PARAM: %v\n", args)
        }
        err = errors.New(errstr)
    }
    return err
}

// 数据库查询，获取查询结果集，以列表结构返回
func (db *Db) GetAll(query string, args ...interface{}) (Result, error) {
    // 执行sql
    rows, err := db.Query(query, args ...)
    if err != nil || rows == nil {
        return nil, err
    }
    // 列名称列表
    columns, err := rows.Columns()
    if err != nil {
        return nil, err
    }
    // 返回结构组装
    values   := make([]sql.RawBytes, len(columns))
    scanArgs := make([]interface{}, len(values))
    records  := make(Result, 0)
    for i := range values {
        scanArgs[i] = &values[i]
    }
    for rows.Next() {
        err = rows.Scan(scanArgs...)
        if err != nil {
            return records, err
        }
        row := make(Record)
        // 注意col字段是一个[]byte类型(slice类型本身是一个指针)，多个记录循环时该变量指向的是同一个内存地址
        for i, col := range values {
            k := columns[i]
            v := make([]byte, len(col))
            copy(v, col)
            row[k] = v
        }
        //fmt.Printf("%p\n", row["typeid"])
        records = append(records, row)
    }
    return records, nil
}

// 数据库查询，获取查询结果记录，以关联数组结构返回
func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
    list, err := db.GetAll(query, args ...)
    if err != nil {
        return nil, err
    }
    if len(list) > 0 {
        return list[0], nil
    }
    return nil, nil
}

// 数据库查询，获取查询结果记录，自动映射数据到给定的struct对象中
func (db *Db) GetStruct(obj interface{}, query string, args ...interface{}) error {
    one, err := db.GetOne(query, args...)
    if err != nil {
        return err
    }
    return one.ToStruct(obj)
}

// 数据库查询，获取查询字段值
func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
    one, err := db.GetOne(query, args ...)
    if err != nil {
        return nil, err
    }
    for _, v := range one {
        return v, nil
    }
    return nil, nil
}

// 数据库查询，获取查询数量
func (db *Db) GetCount(query string, args ...interface{}) (int, error) {
    val, err := db.GetValue(query, args ...)
    if err != nil {
        return 0, err
    }
    return gconv.Int(val), nil
}

// 数据表查询，其中tables可以是多个联表查询语句，这种查询方式较复杂，建议使用链式操作
func (db *Db) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
    s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
    if condition != nil {
        s += fmt.Sprintf("WHERE %s ", db.formatCondition(condition))
    }
    if len(groupBy) > 0 {
        s += fmt.Sprintf("GROUP BY %s ", groupBy)
    }
    if len(orderBy) > 0 {
        s += fmt.Sprintf("ORDER BY %s ", orderBy)
    }
    if limit > 0 {
        s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
    }
    return db.GetAll(s, args ... )
}

// sql预处理，执行完成后调用返回值sql.Stmt.Exec完成sql操作
// 记得调用sql.Stmt.Close关闭操作对象
func (db *Db) Prepare(query string) (*sql.Stmt, error) {
    return db.master.Prepare(query)
}

// ping一下，判断或保持数据库链接(master)
func (db *Db) PingMaster() error {
    err := db.master.Ping();
    return err
}

// ping一下，判断或保持数据库链接(slave)
func (db *Db) PingSlave() error {
    err := db.slave.Ping();
    return err
}

// 设置数据库连接池中空闲链接的大小
func (db *Db) SetMaxIdleConns(n int) {
    db.master.SetMaxIdleConns(n);
}

// 设置数据库连接池最大打开的链接数量
func (db *Db) SetMaxOpenConns(n int) {
    db.master.SetMaxOpenConns(n);
}

// 事务操作，开启，会返回一个底层的事务操作对象链接如需要嵌套事务，那么可以使用该对象，否则请忽略
func (db *Db) Begin() (*Tx, error) {
    if tx, err := db.master.Begin(); err == nil {
        return &Tx {
            db : db,
            tx : tx,
        }, nil
    } else {
        return nil, err
    }
}

// 根据insert选项获得操作名称
func (db *Db) getInsertOperationByOption(option uint8) string {
    oper := "INSERT"
    switch option {
        case OPTION_INSERT:
        case OPTION_REPLACE:
            oper = "REPLACE"
        case OPTION_SAVE:
        case OPTION_IGNORE:
            oper = "INSERT IGNORE"
    }
    return oper
}

// insert、replace, save， ignore操作
// 0: insert:  仅仅执行写入操作，如果存在冲突的主键或者唯一索引，那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引)，那么删除后重新写入一条
// 2: save:    如果数据存在(主键或者唯一索引)，那么更新，否则写入一条新数据
// 3: ignore:  如果数据存在(主键或者唯一索引)，那么什么也不做
func (db *Db) insert(table string, data Map, option uint8) (sql.Result, error) {
    var keys   []string
    var values []string
    var params []interface{}
    for k, v := range data {
        keys   = append(keys,   db.charl + k + db.charr)
        values = append(values, "?")
        params = append(params, v)
    }
    operation := db.getInsertOperationByOption(option)
    updatestr := ""
    if option == OPTION_SAVE {
        var updates []string
        for k, _ := range data {
            updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", db.charl, k, db.charr, k))
        }
        updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
    }
    return db.Exec(
        fmt.Sprintf("%s INTO %s%s%s(%s) VALUES(%s) %s",
            operation, db.charl, table, db.charr, strings.Join(keys, ","), strings.Join(values, ","), updatestr), params...
    )
}

// CURD操作:单条数据写入, 仅仅执行写入操作，如果存在冲突的主键或者唯一索引，那么报错返回
func (db *Db) Insert(table string, data Map) (sql.Result, error) {
    return db.insert(table, data, OPTION_INSERT)
}

// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引)，那么删除后重新写入一条
func (db *Db) Replace(table string, data Map) (sql.Result, error) {
    return db.insert(table, data, OPTION_REPLACE)
}

// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引)，那么更新，否则写入一条新数据
func (db *Db) Save(table string, data Map) (sql.Result, error) {
    return db.insert(table, data, OPTION_SAVE)
}

// 批量写入数据
func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
    var keys    []string
    var values  []string
    var bvalues []string
    var params  []interface{}
    var result  sql.Result
    var size = len(list)
    // 判断长度
    if size < 1 {
        return result, errors.New("empty data list")
    }
    // 首先获取字段名称及记录长度
    for k, _ := range list[0] {
        keys   = append(keys,   k)
        values = append(values, "?")
    }
    var kstr = db.charl + strings.Join(keys, db.charl + "," + db.charr) + db.charr
    // 操作判断
    operation := db.getInsertOperationByOption(option)
    updatestr := ""
    if option == OPTION_SAVE {
        var updates []string
        for _, k := range keys {
            updates = append(updates, fmt.Sprintf("%s=VALUES(%s)", db.charl, k, db.charr, k))
        }
        updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
    }
    // 构造批量写入数据格式(注意map的遍历是无序的)
    for i := 0; i < size; i++ {
        for _, k := range keys {
            params = append(params, list[i][k])
        }
        bvalues = append(bvalues, "(" + strings.Join(values, ",") + ")")
        if len(bvalues) == batch {
            r, err := db.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s", operation, db.charl, table, db.charr, kstr, strings.Join(bvalues, ","), updatestr), params...)
            if err != nil {
                return result, err
            }
            result  = r
            bvalues = bvalues[:0]
        }
    }
    // 处理最后不构成指定批量的数据
    if len(bvalues) > 0 {
        r, err := db.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s", operation, db.charl, table, db.charr, kstr, strings.Join(bvalues, ","), updatestr), params...)
        if err != nil {
            return result, err
        }
        result = r
    }
    return result, nil
}

// CURD操作:批量数据指定批次量写入
func (db *Db) BatchInsert(table string, list List, batch int) (sql.Result, error) {
    return db.batchInsert(table, list, batch, OPTION_INSERT)
}

// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引)，那么删除后重新写入一条
func (db *Db) BatchReplace(table string, list List, batch int) (sql.Result, error) {
    return db.batchInsert(table, list, batch, OPTION_REPLACE)
}

// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引)，那么更新，否则写入一条新数据
func (db *Db) BatchSave(table string, list List, batch int) (sql.Result, error) {
    return db.batchInsert(table, list, batch, OPTION_SAVE)
}

// CURD操作:数据更新，统一采用sql预处理
// data参数支持字符串或者关联数组类型，内部会自行做判断处理
func (db *Db) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
    var params  []interface{}
    var updates string
    switch value := data.(type) {
        case string:
            updates = value
        case Map:
            var keys []string
            for k, v := range value {
                keys   = append(keys,   fmt.Sprintf("%s%s%s=?", db.charl, k, db.charr))
                params = append(params, v)
            }
            updates = strings.Join(keys,   ",")

        default:
            return nil, errors.New("invalid data type for 'data' field, string or Map expected")
    }
    for _, v := range args {
        params = append(params, gconv.String(v))
    }
    return db.Exec(fmt.Sprintf("UPDATE %s%s%s SET %s WHERE %s", db.charl, table, db.charr, updates, db.formatCondition(condition)), params...)
}

// CURD操作:删除数据
func (db *Db) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
    return db.Exec(fmt.Sprintf("DELETE FROM %s%s%s WHERE %s", db.charl, table, db.charr, db.formatCondition(condition)), args...)
}

// 格式化SQL查询条件
func (db *Db) formatCondition(condition interface{}) (where string) {
    if reflect.ValueOf(condition).Kind() == reflect.Map {
        ks := reflect.ValueOf(condition).MapKeys()
        vs := reflect.ValueOf(condition)
        for _, k := range ks {
            key   := gconv.String(k.Interface())
            value := gconv.String(vs.MapIndex(k).Interface())
            isNum := gutil.IsNumeric(value)
            if len(where) > 0 {
                where += " and "
            }
            if isNum || value == "?" {
                where += key + "=" + value
            } else {
                where += key + "='" + value + "'"
            }
        }
    } else {
        where += gconv.String(condition)
    }
    return
}

<commit_msg>去掉调试语句<commit_after>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//

package gdb

import (
    "fmt"
    "errors"
    "strings"
    "reflect"
    "database/sql"
    "gitee.com/johng/gf/g/util/gconv"
    "gitee.com/johng/gf/g/util/gutil"
)

// 关闭链接
func (db *Db) Close() error {
    if db.master != nil {
        if err := db.master.Close(); err == nil {
            db.master = nil
        } else {
            return err
        }
    }
    if db.slave != nil {
        if err := db.slave.Close(); err == nil {
            db.slave = nil
        } else {
            return err
        }
    }
    return nil
}

// 数据库sql查询操作，主要执行查询
func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) {
    p         := db.link.handleSqlBeforeExec(&query)
    rows, err := db.slave.Query(*p, args ...)
    err        = db.formatError(err, p, args...)
    if err == nil {
        return rows, nil
    }
    return nil, err
}

// 执行一条sql，并返回执行情况，主要用于非查询操作
func (db *Db) Exec(query string, args ...interface{}) (sql.Result, error) {
    p      := db.link.handleSqlBeforeExec(&query)
    r, err := db.master.Exec(*p, args ...)
    err     = db.formatError(err, p, args...)
    return r, err
}

// 格式化错误信息
func (db *Db) formatError(err error, query *string, args ...interface{}) error {
    if err != nil {
        errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
        errstr += fmt.Sprintf("DB QUERY: %s\n", *query)
        if len(args) > 0 {
            errstr += fmt.Sprintf("DB PARAM: %v\n", args)
        }
        err = errors.New(errstr)
    }
    return err
}

// 数据库查询，获取查询结果集，以列表结构返回
func (db *Db) GetAll(query string, args ...interface{}) (Result, error) {
    // 执行sql
    rows, err := db.Query(query, args ...)
    if err != nil || rows == nil {
        return nil, err
    }
    // 列名称列表
    columns, err := rows.Columns()
    if err != nil {
        return nil, err
    }
    // 返回结构组装
    values   := make([]sql.RawBytes, len(columns))
    scanArgs := make([]interface{}, len(values))
    records  := make(Result, 0)
    for i := range values {
        scanArgs[i] = &values[i]
    }
    for rows.Next() {
        err = rows.Scan(scanArgs...)
        if err != nil {
            return records, err
        }
        row := make(Record)
        // 注意col字段是一个[]byte类型(slice类型本身是一个指针)，多个记录循环时该变量指向的是同一个内存地址
        for i, col := range values {
            k := columns[i]
            v := make([]byte, len(col))
            copy(v, col)
            row[k] = v
        }
        //fmt.Printf("%p\n", row["typeid"])
        records = append(records, row)
    }
    return records, nil
}

// 数据库查询，获取查询结果记录，以关联数组结构返回
func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
    list, err := db.GetAll(query, args ...)
    if err != nil {
        return nil, err
    }
    if len(list) > 0 {
        return list[0], nil
    }
    return nil, nil
}

// 数据库查询，获取查询结果记录，自动映射数据到给定的struct对象中
func (db *Db) GetStruct(obj interface{}, query string, args ...interface{}) error {
    one, err := db.GetOne(query, args...)
    if err != nil {
        return err
    }
    return one.ToStruct(obj)
}

// 数据库查询，获取查询字段值
func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
    one, err := db.GetOne(query, args ...)
    if err != nil {
        return nil, err
    }
    for _, v := range one {
        return v, nil
    }
    return nil, nil
}

// 数据库查询，获取查询数量
func (db *Db) GetCount(query string, args ...interface{}) (int, error) {
    val, err := db.GetValue(query, args ...)
    if err != nil {
        return 0, err
    }
    return gconv.Int(val), nil
}

// 数据表查询，其中tables可以是多个联表查询语句，这种查询方式较复杂，建议使用链式操作
func (db *Db) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
    s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
    if condition != nil {
        s += fmt.Sprintf("WHERE %s ", db.formatCondition(condition))
    }
    if len(groupBy) > 0 {
        s += fmt.Sprintf("GROUP BY %s ", groupBy)
    }
    if len(orderBy) > 0 {
        s += fmt.Sprintf("ORDER BY %s ", orderBy)
    }
    if limit > 0 {
        s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
    }
    return db.GetAll(s, args ... )
}

// sql预处理，执行完成后调用返回值sql.Stmt.Exec完成sql操作
// 记得调用sql.Stmt.Close关闭操作对象
func (db *Db) Prepare(query string) (*sql.Stmt, error) {
    return db.master.Prepare(query)
}

// ping一下，判断或保持数据库链接(master)
func (db *Db) PingMaster() error {
    err := db.master.Ping();
    return err
}

// ping一下，判断或保持数据库链接(slave)
func (db *Db) PingSlave() error {
    err := db.slave.Ping();
    return err
}

// 设置数据库连接池中空闲链接的大小
func (db *Db) SetMaxIdleConns(n int) {
    db.master.SetMaxIdleConns(n);
}

// 设置数据库连接池最大打开的链接数量
func (db *Db) SetMaxOpenConns(n int) {
    db.master.SetMaxOpenConns(n);
}

// 事务操作，开启，会返回一个底层的事务操作对象链接如需要嵌套事务，那么可以使用该对象，否则请忽略
func (db *Db) Begin() (*Tx, error) {
    if tx, err := db.master.Begin(); err == nil {
        return &Tx {
            db : db,
            tx : tx,
        }, nil
    } else {
        return nil, err
    }
}

// 根据insert选项获得操作名称
func (db *Db) getInsertOperationByOption(option uint8) string {
    oper := "INSERT"
    switch option {
        case OPTION_INSERT:
        case OPTION_REPLACE:
            oper = "REPLACE"
        case OPTION_SAVE:
        case OPTION_IGNORE:
            oper = "INSERT IGNORE"
    }
    return oper
}

// insert、replace, save， ignore操作
// 0: insert:  仅仅执行写入操作，如果存在冲突的主键或者唯一索引，那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引)，那么删除后重新写入一条
// 2: save:    如果数据存在(主键或者唯一索引)，那么更新，否则写入一条新数据
// 3: ignore:  如果数据存在(主键或者唯一索引)，那么什么也不做
func (db *Db) insert(table string, data Map, option uint8) (sql.Result, error) {
    var keys   []string
    var values []string
    var params []interface{}
    for k, v := range data {
        keys   = append(keys,   db.charl + k + db.charr)
        values = append(values, "?")
        params = append(params, v)
    }
    operation := db.getInsertOperationByOption(option)
    updatestr := ""
    if option == OPTION_SAVE {
        var updates []string
        for k, _ := range data {
            updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", db.charl, k, db.charr, k))
        }
        updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
    }
    return db.Exec(
        fmt.Sprintf("%s INTO %s%s%s(%s) VALUES(%s) %s",
            operation, db.charl, table, db.charr, strings.Join(keys, ","), strings.Join(values, ","), updatestr), params...
    )
}

// CURD操作:单条数据写入, 仅仅执行写入操作，如果存在冲突的主键或者唯一索引，那么报错返回
func (db *Db) Insert(table string, data Map) (sql.Result, error) {
    return db.insert(table, data, OPTION_INSERT)
}

// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引)，那么删除后重新写入一条
func (db *Db) Replace(table string, data Map) (sql.Result, error) {
    return db.insert(table, data, OPTION_REPLACE)
}

// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引)，那么更新，否则写入一条新数据
func (db *Db) Save(table string, data Map) (sql.Result, error) {
    return db.insert(table, data, OPTION_SAVE)
}

// 批量写入数据
func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
    var keys    []string
    var values  []string
    var bvalues []string
    var params  []interface{}
    var result  sql.Result
    var size = len(list)
    // 判断长度
    if size < 1 {
        return result, errors.New("empty data list")
    }
    // 首先获取字段名称及记录长度
    for k, _ := range list[0] {
        keys   = append(keys,   k)
        values = append(values, "?")
    }
    var kstr = db.charl + strings.Join(keys, db.charl + "," + db.charr) + db.charr
    // 操作判断
    operation := db.getInsertOperationByOption(option)
    updatestr := ""
    if option == OPTION_SAVE {
        var updates []string
        for _, k := range keys {
            updates = append(updates, fmt.Sprintf("%s=VALUES(%s)", db.charl, k, db.charr, k))
        }
        updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
    }
    // 构造批量写入数据格式(注意map的遍历是无序的)
    for i := 0; i < size; i++ {
        for _, k := range keys {
            params = append(params, list[i][k])
        }
        bvalues = append(bvalues, "(" + strings.Join(values, ",") + ")")
        if len(bvalues) == batch {
            r, err := db.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s", operation, db.charl, table, db.charr, kstr, strings.Join(bvalues, ","), updatestr), params...)
            if err != nil {
                return result, err
            }
            result  = r
            bvalues = bvalues[:0]
        }
    }
    // 处理最后不构成指定批量的数据
    if len(bvalues) > 0 {
        r, err := db.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s", operation, db.charl, table, db.charr, kstr, strings.Join(bvalues, ","), updatestr), params...)
        if err != nil {
            return result, err
        }
        result = r
    }
    return result, nil
}

// CURD操作:批量数据指定批次量写入
func (db *Db) BatchInsert(table string, list List, batch int) (sql.Result, error) {
    return db.batchInsert(table, list, batch, OPTION_INSERT)
}

// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引)，那么删除后重新写入一条
func (db *Db) BatchReplace(table string, list List, batch int) (sql.Result, error) {
    return db.batchInsert(table, list, batch, OPTION_REPLACE)
}

// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引)，那么更新，否则写入一条新数据
func (db *Db) BatchSave(table string, list List, batch int) (sql.Result, error) {
    return db.batchInsert(table, list, batch, OPTION_SAVE)
}

// CURD操作:数据更新，统一采用sql预处理
// data参数支持字符串或者关联数组类型，内部会自行做判断处理
func (db *Db) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
    var params  []interface{}
    var updates string
    switch value := data.(type) {
        case string:
            updates = value
        case Map:
            var keys []string
            for k, v := range value {
                keys   = append(keys,   fmt.Sprintf("%s%s%s=?", db.charl, k, db.charr))
                params = append(params, v)
            }
            updates = strings.Join(keys,   ",")

        default:
            return nil, errors.New("invalid data type for 'data' field, string or Map expected")
    }
    for _, v := range args {
        params = append(params, gconv.String(v))
    }
    return db.Exec(fmt.Sprintf("UPDATE %s%s%s SET %s WHERE %s", db.charl, table, db.charr, updates, db.formatCondition(condition)), params...)
}

// CURD操作:删除数据
func (db *Db) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
    return db.Exec(fmt.Sprintf("DELETE FROM %s%s%s WHERE %s", db.charl, table, db.charr, db.formatCondition(condition)), args...)
}

// 格式化SQL查询条件
func (db *Db) formatCondition(condition interface{}) (where string) {
    if reflect.ValueOf(condition).Kind() == reflect.Map {
        ks := reflect.ValueOf(condition).MapKeys()
        vs := reflect.ValueOf(condition)
        for _, k := range ks {
            key   := gconv.String(k.Interface())
            value := gconv.String(vs.MapIndex(k).Interface())
            isNum := gutil.IsNumeric(value)
            if len(where) > 0 {
                where += " and "
            }
            if isNum || value == "?" {
                where += key + "=" + value
            } else {
                where += key + "='" + value + "'"
            }
        }
    } else {
        where += gconv.String(condition)
    }
    return
}

<|endoftext|>