WIP update deps, sql builder instead of jet
This commit is contained in:
4
vendor/github.com/go-co-op/gocron/v2/.golangci.yaml
generated
vendored
4
vendor/github.com/go-co-op/gocron/v2/.golangci.yaml
generated
vendored
@@ -20,10 +20,9 @@ issues:
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- exportloopref
|
||||
- copyloopvar
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
@@ -39,6 +38,5 @@ output:
|
||||
- format: colored-line-number
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
uniq-by-line: true
|
||||
path-prefix: ""
|
||||
sort-results: true
|
||||
|
||||
2
vendor/github.com/go-co-op/gocron/v2/.pre-commit-config.yaml
generated
vendored
2
vendor/github.com/go-co-op/gocron/v2/.pre-commit-config.yaml
generated
vendored
@@ -12,7 +12,7 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.61.0
|
||||
rev: v1.64.5
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/TekWizely/pre-commit-golang
|
||||
|
||||
2
vendor/github.com/go-co-op/gocron/v2/Makefile
generated
vendored
2
vendor/github.com/go-co-op/gocron/v2/Makefile
generated
vendored
@@ -16,7 +16,7 @@ test_coverage:
|
||||
@go test -race -v $(GO_FLAGS) -count=1 -coverprofile=coverage.out -covermode=atomic $(GO_PKGS)
|
||||
|
||||
test_ci:
|
||||
@TEST_ENV=ci go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS)
|
||||
@go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS)
|
||||
|
||||
mocks:
|
||||
@go generate ./...
|
||||
|
||||
9
vendor/github.com/go-co-op/gocron/v2/README.md
generated
vendored
9
vendor/github.com/go-co-op/gocron/v2/README.md
generated
vendored
@@ -170,11 +170,20 @@ We appreciate the support for free and open source software!
|
||||
This project is supported by:
|
||||
|
||||
[Jetbrains](https://www.jetbrains.com/?from=gocron)
|
||||
|
||||

|
||||
|
||||
|
||||
[Sentry](https://sentry.io/welcome/)
|
||||
|
||||
<p align="left">
|
||||
<p align="left">
|
||||
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
|
||||
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#go-co-op/gocron&Date)
|
||||
|
||||
13
vendor/github.com/go-co-op/gocron/v2/executor.go
generated
vendored
13
vendor/github.com/go-co-op/gocron/v2/executor.go
generated
vendored
@@ -31,6 +31,9 @@ type executor struct {
|
||||
// used to request jobs from the scheduler
|
||||
jobOutRequest chan jobOutRequest
|
||||
|
||||
// sends out job needs to update the next runs
|
||||
jobUpdateNextRuns chan uuid.UUID
|
||||
|
||||
// used by the executor to receive a stop signal from the scheduler
|
||||
stopCh chan struct{}
|
||||
// the timeout value when stopping
|
||||
@@ -247,6 +250,14 @@ func (e *executor) sendOutForRescheduling(jIn *jobIn) {
|
||||
jIn.shouldSendOut = false
|
||||
}
|
||||
|
||||
func (e *executor) sendOutForNextRunUpdate(jIn *jobIn) {
|
||||
select {
|
||||
case e.jobUpdateNextRuns <- jIn.id:
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) limitModeRunner(name string, in chan jobIn, wg *waitGroupWithMutex, limitMode LimitMode, rescheduleLimiter chan struct{}) {
|
||||
e.logger.Debug("gocron: limitModeRunner starting", "name", name)
|
||||
for {
|
||||
@@ -376,6 +387,7 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
|
||||
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
e.sendOutForNextRunUpdate(&jIn)
|
||||
return
|
||||
}
|
||||
defer func() { _ = lock.Unlock(j.ctx) }()
|
||||
@@ -385,6 +397,7 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
|
||||
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
e.sendOutForNextRunUpdate(&jIn)
|
||||
return
|
||||
}
|
||||
defer func() { _ = lock.Unlock(j.ctx) }()
|
||||
|
||||
93
vendor/github.com/go-co-op/gocron/v2/job.go
generated
vendored
93
vendor/github.com/go-co-op/gocron/v2/job.go
generated
vendored
@@ -6,13 +6,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/robfig/cron/v3"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// internalJob stores the information needed by the scheduler
|
||||
@@ -24,6 +24,7 @@ type internalJob struct {
|
||||
id uuid.UUID
|
||||
name string
|
||||
tags []string
|
||||
cron Cron
|
||||
jobSchedule
|
||||
|
||||
// as some jobs may queue up, it's possible to
|
||||
@@ -104,6 +105,20 @@ type limitRunsTo struct {
|
||||
runCount uint
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// --------------- Custom Cron -------------------
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
|
||||
// Cron defines the interface that must be
|
||||
// implemented to provide a custom cron implementation for
|
||||
// the job. Pass in the implementation using the JobOption WithCronImplementation.
|
||||
type Cron interface {
|
||||
IsValid(crontab string, location *time.Location, now time.Time) error
|
||||
Next(lastRun time.Time) time.Time
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// --------------- Job Variants ------------------
|
||||
@@ -116,21 +131,29 @@ type JobDefinition interface {
|
||||
setup(j *internalJob, l *time.Location, now time.Time) error
|
||||
}
|
||||
|
||||
var _ JobDefinition = (*cronJobDefinition)(nil)
|
||||
// Default cron implementation
|
||||
|
||||
type cronJobDefinition struct {
|
||||
crontab string
|
||||
withSeconds bool
|
||||
func newDefaultCronImplementation(withSeconds bool) Cron {
|
||||
return &defaultCron{
|
||||
withSeconds: withSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now time.Time) error {
|
||||
var _ Cron = (*defaultCron)(nil)
|
||||
|
||||
type defaultCron struct {
|
||||
cronSchedule cron.Schedule
|
||||
withSeconds bool
|
||||
}
|
||||
|
||||
func (c *defaultCron) IsValid(crontab string, location *time.Location, now time.Time) error {
|
||||
var withLocation string
|
||||
if strings.HasPrefix(c.crontab, "TZ=") || strings.HasPrefix(c.crontab, "CRON_TZ=") {
|
||||
withLocation = c.crontab
|
||||
if strings.HasPrefix(crontab, "TZ=") || strings.HasPrefix(crontab, "CRON_TZ=") {
|
||||
withLocation = crontab
|
||||
} else {
|
||||
// since the user didn't provide a timezone default to the location
|
||||
// passed in by the scheduler. Default: time.Local
|
||||
withLocation = fmt.Sprintf("CRON_TZ=%s %s", location.String(), c.crontab)
|
||||
withLocation = fmt.Sprintf("CRON_TZ=%s %s", location.String(), crontab)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -150,8 +173,32 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now ti
|
||||
if cronSchedule.Next(now).IsZero() {
|
||||
return ErrCronJobInvalid
|
||||
}
|
||||
c.cronSchedule = cronSchedule
|
||||
return nil
|
||||
}
|
||||
|
||||
j.jobSchedule = &cronJob{cronSchedule: cronSchedule}
|
||||
func (c *defaultCron) Next(lastRun time.Time) time.Time {
|
||||
return c.cronSchedule.Next(lastRun)
|
||||
}
|
||||
|
||||
// default cron job implementation
|
||||
var _ JobDefinition = (*cronJobDefinition)(nil)
|
||||
|
||||
type cronJobDefinition struct {
|
||||
crontab string
|
||||
cron Cron
|
||||
}
|
||||
|
||||
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now time.Time) error {
|
||||
if j.cron != nil {
|
||||
c.cron = j.cron
|
||||
}
|
||||
|
||||
if err := c.cron.IsValid(c.crontab, location, now); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
j.jobSchedule = &cronJob{crontab: c.crontab, cronSchedule: c.cron}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -163,8 +210,8 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now ti
|
||||
// `CRON_TZ=America/Chicago * * * * *`
|
||||
func CronJob(crontab string, withSeconds bool) JobDefinition {
|
||||
return cronJobDefinition{
|
||||
crontab: crontab,
|
||||
withSeconds: withSeconds,
|
||||
crontab: crontab,
|
||||
cron: newDefaultCronImplementation(withSeconds),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,11 +416,9 @@ func (m monthlyJobDefinition) setup(j *internalJob, location *time.Location, _ t
|
||||
}
|
||||
}
|
||||
daysStart = removeSliceDuplicatesInt(daysStart)
|
||||
slices.Sort(daysStart)
|
||||
ms.days = daysStart
|
||||
|
||||
daysEnd = removeSliceDuplicatesInt(daysEnd)
|
||||
slices.Sort(daysEnd)
|
||||
ms.daysFromEnd = daysEnd
|
||||
|
||||
atTimesDate, err := convertAtTimesToDateTime(m.atTimes, location)
|
||||
@@ -610,6 +655,15 @@ func WithName(name string) JobOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithCronImplementation sets the custom Cron implementation for the job.
|
||||
// This is only utilized for the CronJob type.
|
||||
func WithCronImplementation(c Cron) JobOption {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
j.cron = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSingletonMode keeps the job from running again if it is already running.
|
||||
// This is useful for jobs that should not overlap, and that occasionally
|
||||
// (but not consistently) run longer than the interval between job runs.
|
||||
@@ -820,7 +874,8 @@ type jobSchedule interface {
|
||||
var _ jobSchedule = (*cronJob)(nil)
|
||||
|
||||
type cronJob struct {
|
||||
cronSchedule cron.Schedule
|
||||
crontab string
|
||||
cronSchedule Cron
|
||||
}
|
||||
|
||||
func (j *cronJob) next(lastRun time.Time) time.Time {
|
||||
@@ -864,7 +919,7 @@ func (d dailyJob) next(lastRun time.Time) time.Time {
|
||||
}
|
||||
firstPass = false
|
||||
|
||||
startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, lastRun.Nanosecond(), lastRun.Location())
|
||||
startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, 0, lastRun.Location())
|
||||
return d.nextDay(startNextDay, firstPass)
|
||||
}
|
||||
|
||||
@@ -872,7 +927,7 @@ func (d dailyJob) nextDay(lastRun time.Time, firstPass bool) time.Time {
|
||||
for _, at := range d.atTimes {
|
||||
// sub the at time hour/min/sec onto the lastScheduledRun's values
|
||||
// to use in checks to see if we've got our next run time
|
||||
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location())
|
||||
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
|
||||
|
||||
if firstPass && atDate.After(lastRun) {
|
||||
// checking to see if it is after i.e. greater than,
|
||||
@@ -918,7 +973,7 @@ func (w weeklyJob) nextWeekDayAtTime(lastRun time.Time, firstPass bool) time.Tim
|
||||
for _, at := range w.atTimes {
|
||||
// sub the at time hour/min/sec onto the lastScheduledRun's values
|
||||
// to use in checks to see if we've got our next run time
|
||||
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(weekDayDiff), at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location())
|
||||
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(weekDayDiff), at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
|
||||
|
||||
if firstPass && atDate.After(lastRun) {
|
||||
// checking to see if it is after i.e. greater than,
|
||||
@@ -986,7 +1041,7 @@ func (m monthlyJob) nextMonthDayAtTime(lastRun time.Time, days []int, firstPass
|
||||
for _, at := range m.atTimes {
|
||||
// sub the day, and the at time hour/min/sec onto the lastScheduledRun's values
|
||||
// to use in checks to see if we've got our next run time
|
||||
atDate := time.Date(lastRun.Year(), lastRun.Month(), day, at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location())
|
||||
atDate := time.Date(lastRun.Year(), lastRun.Month(), day, at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
|
||||
|
||||
if atDate.Month() != lastRun.Month() {
|
||||
// this check handles if we're setting a day not in the current month
|
||||
|
||||
63
vendor/github.com/go-co-op/gocron/v2/scheduler.go
generated
vendored
63
vendor/github.com/go-co-op/gocron/v2/scheduler.go
generated
vendored
@@ -5,11 +5,12 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var _ Scheduler = (*scheduler)(nil)
|
||||
@@ -137,6 +138,7 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
|
||||
|
||||
jobsIn: make(chan jobIn),
|
||||
jobsOutForRescheduling: make(chan uuid.UUID),
|
||||
jobUpdateNextRuns: make(chan uuid.UUID),
|
||||
jobsOutCompleted: make(chan uuid.UUID),
|
||||
jobOutRequest: make(chan jobOutRequest, 1000),
|
||||
done: make(chan error),
|
||||
@@ -175,7 +177,8 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
|
||||
select {
|
||||
case id := <-s.exec.jobsOutForRescheduling:
|
||||
s.selectExecJobsOutForRescheduling(id)
|
||||
|
||||
case id := <-s.exec.jobUpdateNextRuns:
|
||||
s.updateNextScheduled(id)
|
||||
case id := <-s.exec.jobsOutCompleted:
|
||||
s.selectExecJobsOutCompleted(id)
|
||||
|
||||
@@ -237,11 +240,8 @@ func (s *scheduler) stopScheduler() {
|
||||
for _, j := range s.jobs {
|
||||
j.stop()
|
||||
}
|
||||
for id, j := range s.jobs {
|
||||
for _, j := range s.jobs {
|
||||
<-j.ctx.Done()
|
||||
|
||||
j.ctx, j.cancel = context.WithCancel(s.shutdownCtx)
|
||||
s.jobs[id] = j
|
||||
}
|
||||
var err error
|
||||
if s.started {
|
||||
@@ -253,6 +253,21 @@ func (s *scheduler) stopScheduler() {
|
||||
err = ErrStopExecutorTimedOut
|
||||
}
|
||||
}
|
||||
for id, j := range s.jobs {
|
||||
oldCtx := j.ctx
|
||||
if j.parentCtx == nil {
|
||||
j.parentCtx = s.shutdownCtx
|
||||
}
|
||||
j.ctx, j.cancel = context.WithCancel(j.parentCtx)
|
||||
|
||||
// also replace the old context with the new one in the parameters
|
||||
if len(j.parameters) > 0 && j.parameters[0] == oldCtx {
|
||||
j.parameters[0] = j.ctx
|
||||
}
|
||||
|
||||
s.jobs[id] = j
|
||||
}
|
||||
|
||||
s.stopErrCh <- err
|
||||
s.started = false
|
||||
s.logger.Debug("gocron: scheduler stopped")
|
||||
@@ -267,14 +282,7 @@ func (s *scheduler) selectAllJobsOutRequest(out allJobsOutRequest) {
|
||||
}
|
||||
slices.SortFunc(outJobs, func(a, b Job) int {
|
||||
aID, bID := a.ID().String(), b.ID().String()
|
||||
switch {
|
||||
case aID < bID:
|
||||
return -1
|
||||
case aID > bID:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
return strings.Compare(aID, bID)
|
||||
})
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
@@ -335,7 +343,7 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
|
||||
return
|
||||
}
|
||||
|
||||
scheduleFrom := j.lastRun
|
||||
var scheduleFrom time.Time
|
||||
if len(j.nextScheduled) > 0 {
|
||||
// always grab the last element in the slice as that is the furthest
|
||||
// out in the future and the time from which we want to calculate
|
||||
@@ -366,6 +374,15 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(j.nextScheduled, next) {
|
||||
// if the next value is a duplicate of what's already in the nextScheduled slice, for example:
|
||||
// - the job is being rescheduled off the same next run value as before
|
||||
// increment to the next, next value
|
||||
for slices.Contains(j.nextScheduled, next) {
|
||||
next = j.next(next)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any existing timer to prevent leaks
|
||||
if j.timer != nil {
|
||||
j.timer.Stop()
|
||||
@@ -390,6 +407,22 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
|
||||
s.jobs[id] = j
|
||||
}
|
||||
|
||||
func (s *scheduler) updateNextScheduled(id uuid.UUID) {
|
||||
j, ok := s.jobs[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var newNextScheduled []time.Time
|
||||
for _, t := range j.nextScheduled {
|
||||
if t.Before(s.now()) {
|
||||
continue
|
||||
}
|
||||
newNextScheduled = append(newNextScheduled, t)
|
||||
}
|
||||
j.nextScheduled = newNextScheduled
|
||||
s.jobs[id] = j
|
||||
}
|
||||
|
||||
func (s *scheduler) selectExecJobsOutCompleted(id uuid.UUID) {
|
||||
j, ok := s.jobs[id]
|
||||
if !ok {
|
||||
|
||||
11
vendor/github.com/go-co-op/gocron/v2/util.go
generated
vendored
11
vendor/github.com/go-co-op/gocron/v2/util.go
generated
vendored
@@ -3,12 +3,11 @@ package gocron
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func callJobFuncWithParams(jobFunc any, params ...any) error {
|
||||
@@ -63,12 +62,8 @@ func requestJobCtx(ctx context.Context, id uuid.UUID, ch chan jobOutRequest) *in
|
||||
}
|
||||
|
||||
func removeSliceDuplicatesInt(in []int) []int {
|
||||
m := make(map[int]struct{})
|
||||
|
||||
for _, i := range in {
|
||||
m[i] = struct{}{}
|
||||
}
|
||||
return maps.Keys(m)
|
||||
slices.Sort(in)
|
||||
return slices.Compact(in)
|
||||
}
|
||||
|
||||
func convertAtTimesToDateTime(atTimes AtTimes, location *time.Location) ([]time.Time, error) {
|
||||
|
||||
81
vendor/github.com/gomarkdown/markdown/parser/block.go
generated
vendored
81
vendor/github.com/gomarkdown/markdown/parser/block.go
generated
vendored
@@ -951,8 +951,6 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
|
||||
work.WriteByte('\n')
|
||||
|
||||
for {
|
||||
// safe to assume beg < len(data)
|
||||
|
||||
// check for the end of the code block
|
||||
fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
|
||||
if fenceEnd != 0 {
|
||||
@@ -969,48 +967,47 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
|
||||
}
|
||||
|
||||
// verbatim copy to the working buffer
|
||||
if doRender {
|
||||
work.Write(data[beg:end])
|
||||
}
|
||||
work.Write(data[beg:end])
|
||||
beg = end
|
||||
}
|
||||
|
||||
if doRender {
|
||||
codeBlock := &ast.CodeBlock{
|
||||
IsFenced: true,
|
||||
}
|
||||
codeBlock.Content = work.Bytes() // TODO: get rid of temp buffer
|
||||
if !doRender {
|
||||
return beg
|
||||
}
|
||||
codeBlock := &ast.CodeBlock{
|
||||
IsFenced: true,
|
||||
}
|
||||
codeBlock.Content = work.Bytes() // TODO: get rid of temp buffer
|
||||
|
||||
if p.extensions&Mmark == 0 {
|
||||
p.AddBlock(codeBlock)
|
||||
finalizeCodeBlock(codeBlock)
|
||||
return beg
|
||||
}
|
||||
|
||||
// Check for caption and if found make it a figure.
|
||||
if captionContent, id, consumed := p.caption(data[beg:], []byte(captionFigure)); consumed > 0 {
|
||||
figure := &ast.CaptionFigure{}
|
||||
caption := &ast.Caption{}
|
||||
figure.HeadingID = id
|
||||
p.Inline(caption, captionContent)
|
||||
|
||||
p.AddBlock(figure)
|
||||
codeBlock.AsLeaf().Attribute = figure.AsContainer().Attribute
|
||||
p.addChild(codeBlock)
|
||||
finalizeCodeBlock(codeBlock)
|
||||
p.addChild(caption)
|
||||
p.Finalize(figure)
|
||||
|
||||
beg += consumed
|
||||
|
||||
return beg
|
||||
}
|
||||
|
||||
// Still here, normal block
|
||||
if p.extensions&Mmark == 0 {
|
||||
p.AddBlock(codeBlock)
|
||||
finalizeCodeBlock(codeBlock)
|
||||
return beg
|
||||
}
|
||||
|
||||
// Check for caption and if found make it a figure.
|
||||
if captionContent, id, consumed := p.caption(data[beg:], []byte(captionFigure)); consumed > 0 {
|
||||
figure := &ast.CaptionFigure{}
|
||||
caption := &ast.Caption{}
|
||||
figure.HeadingID = id
|
||||
p.Inline(caption, captionContent)
|
||||
|
||||
p.AddBlock(figure)
|
||||
codeBlock.AsLeaf().Attribute = figure.AsContainer().Attribute
|
||||
p.addChild(codeBlock)
|
||||
finalizeCodeBlock(codeBlock)
|
||||
p.addChild(caption)
|
||||
p.Finalize(figure)
|
||||
|
||||
beg += consumed
|
||||
|
||||
return beg
|
||||
}
|
||||
|
||||
// Still here, normal block
|
||||
p.AddBlock(codeBlock)
|
||||
finalizeCodeBlock(codeBlock)
|
||||
|
||||
return beg
|
||||
}
|
||||
|
||||
@@ -1353,6 +1350,7 @@ func finalizeList(list *ast.List) {
|
||||
// Parse a single list item.
|
||||
// Assumes initial prefix is already removed if this is a sublist.
|
||||
func (p *Parser) listItem(data []byte, flags *ast.ListType) int {
|
||||
isDefinitionList := *flags&ast.ListTypeDefinition != 0
|
||||
// keep track of the indentation of the first line
|
||||
itemIndent := 0
|
||||
if data[0] == '\t' {
|
||||
@@ -1385,7 +1383,7 @@ func (p *Parser) listItem(data []byte, flags *ast.ListType) int {
|
||||
}
|
||||
if i == 0 {
|
||||
// if in definition list, set term flag and continue
|
||||
if *flags&ast.ListTypeDefinition != 0 {
|
||||
if isDefinitionList {
|
||||
*flags |= ast.ListTypeTerm
|
||||
} else {
|
||||
return 0
|
||||
@@ -1446,7 +1444,14 @@ gatherlines:
|
||||
|
||||
// If there is a fence line (marking starting of a code block)
|
||||
// without indent do not process it as part of the list.
|
||||
if p.extensions&FencedCode != 0 {
|
||||
//
|
||||
// does not apply for definition lists because it causes infinite
|
||||
// loop if text before defintion term is fenced code block start
|
||||
// marker but not part of actual fenced code block
|
||||
// for defnition lists we're called after parsing fence code blocks
|
||||
// so we kno this cannot be a fenced block
|
||||
// https://github.com/gomarkdown/markdown/issues/326
|
||||
if !isDefinitionList && p.extensions&FencedCode != 0 {
|
||||
fenceLineEnd, _ := isFenceLine(chunk, nil, "")
|
||||
if fenceLineEnd > 0 && indent == 0 {
|
||||
*flags |= ast.ListItemEndOfList
|
||||
|
||||
27
vendor/github.com/gomarkdown/markdown/parser/inline.go
generated
vendored
27
vendor/github.com/gomarkdown/markdown/parser/inline.go
generated
vendored
@@ -271,7 +271,7 @@ func maybeInlineFootnoteOrSuper(p *Parser, data []byte, offset int) (int, ast.No
|
||||
// '[': parse a link or an image or a footnote or a citation
|
||||
func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
// no links allowed inside regular links, footnote, and deferred footnotes
|
||||
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
|
||||
if p.InsideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -362,25 +362,27 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
linkB := i
|
||||
brace := 0
|
||||
|
||||
var c byte
|
||||
// look for link end: ' " )
|
||||
findlinkend:
|
||||
for i < len(data) {
|
||||
c = data[i]
|
||||
switch {
|
||||
case data[i] == '\\':
|
||||
case c == '\\':
|
||||
i += 2
|
||||
|
||||
case data[i] == '(':
|
||||
case c == '(':
|
||||
brace++
|
||||
i++
|
||||
|
||||
case data[i] == ')':
|
||||
case c == ')':
|
||||
if brace <= 0 {
|
||||
break findlinkend
|
||||
}
|
||||
brace--
|
||||
i++
|
||||
|
||||
case data[i] == '\'' || data[i] == '"':
|
||||
case c == '\'' || c == '"':
|
||||
break findlinkend
|
||||
|
||||
default:
|
||||
@@ -402,14 +404,15 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
|
||||
findtitleend:
|
||||
for i < len(data) {
|
||||
c = data[i]
|
||||
switch {
|
||||
case data[i] == '\\':
|
||||
case c == '\\':
|
||||
i++
|
||||
|
||||
case data[i] == data[titleB-1]: // matching title delimiter
|
||||
case c == data[titleB-1]: // matching title delimiter
|
||||
titleEndCharFound = true
|
||||
|
||||
case titleEndCharFound && data[i] == ')':
|
||||
case titleEndCharFound && c == ')':
|
||||
break findtitleend
|
||||
}
|
||||
i++
|
||||
@@ -619,10 +622,10 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
} else {
|
||||
// links cannot contain other links, so turn off link parsing
|
||||
// temporarily and recurse
|
||||
insideLink := p.insideLink
|
||||
p.insideLink = true
|
||||
InsideLink := p.InsideLink
|
||||
p.InsideLink = true
|
||||
p.Inline(link, data[1:txtE])
|
||||
p.insideLink = insideLink
|
||||
p.InsideLink = InsideLink
|
||||
}
|
||||
return i, link
|
||||
|
||||
@@ -857,7 +860,7 @@ const shortestPrefix = 6 // len("ftp://"), the shortest of the above
|
||||
|
||||
func maybeAutoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
// quick check to rule out most false hits
|
||||
if p.insideLink || len(data) < offset+shortestPrefix {
|
||||
if p.InsideLink || len(data) < offset+shortestPrefix {
|
||||
return 0, nil
|
||||
}
|
||||
for _, prefix := range protocolPrefixes {
|
||||
|
||||
4
vendor/github.com/gomarkdown/markdown/parser/parser.go
generated
vendored
4
vendor/github.com/gomarkdown/markdown/parser/parser.go
generated
vendored
@@ -103,7 +103,7 @@ type Parser struct {
|
||||
inlineCallback [256]InlineParser
|
||||
nesting int
|
||||
maxNesting int
|
||||
insideLink bool
|
||||
InsideLink bool
|
||||
indexCnt int // incremented after every index
|
||||
|
||||
// Footnotes need to be ordered as well as available to quickly check for
|
||||
@@ -143,7 +143,7 @@ func NewWithExtensions(extension Extensions) *Parser {
|
||||
refs: make(map[string]*reference),
|
||||
refsRecord: make(map[string]struct{}),
|
||||
maxNesting: 64,
|
||||
insideLink: false,
|
||||
InsideLink: false,
|
||||
Doc: &ast.Document{},
|
||||
extensions: extension,
|
||||
allClosed: true,
|
||||
|
||||
25
vendor/github.com/jmoiron/sqlx/.gitignore
generated
vendored
Normal file
25
vendor/github.com/jmoiron/sqlx/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.idea
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
tags
|
||||
environ
|
||||
23
vendor/github.com/jmoiron/sqlx/LICENSE
generated
vendored
Normal file
23
vendor/github.com/jmoiron/sqlx/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright (c) 2013, Jason Moiron
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
30
vendor/github.com/jmoiron/sqlx/Makefile
generated
vendored
Normal file
30
vendor/github.com/jmoiron/sqlx/Makefile
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
.ONESHELL:
|
||||
SHELL = /bin/sh
|
||||
.SHELLFLAGS = -ec
|
||||
|
||||
BASE_PACKAGE := github.com/jmoiron/sqlx
|
||||
|
||||
tooling:
|
||||
go install honnef.co/go/tools/cmd/staticcheck@v0.4.7
|
||||
go install golang.org/x/vuln/cmd/govulncheck@v1.0.4
|
||||
go install golang.org/x/tools/cmd/goimports@v0.20.0
|
||||
|
||||
has-changes:
|
||||
git diff --exit-code --quiet HEAD --
|
||||
|
||||
lint:
|
||||
go vet ./...
|
||||
staticcheck -checks=all ./...
|
||||
|
||||
fmt:
|
||||
go list -f '{{.Dir}}' ./... | xargs -I {} goimports -local $(BASE_PACKAGE) -w {}
|
||||
|
||||
vuln-check:
|
||||
govulncheck ./...
|
||||
|
||||
test-race:
|
||||
go test -v -race -count=1 ./...
|
||||
|
||||
update-dependencies:
|
||||
go get -u -t -v ./...
|
||||
go mod tidy
|
||||
213
vendor/github.com/jmoiron/sqlx/README.md
generated
vendored
Normal file
213
vendor/github.com/jmoiron/sqlx/README.md
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
# sqlx
|
||||
|
||||
[](https://dl.circleci.com/status-badge/redirect/gh/jmoiron/sqlx/tree/master) [](https://coveralls.io/github/jmoiron/sqlx?branch=master) [](https://godoc.org/github.com/jmoiron/sqlx) [](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE)
|
||||
|
||||
sqlx is a library which provides a set of extensions on go's standard
|
||||
`database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`,
|
||||
et al. all leave the underlying interfaces untouched, so that their interfaces
|
||||
are a superset on the standard ones. This makes it relatively painless to
|
||||
integrate existing codebases using database/sql with sqlx.
|
||||
|
||||
Major additional concepts are:
|
||||
|
||||
* Marshal rows into structs (with embedded struct support), maps, and slices
|
||||
* Named parameter support including prepared statements
|
||||
* `Get` and `Select` to go quickly from query to struct/slice
|
||||
|
||||
In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx),
|
||||
there is also some [user documentation](http://jmoiron.github.io/sqlx/) that
|
||||
explains how to use `database/sql` along with sqlx.
|
||||
|
||||
## Recent Changes
|
||||
|
||||
1.3.0:
|
||||
|
||||
* `sqlx.DB.Connx(context.Context) *sqlx.Conn`
|
||||
* `sqlx.BindDriver(driverName, bindType)`
|
||||
* support for `[]map[string]interface{}` to do "batch" insertions
|
||||
* allocation & perf improvements for `sqlx.In`
|
||||
|
||||
DB.Connx returns an `sqlx.Conn`, which is an `sql.Conn`-alike consistent with
|
||||
sqlx's wrapping of other types.
|
||||
|
||||
`BindDriver` allows users to control the bindvars that sqlx will use for drivers,
|
||||
and add new drivers at runtime. This results in a very slight performance hit
|
||||
when resolving the driver into a bind type (~40ns per call), but it allows users
|
||||
to specify what bindtype their driver uses even when sqlx has not been updated
|
||||
to know about it by default.
|
||||
|
||||
### Backwards Compatibility
|
||||
|
||||
Compatibility with the most recent two versions of Go is a requirement for any
|
||||
new changes. Compatibility beyond that is not guaranteed.
|
||||
|
||||
Versioning is done with Go modules. Breaking changes (eg. removing deprecated API)
|
||||
will get major version number bumps.
|
||||
|
||||
## install
|
||||
|
||||
go get github.com/jmoiron/sqlx
|
||||
|
||||
## issues
|
||||
|
||||
Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of
|
||||
`Columns()` does not fully qualify column names in queries like:
|
||||
|
||||
```sql
|
||||
SELECT a.id, a.name, b.id, b.name FROM foos AS a JOIN foos AS b ON a.parent = b.id;
|
||||
```
|
||||
|
||||
making a struct or map destination ambiguous. Use `AS` in your queries
|
||||
to give columns distinct names, `rows.Scan` to scan them manually, or
|
||||
`SliceScan` to get a slice of results.
|
||||
|
||||
## usage
|
||||
|
||||
Below is an example which shows some common use cases for sqlx. Check
|
||||
[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
|
||||
usage.
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var schema = `
|
||||
CREATE TABLE person (
|
||||
first_name text,
|
||||
last_name text,
|
||||
email text
|
||||
);
|
||||
|
||||
CREATE TABLE place (
|
||||
country text,
|
||||
city text NULL,
|
||||
telcode integer
|
||||
)`
|
||||
|
||||
type Person struct {
|
||||
FirstName string `db:"first_name"`
|
||||
LastName string `db:"last_name"`
|
||||
Email string
|
||||
}
|
||||
|
||||
type Place struct {
|
||||
Country string
|
||||
City sql.NullString
|
||||
TelCode int
|
||||
}
|
||||
|
||||
func main() {
|
||||
// this Pings the database trying to connect
|
||||
// use sqlx.Open() for sql.Open() semantics
|
||||
db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// exec the schema or fail; multi-statement Exec behavior varies between
|
||||
// database drivers; pq will exec them all, sqlite3 won't, ymmv
|
||||
db.MustExec(schema)
|
||||
|
||||
tx := db.MustBegin()
|
||||
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
|
||||
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
|
||||
tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
|
||||
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
|
||||
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
|
||||
// Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
|
||||
tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
|
||||
tx.Commit()
|
||||
|
||||
// Query the database, storing results in a []Person (wrapped in []interface{})
|
||||
people := []Person{}
|
||||
db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
|
||||
jason, john := people[0], people[1]
|
||||
|
||||
fmt.Printf("%#v\n%#v", jason, john)
|
||||
// Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
|
||||
// Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
|
||||
|
||||
// You can also get a single result, a la QueryRow
|
||||
jason = Person{}
|
||||
err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
|
||||
fmt.Printf("%#v\n", jason)
|
||||
// Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
|
||||
|
||||
// if you have null fields and use SELECT *, you must use sql.Null* in your struct
|
||||
places := []Place{}
|
||||
err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
usa, singsing, honkers := places[0], places[1], places[2]
|
||||
|
||||
fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
|
||||
// Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
|
||||
// Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
|
||||
// Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
|
||||
|
||||
// Loop through rows using only one struct
|
||||
place := Place{}
|
||||
rows, err := db.Queryx("SELECT * FROM place")
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&place)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Printf("%#v\n", place)
|
||||
}
|
||||
// Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
|
||||
// Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
|
||||
// Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
|
||||
|
||||
// Named queries, using `:name` as the bindvar. Automatic bindvar support
|
||||
// which takes into account the dbtype based on the driverName on sqlx.Open/Connect
|
||||
_, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`,
|
||||
map[string]interface{}{
|
||||
"first": "Bin",
|
||||
"last": "Smuth",
|
||||
"email": "bensmith@allblacks.nz",
|
||||
})
|
||||
|
||||
// Selects Mr. Smith from the database
|
||||
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
|
||||
|
||||
// Named queries can also use structs. Their bind names follow the same rules
|
||||
// as the name -> db mapping, so struct fields are lowercased and the `db` tag
|
||||
// is taken into consideration.
|
||||
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
|
||||
|
||||
|
||||
// batch insert
|
||||
|
||||
// batch insert with structs
|
||||
personStructs := []Person{
|
||||
{FirstName: "Ardie", LastName: "Savea", Email: "asavea@ab.co.nz"},
|
||||
{FirstName: "Sonny Bill", LastName: "Williams", Email: "sbw@ab.co.nz"},
|
||||
{FirstName: "Ngani", LastName: "Laumape", Email: "nlaumape@ab.co.nz"},
|
||||
}
|
||||
|
||||
_, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
|
||||
VALUES (:first_name, :last_name, :email)`, personStructs)
|
||||
|
||||
// batch insert with maps
|
||||
personMaps := []map[string]interface{}{
|
||||
{"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"},
|
||||
{"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"},
|
||||
{"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"},
|
||||
}
|
||||
|
||||
_, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
|
||||
VALUES (:first_name, :last_name, :email)`, personMaps)
|
||||
}
|
||||
```
|
||||
265
vendor/github.com/jmoiron/sqlx/bind.go
generated
vendored
Normal file
265
vendor/github.com/jmoiron/sqlx/bind.go
generated
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
)
|
||||
|
||||
// Bindvar types supported by Rebind, BindMap and BindStruct.
|
||||
const (
|
||||
UNKNOWN = iota
|
||||
QUESTION
|
||||
DOLLAR
|
||||
NAMED
|
||||
AT
|
||||
)
|
||||
|
||||
var defaultBinds = map[int][]string{
|
||||
DOLLAR: []string{"postgres", "pgx", "pq-timeouts", "cloudsqlpostgres", "ql", "nrpostgres", "cockroach"},
|
||||
QUESTION: []string{"mysql", "sqlite3", "nrmysql", "nrsqlite3"},
|
||||
NAMED: []string{"oci8", "ora", "goracle", "godror"},
|
||||
AT: []string{"sqlserver"},
|
||||
}
|
||||
|
||||
var binds sync.Map
|
||||
|
||||
func init() {
|
||||
for bind, drivers := range defaultBinds {
|
||||
for _, driver := range drivers {
|
||||
BindDriver(driver, bind)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// BindType returns the bindtype for a given database given a drivername.
|
||||
func BindType(driverName string) int {
|
||||
itype, ok := binds.Load(driverName)
|
||||
if !ok {
|
||||
return UNKNOWN
|
||||
}
|
||||
return itype.(int)
|
||||
}
|
||||
|
||||
// BindDriver sets the BindType for driverName to bindType.
|
||||
func BindDriver(driverName string, bindType int) {
|
||||
binds.Store(driverName, bindType)
|
||||
}
|
||||
|
||||
// FIXME: this should be able to be tolerant of escaped ?'s in queries without
|
||||
// losing much speed, and should be to avoid confusion.
|
||||
|
||||
// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
|
||||
func Rebind(bindType int, query string) string {
|
||||
switch bindType {
|
||||
case QUESTION, UNKNOWN:
|
||||
return query
|
||||
}
|
||||
|
||||
// Add space enough for 10 params before we have to allocate
|
||||
rqb := make([]byte, 0, len(query)+10)
|
||||
|
||||
var i, j int
|
||||
|
||||
for i = strings.Index(query, "?"); i != -1; i = strings.Index(query, "?") {
|
||||
rqb = append(rqb, query[:i]...)
|
||||
|
||||
switch bindType {
|
||||
case DOLLAR:
|
||||
rqb = append(rqb, '$')
|
||||
case NAMED:
|
||||
rqb = append(rqb, ':', 'a', 'r', 'g')
|
||||
case AT:
|
||||
rqb = append(rqb, '@', 'p')
|
||||
}
|
||||
|
||||
j++
|
||||
rqb = strconv.AppendInt(rqb, int64(j), 10)
|
||||
|
||||
query = query[i+1:]
|
||||
}
|
||||
|
||||
return string(append(rqb, query...))
|
||||
}
|
||||
|
||||
// Experimental implementation of Rebind which uses a bytes.Buffer. The code is
|
||||
// much simpler and should be more resistant to odd unicode, but it is twice as
|
||||
// slow. Kept here for benchmarking purposes and to possibly replace Rebind if
|
||||
// problems arise with its somewhat naive handling of unicode.
|
||||
func rebindBuff(bindType int, query string) string {
|
||||
if bindType != DOLLAR {
|
||||
return query
|
||||
}
|
||||
|
||||
b := make([]byte, 0, len(query))
|
||||
rqb := bytes.NewBuffer(b)
|
||||
j := 1
|
||||
for _, r := range query {
|
||||
if r == '?' {
|
||||
rqb.WriteRune('$')
|
||||
rqb.WriteString(strconv.Itoa(j))
|
||||
j++
|
||||
} else {
|
||||
rqb.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
return rqb.String()
|
||||
}
|
||||
|
||||
func asSliceForIn(i interface{}) (v reflect.Value, ok bool) {
|
||||
if i == nil {
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
v = reflect.ValueOf(i)
|
||||
t := reflectx.Deref(v.Type())
|
||||
|
||||
// Only expand slices
|
||||
if t.Kind() != reflect.Slice {
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
// []byte is a driver.Value type so it should not be expanded
|
||||
if t == reflect.TypeOf([]byte{}) {
|
||||
return reflect.Value{}, false
|
||||
|
||||
}
|
||||
|
||||
return v, true
|
||||
}
|
||||
|
||||
// In expands slice values in args, returning the modified query string
|
||||
// and a new arg list that can be executed by a database. The `query` should
|
||||
// use the `?` bindVar. The return value uses the `?` bindVar.
|
||||
func In(query string, args ...interface{}) (string, []interface{}, error) {
|
||||
// argMeta stores reflect.Value and length for slices and
|
||||
// the value itself for non-slice arguments
|
||||
type argMeta struct {
|
||||
v reflect.Value
|
||||
i interface{}
|
||||
length int
|
||||
}
|
||||
|
||||
var flatArgsCount int
|
||||
var anySlices bool
|
||||
|
||||
var stackMeta [32]argMeta
|
||||
|
||||
var meta []argMeta
|
||||
if len(args) <= len(stackMeta) {
|
||||
meta = stackMeta[:len(args)]
|
||||
} else {
|
||||
meta = make([]argMeta, len(args))
|
||||
}
|
||||
|
||||
for i, arg := range args {
|
||||
if a, ok := arg.(driver.Valuer); ok {
|
||||
var err error
|
||||
arg, err = a.Value()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := asSliceForIn(arg); ok {
|
||||
meta[i].length = v.Len()
|
||||
meta[i].v = v
|
||||
|
||||
anySlices = true
|
||||
flatArgsCount += meta[i].length
|
||||
|
||||
if meta[i].length == 0 {
|
||||
return "", nil, errors.New("empty slice passed to 'in' query")
|
||||
}
|
||||
} else {
|
||||
meta[i].i = arg
|
||||
flatArgsCount++
|
||||
}
|
||||
}
|
||||
|
||||
// don't do any parsing if there aren't any slices; note that this means
|
||||
// some errors that we might have caught below will not be returned.
|
||||
if !anySlices {
|
||||
return query, args, nil
|
||||
}
|
||||
|
||||
newArgs := make([]interface{}, 0, flatArgsCount)
|
||||
|
||||
var buf strings.Builder
|
||||
buf.Grow(len(query) + len(", ?")*flatArgsCount)
|
||||
|
||||
var arg, offset int
|
||||
|
||||
for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
|
||||
if arg >= len(meta) {
|
||||
// if an argument wasn't passed, lets return an error; this is
|
||||
// not actually how database/sql Exec/Query works, but since we are
|
||||
// creating an argument list programmatically, we want to be able
|
||||
// to catch these programmer errors earlier.
|
||||
return "", nil, errors.New("number of bindVars exceeds arguments")
|
||||
}
|
||||
|
||||
argMeta := meta[arg]
|
||||
arg++
|
||||
|
||||
// not a slice, continue.
|
||||
// our questionmark will either be written before the next expansion
|
||||
// of a slice or after the loop when writing the rest of the query
|
||||
if argMeta.length == 0 {
|
||||
offset = offset + i + 1
|
||||
newArgs = append(newArgs, argMeta.i)
|
||||
continue
|
||||
}
|
||||
|
||||
// write everything up to and including our ? character
|
||||
buf.WriteString(query[:offset+i+1])
|
||||
|
||||
for si := 1; si < argMeta.length; si++ {
|
||||
buf.WriteString(", ?")
|
||||
}
|
||||
|
||||
newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
|
||||
|
||||
// slice the query and reset the offset. this avoids some bookkeeping for
|
||||
// the write after the loop
|
||||
query = query[offset+i+1:]
|
||||
offset = 0
|
||||
}
|
||||
|
||||
buf.WriteString(query)
|
||||
|
||||
if arg < len(meta) {
|
||||
return "", nil, errors.New("number of bindVars less than number arguments")
|
||||
}
|
||||
|
||||
return buf.String(), newArgs, nil
|
||||
}
|
||||
|
||||
func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
|
||||
switch val := v.Interface().(type) {
|
||||
case []interface{}:
|
||||
args = append(args, val...)
|
||||
case []int:
|
||||
for i := range val {
|
||||
args = append(args, val[i])
|
||||
}
|
||||
case []string:
|
||||
for i := range val {
|
||||
args = append(args, val[i])
|
||||
}
|
||||
default:
|
||||
for si := 0; si < vlen; si++ {
|
||||
args = append(args, v.Index(si).Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
11
vendor/github.com/jmoiron/sqlx/doc.go
generated
vendored
Normal file
11
vendor/github.com/jmoiron/sqlx/doc.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Package sqlx provides general purpose extensions to database/sql.
|
||||
//
|
||||
// It is intended to seamlessly wrap database/sql and provide convenience
|
||||
// methods which are useful in the development of database driven applications.
|
||||
// None of the underlying database/sql methods are changed. Instead all extended
|
||||
// behavior is implemented through new methods defined on wrapper types.
|
||||
//
|
||||
// Additions include scanning into structs, named query support, rebinding
|
||||
// queries for different drivers, convenient shorthands for common error handling
|
||||
// and more.
|
||||
package sqlx
|
||||
458
vendor/github.com/jmoiron/sqlx/named.go
generated
vendored
Normal file
458
vendor/github.com/jmoiron/sqlx/named.go
generated
vendored
Normal file
@@ -0,0 +1,458 @@
|
||||
package sqlx
|
||||
|
||||
// Named Query Support
|
||||
//
|
||||
// * BindMap - bind query bindvars to map/struct args
|
||||
// * NamedExec, NamedQuery - named query w/ struct or map
|
||||
// * NamedStmt - a pre-compiled named query which is a prepared statement
|
||||
//
|
||||
// Internal Interfaces:
|
||||
//
|
||||
// * compileNamedQuery - rebind a named query, returning a query and list of names
|
||||
// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
|
||||
//
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"unicode"
|
||||
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
)
|
||||
|
||||
// NamedStmt is a prepared statement that executes named queries. Prepare it
|
||||
// how you would execute a NamedQuery, but pass in a struct or map when executing.
|
||||
type NamedStmt struct {
|
||||
Params []string
|
||||
QueryString string
|
||||
Stmt *Stmt
|
||||
}
|
||||
|
||||
// Close closes the named statement.
|
||||
func (n *NamedStmt) Close() error {
|
||||
return n.Stmt.Close()
|
||||
}
|
||||
|
||||
// Exec executes a named statement using the struct passed.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
|
||||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||
if err != nil {
|
||||
return *new(sql.Result), err
|
||||
}
|
||||
return n.Stmt.Exec(args...)
|
||||
}
|
||||
|
||||
// Query executes a named statement using the struct argument, returning rows.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
|
||||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.Stmt.Query(args...)
|
||||
}
|
||||
|
||||
// QueryRow executes a named statement against the database. Because sqlx cannot
|
||||
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
|
||||
// returns a *sqlx.Row instead.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) QueryRow(arg interface{}) *Row {
|
||||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||
if err != nil {
|
||||
return &Row{err: err}
|
||||
}
|
||||
return n.Stmt.QueryRowx(args...)
|
||||
}
|
||||
|
||||
// MustExec execs a NamedStmt, panicing on error
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
|
||||
res, err := n.Exec(arg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Queryx using this NamedStmt
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
|
||||
r, err := n.Query(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
|
||||
}
|
||||
|
||||
// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
|
||||
// an alias for QueryRow.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
|
||||
return n.QueryRow(arg)
|
||||
}
|
||||
|
||||
// Select using this NamedStmt
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
|
||||
rows, err := n.Queryx(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if something happens here, we want to make sure the rows are Closed
|
||||
defer rows.Close()
|
||||
return scanAll(rows, dest, false)
|
||||
}
|
||||
|
||||
// Get using this NamedStmt
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
|
||||
r := n.QueryRowx(arg)
|
||||
return r.scanAny(dest, false)
|
||||
}
|
||||
|
||||
// Unsafe creates an unsafe version of the NamedStmt
|
||||
func (n *NamedStmt) Unsafe() *NamedStmt {
|
||||
r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
|
||||
r.Stmt.unsafe = true
|
||||
return r
|
||||
}
|
||||
|
||||
// A union interface of preparer and binder, required to be able to prepare
|
||||
// named statements (as the bindtype must be determined).
|
||||
type namedPreparer interface {
|
||||
Preparer
|
||||
binder
|
||||
}
|
||||
|
||||
func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
|
||||
bindType := BindType(p.DriverName())
|
||||
q, args, err := compileNamedQuery([]byte(query), bindType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt, err := Preparex(p, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NamedStmt{
|
||||
QueryString: q,
|
||||
Params: args,
|
||||
Stmt: stmt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertMapStringInterface attempts to convert v to map[string]interface{}.
|
||||
// Unlike v.(map[string]interface{}), this function works on named types that
|
||||
// are convertible to map[string]interface{} as well.
|
||||
func convertMapStringInterface(v interface{}) (map[string]interface{}, bool) {
|
||||
var m map[string]interface{}
|
||||
mtype := reflect.TypeOf(m)
|
||||
t := reflect.TypeOf(v)
|
||||
if !t.ConvertibleTo(mtype) {
|
||||
return nil, false
|
||||
}
|
||||
return reflect.ValueOf(v).Convert(mtype).Interface().(map[string]interface{}), true
|
||||
|
||||
}
|
||||
|
||||
func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
|
||||
if maparg, ok := convertMapStringInterface(arg); ok {
|
||||
return bindMapArgs(names, maparg)
|
||||
}
|
||||
return bindArgs(names, arg, m)
|
||||
}
|
||||
|
||||
// private interface to generate a list of interfaces from a given struct
|
||||
// type, given a list of names to pull out of the struct. Used by public
|
||||
// BindStruct interface.
|
||||
func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
|
||||
arglist := make([]interface{}, 0, len(names))
|
||||
|
||||
// grab the indirected value of arg
|
||||
var v reflect.Value
|
||||
for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
|
||||
if len(t) == 0 {
|
||||
return fmt.Errorf("could not find name %s in %#v", names[i], arg)
|
||||
}
|
||||
|
||||
val := reflectx.FieldByIndexesReadOnly(v, t)
|
||||
arglist = append(arglist, val.Interface())
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return arglist, err
|
||||
}
|
||||
|
||||
// like bindArgs, but for maps.
|
||||
func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
|
||||
arglist := make([]interface{}, 0, len(names))
|
||||
|
||||
for _, name := range names {
|
||||
val, ok := arg[name]
|
||||
if !ok {
|
||||
return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
|
||||
}
|
||||
arglist = append(arglist, val)
|
||||
}
|
||||
return arglist, nil
|
||||
}
|
||||
|
||||
// bindStruct binds a named parameter query with fields from a struct argument.
|
||||
// The rules for binding field names to parameter names follow the same
|
||||
// conventions as for StructScan, including obeying the `db` struct tags.
|
||||
func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
||||
bound, names, err := compileNamedQuery([]byte(query), bindType)
|
||||
if err != nil {
|
||||
return "", []interface{}{}, err
|
||||
}
|
||||
|
||||
arglist, err := bindAnyArgs(names, arg, m)
|
||||
if err != nil {
|
||||
return "", []interface{}{}, err
|
||||
}
|
||||
|
||||
return bound, arglist, nil
|
||||
}
|
||||
|
||||
var valuesReg = regexp.MustCompile(`\)\s*(?i)VALUES\s*\(`)
|
||||
|
||||
func findMatchingClosingBracketIndex(s string) int {
|
||||
count := 0
|
||||
for i, ch := range s {
|
||||
if ch == '(' {
|
||||
count++
|
||||
}
|
||||
if ch == ')' {
|
||||
count--
|
||||
if count == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func fixBound(bound string, loop int) string {
|
||||
loc := valuesReg.FindStringIndex(bound)
|
||||
// defensive guard when "VALUES (...)" not found
|
||||
if len(loc) < 2 {
|
||||
return bound
|
||||
}
|
||||
|
||||
openingBracketIndex := loc[1] - 1
|
||||
index := findMatchingClosingBracketIndex(bound[openingBracketIndex:])
|
||||
// defensive guard. must have closing bracket
|
||||
if index == 0 {
|
||||
return bound
|
||||
}
|
||||
closingBracketIndex := openingBracketIndex + index + 1
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(bound[0:closingBracketIndex])
|
||||
for i := 0; i < loop-1; i++ {
|
||||
buffer.WriteString(",")
|
||||
buffer.WriteString(bound[openingBracketIndex:closingBracketIndex])
|
||||
}
|
||||
buffer.WriteString(bound[closingBracketIndex:])
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// bindArray binds a named parameter query with fields from an array or slice of
|
||||
// structs argument.
|
||||
func bindArray(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
||||
// do the initial binding with QUESTION; if bindType is not question,
|
||||
// we can rebind it at the end.
|
||||
bound, names, err := compileNamedQuery([]byte(query), QUESTION)
|
||||
if err != nil {
|
||||
return "", []interface{}{}, err
|
||||
}
|
||||
arrayValue := reflect.ValueOf(arg)
|
||||
arrayLen := arrayValue.Len()
|
||||
if arrayLen == 0 {
|
||||
return "", []interface{}{}, fmt.Errorf("length of array is 0: %#v", arg)
|
||||
}
|
||||
var arglist = make([]interface{}, 0, len(names)*arrayLen)
|
||||
for i := 0; i < arrayLen; i++ {
|
||||
elemArglist, err := bindAnyArgs(names, arrayValue.Index(i).Interface(), m)
|
||||
if err != nil {
|
||||
return "", []interface{}{}, err
|
||||
}
|
||||
arglist = append(arglist, elemArglist...)
|
||||
}
|
||||
if arrayLen > 1 {
|
||||
bound = fixBound(bound, arrayLen)
|
||||
}
|
||||
// adjust binding type if we weren't on question
|
||||
if bindType != QUESTION {
|
||||
bound = Rebind(bindType, bound)
|
||||
}
|
||||
return bound, arglist, nil
|
||||
}
|
||||
|
||||
// bindMap binds a named parameter query with a map of arguments.
|
||||
func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
|
||||
bound, names, err := compileNamedQuery([]byte(query), bindType)
|
||||
if err != nil {
|
||||
return "", []interface{}{}, err
|
||||
}
|
||||
|
||||
arglist, err := bindMapArgs(names, args)
|
||||
return bound, arglist, err
|
||||
}
|
||||
|
||||
// -- Compilation of Named Queries
|
||||
|
||||
// Allow digits and letters in bind params; additionally runes are
|
||||
// checked against underscores, meaning that bind params can have be
|
||||
// alphanumeric with underscores. Mind the difference between unicode
|
||||
// digits and numbers, where '5' is a digit but '五' is not.
|
||||
var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
|
||||
|
||||
// FIXME: this function isn't safe for unicode named params, as a failing test
|
||||
// can testify. This is not a regression but a failure of the original code
|
||||
// as well. It should be modified to range over runes in a string rather than
|
||||
// bytes, even though this is less convenient and slower. Hopefully the
|
||||
// addition of the prepared NamedStmt (which will only do this once) will make
|
||||
// up for the slightly slower ad-hoc NamedExec/NamedQuery.
|
||||
|
||||
// compile a NamedQuery into an unbound query (using the '?' bindvar) and
|
||||
// a list of names.
|
||||
func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
|
||||
names = make([]string, 0, 10)
|
||||
rebound := make([]byte, 0, len(qs))
|
||||
|
||||
inName := false
|
||||
last := len(qs) - 1
|
||||
currentVar := 1
|
||||
name := make([]byte, 0, 10)
|
||||
|
||||
for i, b := range qs {
|
||||
// a ':' while we're in a name is an error
|
||||
if b == ':' {
|
||||
// if this is the second ':' in a '::' escape sequence, append a ':'
|
||||
if inName && i > 0 && qs[i-1] == ':' {
|
||||
rebound = append(rebound, ':')
|
||||
inName = false
|
||||
continue
|
||||
} else if inName {
|
||||
err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
|
||||
return query, names, err
|
||||
}
|
||||
inName = true
|
||||
name = []byte{}
|
||||
} else if inName && i > 0 && b == '=' && len(name) == 0 {
|
||||
rebound = append(rebound, ':', '=')
|
||||
inName = false
|
||||
continue
|
||||
// if we're in a name, and this is an allowed character, continue
|
||||
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
|
||||
// append the byte to the name if we are in a name and not on the last byte
|
||||
name = append(name, b)
|
||||
// if we're in a name and it's not an allowed character, the name is done
|
||||
} else if inName {
|
||||
inName = false
|
||||
// if this is the final byte of the string and it is part of the name, then
|
||||
// make sure to add it to the name
|
||||
if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
|
||||
name = append(name, b)
|
||||
}
|
||||
// add the string representation to the names list
|
||||
names = append(names, string(name))
|
||||
// add a proper bindvar for the bindType
|
||||
switch bindType {
|
||||
// oracle only supports named type bind vars even for positional
|
||||
case NAMED:
|
||||
rebound = append(rebound, ':')
|
||||
rebound = append(rebound, name...)
|
||||
case QUESTION, UNKNOWN:
|
||||
rebound = append(rebound, '?')
|
||||
case DOLLAR:
|
||||
rebound = append(rebound, '$')
|
||||
for _, b := range strconv.Itoa(currentVar) {
|
||||
rebound = append(rebound, byte(b))
|
||||
}
|
||||
currentVar++
|
||||
case AT:
|
||||
rebound = append(rebound, '@', 'p')
|
||||
for _, b := range strconv.Itoa(currentVar) {
|
||||
rebound = append(rebound, byte(b))
|
||||
}
|
||||
currentVar++
|
||||
}
|
||||
// add this byte to string unless it was not part of the name
|
||||
if i != last {
|
||||
rebound = append(rebound, b)
|
||||
} else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
|
||||
rebound = append(rebound, b)
|
||||
}
|
||||
} else {
|
||||
// this is a normal byte and should just go onto the rebound query
|
||||
rebound = append(rebound, b)
|
||||
}
|
||||
}
|
||||
|
||||
return string(rebound), names, err
|
||||
}
|
||||
|
||||
// BindNamed binds a struct or a map to a query with named parameters.
|
||||
// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
|
||||
func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
|
||||
return bindNamedMapper(bindType, query, arg, mapper())
|
||||
}
|
||||
|
||||
// Named takes a query using named parameters and an argument and
|
||||
// returns a new query with a list of args that can be executed by
|
||||
// a database. The return value uses the `?` bindvar.
|
||||
func Named(query string, arg interface{}) (string, []interface{}, error) {
|
||||
return bindNamedMapper(QUESTION, query, arg, mapper())
|
||||
}
|
||||
|
||||
func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
||||
t := reflect.TypeOf(arg)
|
||||
k := t.Kind()
|
||||
switch {
|
||||
case k == reflect.Map && t.Key().Kind() == reflect.String:
|
||||
m, ok := convertMapStringInterface(arg)
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("sqlx.bindNamedMapper: unsupported map type: %T", arg)
|
||||
}
|
||||
return bindMap(bindType, query, m)
|
||||
case k == reflect.Array || k == reflect.Slice:
|
||||
return bindArray(bindType, query, arg, m)
|
||||
default:
|
||||
return bindStruct(bindType, query, arg, m)
|
||||
}
|
||||
}
|
||||
|
||||
// NamedQuery binds a named query and then runs Query on the result using the
|
||||
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
|
||||
// map[string]interface{} types.
|
||||
func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
|
||||
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.Queryx(q, args...)
|
||||
}
|
||||
|
||||
// NamedExec uses BindStruct to get a query executable by the driver and
|
||||
// then runs Exec on the result. Returns an error from the binding
|
||||
// or the query execution itself.
|
||||
func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
|
||||
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.Exec(q, args...)
|
||||
}
|
||||
133
vendor/github.com/jmoiron/sqlx/named_context.go
generated
vendored
Normal file
133
vendor/github.com/jmoiron/sqlx/named_context.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
//go:build go1.8
|
||||
// +build go1.8
|
||||
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// A union interface of contextPreparer and binder, required to be able to
|
||||
// prepare named statements with context (as the bindtype must be determined).
|
||||
type namedPreparerContext interface {
|
||||
PreparerContext
|
||||
binder
|
||||
}
|
||||
|
||||
func prepareNamedContext(ctx context.Context, p namedPreparerContext, query string) (*NamedStmt, error) {
|
||||
bindType := BindType(p.DriverName())
|
||||
q, args, err := compileNamedQuery([]byte(query), bindType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt, err := PreparexContext(ctx, p, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NamedStmt{
|
||||
QueryString: q,
|
||||
Params: args,
|
||||
Stmt: stmt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecContext executes a named statement using the struct passed.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) ExecContext(ctx context.Context, arg interface{}) (sql.Result, error) {
|
||||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||
if err != nil {
|
||||
return *new(sql.Result), err
|
||||
}
|
||||
return n.Stmt.ExecContext(ctx, args...)
|
||||
}
|
||||
|
||||
// QueryContext executes a named statement using the struct argument, returning rows.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) QueryContext(ctx context.Context, arg interface{}) (*sql.Rows, error) {
|
||||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.Stmt.QueryContext(ctx, args...)
|
||||
}
|
||||
|
||||
// QueryRowContext executes a named statement against the database. Because sqlx cannot
|
||||
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
|
||||
// returns a *sqlx.Row instead.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) QueryRowContext(ctx context.Context, arg interface{}) *Row {
|
||||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||
if err != nil {
|
||||
return &Row{err: err}
|
||||
}
|
||||
return n.Stmt.QueryRowxContext(ctx, args...)
|
||||
}
|
||||
|
||||
// MustExecContext execs a NamedStmt, panicing on error
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) MustExecContext(ctx context.Context, arg interface{}) sql.Result {
|
||||
res, err := n.ExecContext(ctx, arg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// QueryxContext using this NamedStmt
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) QueryxContext(ctx context.Context, arg interface{}) (*Rows, error) {
|
||||
r, err := n.QueryContext(ctx, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
|
||||
}
|
||||
|
||||
// QueryRowxContext this NamedStmt. Because of limitations with QueryRow, this is
|
||||
// an alias for QueryRow.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) QueryRowxContext(ctx context.Context, arg interface{}) *Row {
|
||||
return n.QueryRowContext(ctx, arg)
|
||||
}
|
||||
|
||||
// SelectContext using this NamedStmt
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) SelectContext(ctx context.Context, dest interface{}, arg interface{}) error {
|
||||
rows, err := n.QueryxContext(ctx, arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if something happens here, we want to make sure the rows are Closed
|
||||
defer rows.Close()
|
||||
return scanAll(rows, dest, false)
|
||||
}
|
||||
|
||||
// GetContext using this NamedStmt
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (n *NamedStmt) GetContext(ctx context.Context, dest interface{}, arg interface{}) error {
|
||||
r := n.QueryRowxContext(ctx, arg)
|
||||
return r.scanAny(dest, false)
|
||||
}
|
||||
|
||||
// NamedQueryContext binds a named query and then runs Query on the result using the
|
||||
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
|
||||
// map[string]interface{} types.
|
||||
func NamedQueryContext(ctx context.Context, e ExtContext, query string, arg interface{}) (*Rows, error) {
|
||||
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.QueryxContext(ctx, q, args...)
|
||||
}
|
||||
|
||||
// NamedExecContext uses BindStruct to get a query executable by the driver and
|
||||
// then runs Exec on the result. Returns an error from the binding
|
||||
// or the query execution itself.
|
||||
func NamedExecContext(ctx context.Context, e ExtContext, query string, arg interface{}) (sql.Result, error) {
|
||||
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.ExecContext(ctx, q, args...)
|
||||
}
|
||||
17
vendor/github.com/jmoiron/sqlx/reflectx/README.md
generated
vendored
Normal file
17
vendor/github.com/jmoiron/sqlx/reflectx/README.md
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# reflectx
|
||||
|
||||
The sqlx package has special reflect needs. In particular, it needs to:
|
||||
|
||||
* be able to map a name to a field
|
||||
* understand embedded structs
|
||||
* understand mapping names to fields by a particular tag
|
||||
* user specified name -> field mapping functions
|
||||
|
||||
These behaviors mimic the behaviors by the standard library marshallers and also the
|
||||
behavior of standard Go accessors.
|
||||
|
||||
The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
|
||||
addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
|
||||
tags in the ways that are vital to most marshallers, and they are slow.
|
||||
|
||||
This reflectx package extends reflect to achieve these goals.
|
||||
443
vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
generated
vendored
Normal file
443
vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
generated
vendored
Normal file
@@ -0,0 +1,443 @@
|
||||
// Package reflectx implements extensions to the standard reflect lib suitable
|
||||
// for implementing marshalling and unmarshalling packages. The main Mapper type
|
||||
// allows for Go-compatible named attribute access, including accessing embedded
|
||||
// struct attributes and the ability to use functions and struct tags to
|
||||
// customize field names.
|
||||
package reflectx
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A FieldInfo is metadata for a struct field.
|
||||
type FieldInfo struct {
|
||||
Index []int
|
||||
Path string
|
||||
Field reflect.StructField
|
||||
Zero reflect.Value
|
||||
Name string
|
||||
Options map[string]string
|
||||
Embedded bool
|
||||
Children []*FieldInfo
|
||||
Parent *FieldInfo
|
||||
}
|
||||
|
||||
// A StructMap is an index of field metadata for a struct.
|
||||
type StructMap struct {
|
||||
Tree *FieldInfo
|
||||
Index []*FieldInfo
|
||||
Paths map[string]*FieldInfo
|
||||
Names map[string]*FieldInfo
|
||||
}
|
||||
|
||||
// GetByPath returns a *FieldInfo for a given string path.
|
||||
func (f StructMap) GetByPath(path string) *FieldInfo {
|
||||
return f.Paths[path]
|
||||
}
|
||||
|
||||
// GetByTraversal returns a *FieldInfo for a given integer path. It is
|
||||
// analogous to reflect.FieldByIndex, but using the cached traversal
|
||||
// rather than re-executing the reflect machinery each time.
|
||||
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
|
||||
if len(index) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tree := f.Tree
|
||||
for _, i := range index {
|
||||
if i >= len(tree.Children) || tree.Children[i] == nil {
|
||||
return nil
|
||||
}
|
||||
tree = tree.Children[i]
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// Mapper is a general purpose mapper of names to struct fields. A Mapper
|
||||
// behaves like most marshallers in the standard library, obeying a field tag
|
||||
// for name mapping but also providing a basic transform function.
|
||||
type Mapper struct {
|
||||
cache map[reflect.Type]*StructMap
|
||||
tagName string
|
||||
tagMapFunc func(string) string
|
||||
mapFunc func(string) string
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewMapper returns a new mapper using the tagName as its struct field tag.
|
||||
// If tagName is the empty string, it is ignored.
|
||||
func NewMapper(tagName string) *Mapper {
|
||||
return &Mapper{
|
||||
cache: make(map[reflect.Type]*StructMap),
|
||||
tagName: tagName,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapperTagFunc returns a new mapper which contains a mapper for field names
|
||||
// AND a mapper for tag values. This is useful for tags like json which can
|
||||
// have values like "name,omitempty".
|
||||
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
|
||||
return &Mapper{
|
||||
cache: make(map[reflect.Type]*StructMap),
|
||||
tagName: tagName,
|
||||
mapFunc: mapFunc,
|
||||
tagMapFunc: tagMapFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapperFunc returns a new mapper which optionally obeys a field tag and
|
||||
// a struct field name mapper func given by f. Tags will take precedence, but
|
||||
// for any other field, the mapped name will be f(field.Name)
|
||||
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
|
||||
return &Mapper{
|
||||
cache: make(map[reflect.Type]*StructMap),
|
||||
tagName: tagName,
|
||||
mapFunc: f,
|
||||
}
|
||||
}
|
||||
|
||||
// TypeMap returns a mapping of field strings to int slices representing
|
||||
// the traversal down the struct to reach the field.
|
||||
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
|
||||
m.mutex.Lock()
|
||||
mapping, ok := m.cache[t]
|
||||
if !ok {
|
||||
mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
|
||||
m.cache[t] = mapping
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
return mapping
|
||||
}
|
||||
|
||||
// FieldMap returns the mapper's mapping of field names to reflect values. Panics
|
||||
// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
|
||||
func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
|
||||
v = reflect.Indirect(v)
|
||||
mustBe(v, reflect.Struct)
|
||||
|
||||
r := map[string]reflect.Value{}
|
||||
tm := m.TypeMap(v.Type())
|
||||
for tagName, fi := range tm.Names {
|
||||
r[tagName] = FieldByIndexes(v, fi.Index)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// FieldByName returns a field by its mapped name as a reflect.Value.
|
||||
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
|
||||
// Returns zero Value if the name is not found.
|
||||
func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
|
||||
v = reflect.Indirect(v)
|
||||
mustBe(v, reflect.Struct)
|
||||
|
||||
tm := m.TypeMap(v.Type())
|
||||
fi, ok := tm.Names[name]
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
return FieldByIndexes(v, fi.Index)
|
||||
}
|
||||
|
||||
// FieldsByName returns a slice of values corresponding to the slice of names
|
||||
// for the value. Panics if v's Kind is not Struct or v is not Indirectable
|
||||
// to a struct Kind. Returns zero Value for each name not found.
|
||||
func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
|
||||
v = reflect.Indirect(v)
|
||||
mustBe(v, reflect.Struct)
|
||||
|
||||
tm := m.TypeMap(v.Type())
|
||||
vals := make([]reflect.Value, 0, len(names))
|
||||
for _, name := range names {
|
||||
fi, ok := tm.Names[name]
|
||||
if !ok {
|
||||
vals = append(vals, *new(reflect.Value))
|
||||
} else {
|
||||
vals = append(vals, FieldByIndexes(v, fi.Index))
|
||||
}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// TraversalsByName returns a slice of int slices which represent the struct
|
||||
// traversals for each mapped name. Panics if t is not a struct or Indirectable
|
||||
// to a struct. Returns empty int slice for each name not found.
|
||||
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
|
||||
r := make([][]int, 0, len(names))
|
||||
m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
|
||||
if i == nil {
|
||||
r = append(r, []int{})
|
||||
} else {
|
||||
r = append(r, i)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
|
||||
// each name and the struct traversal represented by that name. Panics if t is not
|
||||
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
|
||||
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
|
||||
t = Deref(t)
|
||||
mustBe(t, reflect.Struct)
|
||||
tm := m.TypeMap(t)
|
||||
for i, name := range names {
|
||||
fi, ok := tm.Names[name]
|
||||
if !ok {
|
||||
if err := fn(i, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := fn(i, fi.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FieldByIndexes returns a value for the field given by the struct traversal
|
||||
// for the given value.
|
||||
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
|
||||
for _, i := range indexes {
|
||||
v = reflect.Indirect(v).Field(i)
|
||||
// if this is a pointer and it's nil, allocate a new value and set it
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
alloc := reflect.New(Deref(v.Type()))
|
||||
v.Set(alloc)
|
||||
}
|
||||
if v.Kind() == reflect.Map && v.IsNil() {
|
||||
v.Set(reflect.MakeMap(v.Type()))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// FieldByIndexesReadOnly returns a value for a particular struct traversal,
|
||||
// but is not concerned with allocating nil pointers because the value is
|
||||
// going to be used for reading and not setting.
|
||||
func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
|
||||
for _, i := range indexes {
|
||||
v = reflect.Indirect(v).Field(i)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Deref is Indirect for reflect.Types
|
||||
func Deref(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// -- helpers & utilities --
|
||||
|
||||
type kinder interface {
|
||||
Kind() reflect.Kind
|
||||
}
|
||||
|
||||
// mustBe checks a value against a kind, panicing with a reflect.ValueError
|
||||
// if the kind isn't that which is required.
|
||||
func mustBe(v kinder, expected reflect.Kind) {
|
||||
if k := v.Kind(); k != expected {
|
||||
panic(&reflect.ValueError{Method: methodName(), Kind: k})
|
||||
}
|
||||
}
|
||||
|
||||
// methodName returns the caller of the function calling methodName
|
||||
func methodName() string {
|
||||
pc, _, _, _ := runtime.Caller(2)
|
||||
f := runtime.FuncForPC(pc)
|
||||
if f == nil {
|
||||
return "unknown method"
|
||||
}
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
type typeQueue struct {
|
||||
t reflect.Type
|
||||
fi *FieldInfo
|
||||
pp string // Parent path
|
||||
}
|
||||
|
||||
// A copying append that creates a new slice each time.
|
||||
func apnd(is []int, i int) []int {
|
||||
x := make([]int, len(is)+1)
|
||||
copy(x, is)
|
||||
x[len(x)-1] = i
|
||||
return x
|
||||
}
|
||||
|
||||
type mapf func(string) string
|
||||
|
||||
// parseName parses the tag and the target name for the given field using
|
||||
// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
|
||||
// field's name to a target name, and tagMapFunc for mapping the tag to
|
||||
// a target name.
|
||||
func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
|
||||
// first, set the fieldName to the field's name
|
||||
fieldName = field.Name
|
||||
// if a mapFunc is set, use that to override the fieldName
|
||||
if mapFunc != nil {
|
||||
fieldName = mapFunc(fieldName)
|
||||
}
|
||||
|
||||
// if there's no tag to look for, return the field name
|
||||
if tagName == "" {
|
||||
return "", fieldName
|
||||
}
|
||||
|
||||
// if this tag is not set using the normal convention in the tag,
|
||||
// then return the fieldname.. this check is done because according
|
||||
// to the reflect documentation:
|
||||
// If the tag does not have the conventional format,
|
||||
// the value returned by Get is unspecified.
|
||||
// which doesn't sound great.
|
||||
if !strings.Contains(string(field.Tag), tagName+":") {
|
||||
return "", fieldName
|
||||
}
|
||||
|
||||
// at this point we're fairly sure that we have a tag, so lets pull it out
|
||||
tag = field.Tag.Get(tagName)
|
||||
|
||||
// if we have a mapper function, call it on the whole tag
|
||||
// XXX: this is a change from the old version, which pulled out the name
|
||||
// before the tagMapFunc could be run, but I think this is the right way
|
||||
if tagMapFunc != nil {
|
||||
tag = tagMapFunc(tag)
|
||||
}
|
||||
|
||||
// finally, split the options from the name
|
||||
parts := strings.Split(tag, ",")
|
||||
fieldName = parts[0]
|
||||
|
||||
return tag, fieldName
|
||||
}
|
||||
|
||||
// parseOptions parses options out of a tag string, skipping the name
|
||||
func parseOptions(tag string) map[string]string {
|
||||
parts := strings.Split(tag, ",")
|
||||
options := make(map[string]string, len(parts))
|
||||
if len(parts) > 1 {
|
||||
for _, opt := range parts[1:] {
|
||||
// short circuit potentially expensive split op
|
||||
if strings.Contains(opt, "=") {
|
||||
kv := strings.Split(opt, "=")
|
||||
options[kv[0]] = kv[1]
|
||||
continue
|
||||
}
|
||||
options[opt] = ""
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
|
||||
// tagMapFunc to determine the canonical names of fields.
|
||||
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
|
||||
m := []*FieldInfo{}
|
||||
|
||||
root := &FieldInfo{}
|
||||
queue := []typeQueue{}
|
||||
queue = append(queue, typeQueue{Deref(t), root, ""})
|
||||
|
||||
QueueLoop:
|
||||
for len(queue) != 0 {
|
||||
// pop the first item off of the queue
|
||||
tq := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
// ignore recursive field
|
||||
for p := tq.fi.Parent; p != nil; p = p.Parent {
|
||||
if tq.fi.Field.Type == p.Field.Type {
|
||||
continue QueueLoop
|
||||
}
|
||||
}
|
||||
|
||||
nChildren := 0
|
||||
if tq.t.Kind() == reflect.Struct {
|
||||
nChildren = tq.t.NumField()
|
||||
}
|
||||
tq.fi.Children = make([]*FieldInfo, nChildren)
|
||||
|
||||
// iterate through all of its fields
|
||||
for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
|
||||
|
||||
f := tq.t.Field(fieldPos)
|
||||
|
||||
// parse the tag and the target name using the mapping options for this field
|
||||
tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
|
||||
|
||||
// if the name is "-", disabled via a tag, skip it
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
fi := FieldInfo{
|
||||
Field: f,
|
||||
Name: name,
|
||||
Zero: reflect.New(f.Type).Elem(),
|
||||
Options: parseOptions(tag),
|
||||
}
|
||||
|
||||
// if the path is empty this path is just the name
|
||||
if tq.pp == "" {
|
||||
fi.Path = fi.Name
|
||||
} else {
|
||||
fi.Path = tq.pp + "." + fi.Name
|
||||
}
|
||||
|
||||
// skip unexported fields
|
||||
if len(f.PkgPath) != 0 && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
// bfs search of anonymous embedded structs
|
||||
if f.Anonymous {
|
||||
pp := tq.pp
|
||||
if tag != "" {
|
||||
pp = fi.Path
|
||||
}
|
||||
|
||||
fi.Embedded = true
|
||||
fi.Index = apnd(tq.fi.Index, fieldPos)
|
||||
nChildren := 0
|
||||
ft := Deref(f.Type)
|
||||
if ft.Kind() == reflect.Struct {
|
||||
nChildren = ft.NumField()
|
||||
}
|
||||
fi.Children = make([]*FieldInfo, nChildren)
|
||||
queue = append(queue, typeQueue{Deref(f.Type), &fi, pp})
|
||||
} else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) {
|
||||
fi.Index = apnd(tq.fi.Index, fieldPos)
|
||||
fi.Children = make([]*FieldInfo, Deref(f.Type).NumField())
|
||||
queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path})
|
||||
}
|
||||
|
||||
fi.Index = apnd(tq.fi.Index, fieldPos)
|
||||
fi.Parent = tq.fi
|
||||
tq.fi.Children[fieldPos] = &fi
|
||||
m = append(m, &fi)
|
||||
}
|
||||
}
|
||||
|
||||
flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
|
||||
for _, fi := range flds.Index {
|
||||
// check if nothing has already been pushed with the same path
|
||||
// sometimes you can choose to override a type using embedded struct
|
||||
fld, ok := flds.Paths[fi.Path]
|
||||
if !ok || fld.Embedded {
|
||||
flds.Paths[fi.Path] = fi
|
||||
if fi.Name != "" && !fi.Embedded {
|
||||
flds.Names[fi.Path] = fi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flds
|
||||
}
|
||||
1054
vendor/github.com/jmoiron/sqlx/sqlx.go
generated
vendored
Normal file
1054
vendor/github.com/jmoiron/sqlx/sqlx.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
415
vendor/github.com/jmoiron/sqlx/sqlx_context.go
generated
vendored
Normal file
415
vendor/github.com/jmoiron/sqlx/sqlx_context.go
generated
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
//go:build go1.8
|
||||
// +build go1.8
|
||||
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ConnectContext to a database and verify with a ping.
|
||||
func ConnectContext(ctx context.Context, driverName, dataSourceName string) (*DB, error) {
|
||||
db, err := Open(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return db, err
|
||||
}
|
||||
err = db.PingContext(ctx)
|
||||
return db, err
|
||||
}
|
||||
|
||||
// QueryerContext is an interface used by GetContext and SelectContext
|
||||
type QueryerContext interface {
|
||||
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||
QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
|
||||
QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row
|
||||
}
|
||||
|
||||
// PreparerContext is an interface used by PreparexContext.
|
||||
type PreparerContext interface {
|
||||
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// ExecerContext is an interface used by MustExecContext and LoadFileContext
|
||||
type ExecerContext interface {
|
||||
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
// ExtContext is a union interface which can bind, query, and exec, with Context
|
||||
// used by NamedQueryContext and NamedExecContext.
|
||||
type ExtContext interface {
|
||||
binder
|
||||
QueryerContext
|
||||
ExecerContext
|
||||
}
|
||||
|
||||
// SelectContext executes a query using the provided Queryer, and StructScans
|
||||
// each row into dest, which must be a slice. If the slice elements are
|
||||
// scannable, then the result set must have only one column. Otherwise,
|
||||
// StructScan is used. The *sql.Rows are closed automatically.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func SelectContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
|
||||
rows, err := q.QueryxContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if something happens here, we want to make sure the rows are Closed
|
||||
defer rows.Close()
|
||||
return scanAll(rows, dest, false)
|
||||
}
|
||||
|
||||
// PreparexContext prepares a statement.
|
||||
//
|
||||
// The provided context is used for the preparation of the statement, not for
|
||||
// the execution of the statement.
|
||||
func PreparexContext(ctx context.Context, p PreparerContext, query string) (*Stmt, error) {
|
||||
s, err := p.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
|
||||
}
|
||||
|
||||
// GetContext does a QueryRow using the provided Queryer, and scans the
|
||||
// resulting row to dest. If dest is scannable, the result must only have one
|
||||
// column. Otherwise, StructScan is used. Get will return sql.ErrNoRows like
|
||||
// row.Scan would. Any placeholder parameters are replaced with supplied args.
|
||||
// An error is returned if the result set is empty.
|
||||
func GetContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
|
||||
r := q.QueryRowxContext(ctx, query, args...)
|
||||
return r.scanAny(dest, false)
|
||||
}
|
||||
|
||||
// LoadFileContext exec's every statement in a file (as a single call to Exec).
|
||||
// LoadFileContext may return a nil *sql.Result if errors are encountered
|
||||
// locating or reading the file at path. LoadFile reads the entire file into
|
||||
// memory, so it is not suitable for loading large data dumps, but can be useful
|
||||
// for initializing schemas or loading indexes.
|
||||
//
|
||||
// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3
|
||||
// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting
|
||||
// this by requiring something with DriverName() and then attempting to split the
|
||||
// queries will be difficult to get right, and its current driver-specific behavior
|
||||
// is deemed at least not complex in its incorrectness.
|
||||
func LoadFileContext(ctx context.Context, e ExecerContext, path string) (*sql.Result, error) {
|
||||
realpath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents, err := ioutil.ReadFile(realpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := e.ExecContext(ctx, string(contents))
|
||||
return &res, err
|
||||
}
|
||||
|
||||
// MustExecContext execs the query using e and panics if there was an error.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func MustExecContext(ctx context.Context, e ExecerContext, query string, args ...interface{}) sql.Result {
|
||||
res, err := e.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// PrepareNamedContext returns an sqlx.NamedStmt
|
||||
func (db *DB) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
|
||||
return prepareNamedContext(ctx, db, query)
|
||||
}
|
||||
|
||||
// NamedQueryContext using this DB.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (db *DB) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) {
|
||||
return NamedQueryContext(ctx, db, query, arg)
|
||||
}
|
||||
|
||||
// NamedExecContext using this DB.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (db *DB) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
|
||||
return NamedExecContext(ctx, db, query, arg)
|
||||
}
|
||||
|
||||
// SelectContext using this DB.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||
return SelectContext(ctx, db, dest, query, args...)
|
||||
}
|
||||
|
||||
// GetContext using this DB.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
// An error is returned if the result set is empty.
|
||||
func (db *DB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||
return GetContext(ctx, db, dest, query, args...)
|
||||
}
|
||||
|
||||
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
|
||||
//
|
||||
// The provided context is used for the preparation of the statement, not for
|
||||
// the execution of the statement.
|
||||
func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
|
||||
return PreparexContext(ctx, db, query)
|
||||
}
|
||||
|
||||
// QueryxContext queries the database and returns an *sqlx.Rows.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := db.DB.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err
|
||||
}
|
||||
|
||||
// QueryRowxContext queries the database and returns an *sqlx.Row.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||
rows, err := db.DB.QueryContext(ctx, query, args...)
|
||||
return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
|
||||
}
|
||||
|
||||
// MustBeginTx starts a transaction, and panics on error. Returns an *sqlx.Tx instead
|
||||
// of an *sql.Tx.
|
||||
//
|
||||
// The provided context is used until the transaction is committed or rolled
|
||||
// back. If the context is canceled, the sql package will roll back the
|
||||
// transaction. Tx.Commit will return an error if the context provided to
|
||||
// MustBeginContext is canceled.
|
||||
func (db *DB) MustBeginTx(ctx context.Context, opts *sql.TxOptions) *Tx {
|
||||
tx, err := db.BeginTxx(ctx, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
// MustExecContext (panic) runs MustExec using this database.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (db *DB) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
|
||||
return MustExecContext(ctx, db, query, args...)
|
||||
}
|
||||
|
||||
// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an
|
||||
// *sql.Tx.
|
||||
//
|
||||
// The provided context is used until the transaction is committed or rolled
|
||||
// back. If the context is canceled, the sql package will roll back the
|
||||
// transaction. Tx.Commit will return an error if the context provided to
|
||||
// BeginxContext is canceled.
|
||||
func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||
tx, err := db.DB.BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err
|
||||
}
|
||||
|
||||
// Connx returns an *sqlx.Conn instead of an *sql.Conn.
|
||||
func (db *DB) Connx(ctx context.Context) (*Conn, error) {
|
||||
conn, err := db.DB.Conn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Conn{Conn: conn, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, nil
|
||||
}
|
||||
|
||||
// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an
|
||||
// *sql.Tx.
|
||||
//
|
||||
// The provided context is used until the transaction is committed or rolled
|
||||
// back. If the context is canceled, the sql package will roll back the
|
||||
// transaction. Tx.Commit will return an error if the context provided to
|
||||
// BeginxContext is canceled.
|
||||
func (c *Conn) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||
tx, err := c.Conn.BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{Tx: tx, driverName: c.driverName, unsafe: c.unsafe, Mapper: c.Mapper}, err
|
||||
}
|
||||
|
||||
// SelectContext using this Conn.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (c *Conn) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||
return SelectContext(ctx, c, dest, query, args...)
|
||||
}
|
||||
|
||||
// GetContext using this Conn.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
// An error is returned if the result set is empty.
|
||||
func (c *Conn) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||
return GetContext(ctx, c, dest, query, args...)
|
||||
}
|
||||
|
||||
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
|
||||
//
|
||||
// The provided context is used for the preparation of the statement, not for
|
||||
// the execution of the statement.
|
||||
func (c *Conn) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
|
||||
return PreparexContext(ctx, c, query)
|
||||
}
|
||||
|
||||
// QueryxContext queries the database and returns an *sqlx.Rows.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (c *Conn) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := c.Conn.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: c.unsafe, Mapper: c.Mapper}, err
|
||||
}
|
||||
|
||||
// QueryRowxContext queries the database and returns an *sqlx.Row.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (c *Conn) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||
rows, err := c.Conn.QueryContext(ctx, query, args...)
|
||||
return &Row{rows: rows, err: err, unsafe: c.unsafe, Mapper: c.Mapper}
|
||||
}
|
||||
|
||||
// Rebind a query within a Conn's bindvar type.
|
||||
func (c *Conn) Rebind(query string) string {
|
||||
return Rebind(BindType(c.driverName), query)
|
||||
}
|
||||
|
||||
// StmtxContext returns a version of the prepared statement which runs within a
|
||||
// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt.
|
||||
func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt {
|
||||
var s *sql.Stmt
|
||||
switch v := stmt.(type) {
|
||||
case Stmt:
|
||||
s = v.Stmt
|
||||
case *Stmt:
|
||||
s = v.Stmt
|
||||
case *sql.Stmt:
|
||||
s = v
|
||||
default:
|
||||
panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
|
||||
}
|
||||
return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper}
|
||||
}
|
||||
|
||||
// NamedStmtContext returns a version of the prepared statement which runs
|
||||
// within a transaction.
|
||||
func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt {
|
||||
return &NamedStmt{
|
||||
QueryString: stmt.QueryString,
|
||||
Params: stmt.Params,
|
||||
Stmt: tx.StmtxContext(ctx, stmt.Stmt),
|
||||
}
|
||||
}
|
||||
|
||||
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
|
||||
//
|
||||
// The provided context is used for the preparation of the statement, not for
|
||||
// the execution of the statement.
|
||||
func (tx *Tx) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
|
||||
return PreparexContext(ctx, tx, query)
|
||||
}
|
||||
|
||||
// PrepareNamedContext returns an sqlx.NamedStmt
|
||||
func (tx *Tx) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
|
||||
return prepareNamedContext(ctx, tx, query)
|
||||
}
|
||||
|
||||
// MustExecContext runs MustExecContext within a transaction.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (tx *Tx) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
|
||||
return MustExecContext(ctx, tx, query, args...)
|
||||
}
|
||||
|
||||
// QueryxContext within a transaction and context.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (tx *Tx) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := tx.Tx.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err
|
||||
}
|
||||
|
||||
// SelectContext within a transaction and context.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (tx *Tx) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||
return SelectContext(ctx, tx, dest, query, args...)
|
||||
}
|
||||
|
||||
// GetContext within a transaction and context.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
// An error is returned if the result set is empty.
|
||||
func (tx *Tx) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||
return GetContext(ctx, tx, dest, query, args...)
|
||||
}
|
||||
|
||||
// QueryRowxContext within a transaction and context.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (tx *Tx) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||
rows, err := tx.Tx.QueryContext(ctx, query, args...)
|
||||
return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
|
||||
}
|
||||
|
||||
// NamedExecContext using this Tx.
|
||||
// Any named placeholder parameters are replaced with fields from arg.
|
||||
func (tx *Tx) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
|
||||
return NamedExecContext(ctx, tx, query, arg)
|
||||
}
|
||||
|
||||
// SelectContext using the prepared statement.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (s *Stmt) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error {
|
||||
return SelectContext(ctx, &qStmt{s}, dest, "", args...)
|
||||
}
|
||||
|
||||
// GetContext using the prepared statement.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
// An error is returned if the result set is empty.
|
||||
func (s *Stmt) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error {
|
||||
return GetContext(ctx, &qStmt{s}, dest, "", args...)
|
||||
}
|
||||
|
||||
// MustExecContext (panic) using this statement. Note that the query portion of
|
||||
// the error output will be blank, as Stmt does not expose its query.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (s *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result {
|
||||
return MustExecContext(ctx, &qStmt{s}, "", args...)
|
||||
}
|
||||
|
||||
// QueryRowxContext using this statement.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (s *Stmt) QueryRowxContext(ctx context.Context, args ...interface{}) *Row {
|
||||
qs := &qStmt{s}
|
||||
return qs.QueryRowxContext(ctx, "", args...)
|
||||
}
|
||||
|
||||
// QueryxContext using this statement.
|
||||
// Any placeholder parameters are replaced with supplied args.
|
||||
func (s *Stmt) QueryxContext(ctx context.Context, args ...interface{}) (*Rows, error) {
|
||||
qs := &qStmt{s}
|
||||
return qs.QueryxContext(ctx, "", args...)
|
||||
}
|
||||
|
||||
func (q *qStmt) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return q.Stmt.QueryContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (q *qStmt) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := q.Stmt.QueryContext(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err
|
||||
}
|
||||
|
||||
func (q *qStmt) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||
rows, err := q.Stmt.QueryContext(ctx, args...)
|
||||
return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}
|
||||
}
|
||||
|
||||
func (q *qStmt) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
return q.Stmt.ExecContext(ctx, args...)
|
||||
}
|
||||
9
vendor/github.com/jonboulle/clockwork/README.md
generated
vendored
9
vendor/github.com/jonboulle/clockwork/README.md
generated
vendored
@@ -36,6 +36,7 @@ Now you can easily test `myFunc` with a `FakeClock`:
|
||||
|
||||
```go
|
||||
func TestMyFunc(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c := clockwork.NewFakeClock()
|
||||
|
||||
// Start our sleepy function
|
||||
@@ -46,8 +47,12 @@ func TestMyFunc(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// Ensure we wait until myFunc is sleeping
|
||||
c.BlockUntil(1)
|
||||
// Ensure we wait until myFunc is waiting on the clock.
|
||||
// Use a context to avoid blocking forever if something
|
||||
// goes wrong.
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
c.BlockUntilContext(ctx, 1)
|
||||
|
||||
assertState()
|
||||
|
||||
|
||||
19
vendor/github.com/jonboulle/clockwork/SECURITY.md
generated
vendored
Normal file
19
vendor/github.com/jonboulle/clockwork/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
If you have discovered a security vulnerability in this project, please report it
|
||||
privately. **Do not disclose it as a public issue.** This gives me time to work with you
|
||||
to fix the issue before public exposure, reducing the chance that the exploit will be
|
||||
used before a patch is released.
|
||||
|
||||
You may submit the report in the following ways:
|
||||
|
||||
- send an email to ???@???; and/or
|
||||
- send a [private vulnerability report](https://github.com/jonboulle/clockwork/security/advisories/new)
|
||||
|
||||
Please provide the following information in your report:
|
||||
|
||||
- A description of the vulnerability and its impact
|
||||
- How to reproduce the issue
|
||||
|
||||
This project is maintained by a single maintainer on a reasonable-effort basis. As such,
|
||||
please give me 90 days to work on a fix before public exposure.
|
||||
264
vendor/github.com/jonboulle/clockwork/clockwork.go
generated
vendored
264
vendor/github.com/jonboulle/clockwork/clockwork.go
generated
vendored
@@ -1,8 +1,10 @@
|
||||
// Package clockwork contains a simple fake clock for Go.
|
||||
package clockwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"errors"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -14,49 +16,18 @@ type Clock interface {
|
||||
Sleep(d time.Duration)
|
||||
Now() time.Time
|
||||
Since(t time.Time) time.Duration
|
||||
Until(t time.Time) time.Duration
|
||||
NewTicker(d time.Duration) Ticker
|
||||
NewTimer(d time.Duration) Timer
|
||||
AfterFunc(d time.Duration, f func()) Timer
|
||||
}
|
||||
|
||||
// FakeClock provides an interface for a clock which can be manually advanced
|
||||
// through time.
|
||||
//
|
||||
// FakeClock maintains a list of "waiters," which consists of all callers
|
||||
// waiting on the underlying clock (i.e. Tickers and Timers including callers of
|
||||
// Sleep or After). Users can call BlockUntil to block until the clock has an
|
||||
// expected number of waiters.
|
||||
type FakeClock interface {
|
||||
Clock
|
||||
// Advance advances the FakeClock to a new point in time, ensuring any existing
|
||||
// waiters are notified appropriately before returning.
|
||||
Advance(d time.Duration)
|
||||
// BlockUntil blocks until the FakeClock has the given number of waiters.
|
||||
BlockUntil(waiters int)
|
||||
}
|
||||
|
||||
// NewRealClock returns a Clock which simply delegates calls to the actual time
|
||||
// package; it should be used by packages in production.
|
||||
func NewRealClock() Clock {
|
||||
return &realClock{}
|
||||
}
|
||||
|
||||
// NewFakeClock returns a FakeClock implementation which can be
|
||||
// manually advanced through time for testing. The initial time of the
|
||||
// FakeClock will be the current system time.
|
||||
//
|
||||
// Tests that require a deterministic time must use NewFakeClockAt.
|
||||
func NewFakeClock() FakeClock {
|
||||
return NewFakeClockAt(time.Now())
|
||||
}
|
||||
|
||||
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
|
||||
func NewFakeClockAt(t time.Time) FakeClock {
|
||||
return &fakeClock{
|
||||
time: t,
|
||||
}
|
||||
}
|
||||
|
||||
type realClock struct{}
|
||||
|
||||
func (rc *realClock) After(d time.Duration) <-chan time.Time {
|
||||
@@ -75,6 +46,10 @@ func (rc *realClock) Since(t time.Time) time.Duration {
|
||||
return rc.Now().Sub(t)
|
||||
}
|
||||
|
||||
func (rc *realClock) Until(t time.Time) time.Duration {
|
||||
return t.Sub(rc.Now())
|
||||
}
|
||||
|
||||
func (rc *realClock) NewTicker(d time.Duration) Ticker {
|
||||
return realTicker{time.NewTicker(d)}
|
||||
}
|
||||
@@ -87,7 +62,14 @@ func (rc *realClock) AfterFunc(d time.Duration, f func()) Timer {
|
||||
return realTimer{time.AfterFunc(d, f)}
|
||||
}
|
||||
|
||||
type fakeClock struct {
|
||||
// FakeClock provides an interface for a clock which can be manually advanced
|
||||
// through time.
|
||||
//
|
||||
// FakeClock maintains a list of "waiters," which consists of all callers
|
||||
// waiting on the underlying clock (i.e. Tickers and Timers including callers of
|
||||
// Sleep or After). Users can call BlockUntil to block until the clock has an
|
||||
// expected number of waiters.
|
||||
type FakeClock struct {
|
||||
// l protects all attributes of the clock, including all attributes of all
|
||||
// waiters and blockers.
|
||||
l sync.RWMutex
|
||||
@@ -96,11 +78,27 @@ type fakeClock struct {
|
||||
time time.Time
|
||||
}
|
||||
|
||||
// NewFakeClock returns a FakeClock implementation which can be
|
||||
// manually advanced through time for testing. The initial time of the
|
||||
// FakeClock will be the current system time.
|
||||
//
|
||||
// Tests that require a deterministic time must use NewFakeClockAt.
|
||||
func NewFakeClock() *FakeClock {
|
||||
return NewFakeClockAt(time.Now())
|
||||
}
|
||||
|
||||
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
|
||||
func NewFakeClockAt(t time.Time) *FakeClock {
|
||||
return &FakeClock{
|
||||
time: t,
|
||||
}
|
||||
}
|
||||
|
||||
// blocker is a caller of BlockUntil.
|
||||
type blocker struct {
|
||||
count int
|
||||
|
||||
// ch is closed when the underlying clock has the specificed number of blockers.
|
||||
// ch is closed when the underlying clock has the specified number of blockers.
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
@@ -111,23 +109,23 @@ type expirer interface {
|
||||
expire(now time.Time) (next *time.Duration)
|
||||
|
||||
// Get and set the expiration time.
|
||||
expiry() time.Time
|
||||
setExpiry(time.Time)
|
||||
expiration() time.Time
|
||||
setExpiration(time.Time)
|
||||
}
|
||||
|
||||
// After mimics [time.After]; it waits for the given duration to elapse on the
|
||||
// fakeClock, then sends the current time on the returned channel.
|
||||
func (fc *fakeClock) After(d time.Duration) <-chan time.Time {
|
||||
func (fc *FakeClock) After(d time.Duration) <-chan time.Time {
|
||||
return fc.NewTimer(d).Chan()
|
||||
}
|
||||
|
||||
// Sleep blocks until the given duration has passed on the fakeClock.
|
||||
func (fc *fakeClock) Sleep(d time.Duration) {
|
||||
func (fc *FakeClock) Sleep(d time.Duration) {
|
||||
<-fc.After(d)
|
||||
}
|
||||
|
||||
// Now returns the current time of the fakeClock
|
||||
func (fc *fakeClock) Now() time.Time {
|
||||
func (fc *FakeClock) Now() time.Time {
|
||||
fc.l.RLock()
|
||||
defer fc.l.RUnlock()
|
||||
return fc.time
|
||||
@@ -135,61 +133,73 @@ func (fc *fakeClock) Now() time.Time {
|
||||
|
||||
// Since returns the duration that has passed since the given time on the
|
||||
// fakeClock.
|
||||
func (fc *fakeClock) Since(t time.Time) time.Duration {
|
||||
func (fc *FakeClock) Since(t time.Time) time.Duration {
|
||||
return fc.Now().Sub(t)
|
||||
}
|
||||
|
||||
// Until returns the duration that has to pass from the given time on the fakeClock
|
||||
// to reach the given time.
|
||||
func (fc *FakeClock) Until(t time.Time) time.Duration {
|
||||
return t.Sub(fc.Now())
|
||||
}
|
||||
|
||||
// NewTicker returns a Ticker that will expire only after calls to
|
||||
// fakeClock.Advance() have moved the clock past the given duration.
|
||||
func (fc *fakeClock) NewTicker(d time.Duration) Ticker {
|
||||
var ft *fakeTicker
|
||||
ft = &fakeTicker{
|
||||
firer: newFirer(),
|
||||
d: d,
|
||||
reset: func(d time.Duration) { fc.set(ft, d) },
|
||||
stop: func() { fc.stop(ft) },
|
||||
// FakeClock.Advance() have moved the clock past the given duration.
|
||||
//
|
||||
// The duration d must be greater than zero; if not, NewTicker will panic.
|
||||
func (fc *FakeClock) NewTicker(d time.Duration) Ticker {
|
||||
// Maintain parity with
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/time/tick.go;l=23-25
|
||||
if d <= 0 {
|
||||
panic(errors.New("non-positive interval for NewTicker"))
|
||||
}
|
||||
fc.set(ft, d)
|
||||
ft := newFakeTicker(fc, d)
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
fc.setExpirer(ft, d)
|
||||
return ft
|
||||
}
|
||||
|
||||
// NewTimer returns a Timer that will fire only after calls to
|
||||
// fakeClock.Advance() have moved the clock past the given duration.
|
||||
func (fc *fakeClock) NewTimer(d time.Duration) Timer {
|
||||
return fc.newTimer(d, nil)
|
||||
func (fc *FakeClock) NewTimer(d time.Duration) Timer {
|
||||
t, _ := fc.newTimer(d, nil)
|
||||
return t
|
||||
}
|
||||
|
||||
// AfterFunc mimics [time.AfterFunc]; it returns a Timer that will invoke the
|
||||
// given function only after calls to fakeClock.Advance() have moved the clock
|
||||
// past the given duration.
|
||||
func (fc *fakeClock) AfterFunc(d time.Duration, f func()) Timer {
|
||||
return fc.newTimer(d, f)
|
||||
func (fc *FakeClock) AfterFunc(d time.Duration, f func()) Timer {
|
||||
t, _ := fc.newTimer(d, f)
|
||||
return t
|
||||
}
|
||||
|
||||
// newTimer returns a new timer, using an optional afterFunc.
|
||||
func (fc *fakeClock) newTimer(d time.Duration, afterfunc func()) *fakeTimer {
|
||||
var ft *fakeTimer
|
||||
ft = &fakeTimer{
|
||||
firer: newFirer(),
|
||||
reset: func(d time.Duration) bool {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
// fc.l must be held across the calls to stopExpirer & setExpirer.
|
||||
stopped := fc.stopExpirer(ft)
|
||||
fc.setExpirer(ft, d)
|
||||
return stopped
|
||||
},
|
||||
stop: func() bool { return fc.stop(ft) },
|
||||
// newTimer returns a new timer using an optional afterFunc and the time that
|
||||
// timer expires.
|
||||
func (fc *FakeClock) newTimer(d time.Duration, afterfunc func()) (*fakeTimer, time.Time) {
|
||||
ft := newFakeTimer(fc, afterfunc)
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
fc.setExpirer(ft, d)
|
||||
return ft, ft.expiration()
|
||||
}
|
||||
|
||||
afterFunc: afterfunc,
|
||||
}
|
||||
fc.set(ft, d)
|
||||
// newTimerAtTime is like newTimer, but uses a time instead of a duration.
|
||||
//
|
||||
// It is used to ensure FakeClock's lock is held constant through calling
|
||||
// fc.After(t.Sub(fc.Now())). It should not be exposed externally.
|
||||
func (fc *FakeClock) newTimerAtTime(t time.Time, afterfunc func()) *fakeTimer {
|
||||
ft := newFakeTimer(fc, afterfunc)
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
fc.setExpirer(ft, t.Sub(fc.time))
|
||||
return ft
|
||||
}
|
||||
|
||||
// Advance advances fakeClock to a new point in time, ensuring waiters and
|
||||
// blockers are notified appropriately before returning.
|
||||
func (fc *fakeClock) Advance(d time.Duration) {
|
||||
func (fc *FakeClock) Advance(d time.Duration) {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
end := fc.time.Add(d)
|
||||
@@ -198,38 +208,34 @@ func (fc *fakeClock) Advance(d time.Duration) {
|
||||
//
|
||||
// We don't iterate because the callback of the waiter might register a new
|
||||
// waiter, so the list of waiters might change as we execute this.
|
||||
for len(fc.waiters) > 0 && !end.Before(fc.waiters[0].expiry()) {
|
||||
for len(fc.waiters) > 0 && !end.Before(fc.waiters[0].expiration()) {
|
||||
w := fc.waiters[0]
|
||||
fc.waiters = fc.waiters[1:]
|
||||
|
||||
// Use the waiter's expriation as the current time for this expiration.
|
||||
now := w.expiry()
|
||||
// Use the waiter's expiration as the current time for this expiration.
|
||||
now := w.expiration()
|
||||
fc.time = now
|
||||
if d := w.expire(now); d != nil {
|
||||
// Set the new exipration if needed.
|
||||
// Set the new expiration if needed.
|
||||
fc.setExpirer(w, *d)
|
||||
}
|
||||
}
|
||||
fc.time = end
|
||||
}
|
||||
|
||||
// BlockUntil blocks until the fakeClock has the given number of waiters.
|
||||
// BlockUntil blocks until the FakeClock has the given number of waiters.
|
||||
//
|
||||
// Prefer BlockUntilContext, which offers context cancellation to prevent
|
||||
// deadlock.
|
||||
// Prefer BlockUntilContext in new code, which offers context cancellation to
|
||||
// prevent deadlock.
|
||||
//
|
||||
// Deprecation warning: This function might be deprecated in later versions.
|
||||
func (fc *fakeClock) BlockUntil(n int) {
|
||||
b := fc.newBlocker(n)
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
<-b.ch
|
||||
// Deprecated: New code should prefer BlockUntilContext.
|
||||
func (fc *FakeClock) BlockUntil(n int) {
|
||||
fc.BlockUntilContext(context.TODO(), n)
|
||||
}
|
||||
|
||||
// BlockUntilContext blocks until the fakeClock has the given number of waiters
|
||||
// or the context is cancelled.
|
||||
func (fc *fakeClock) BlockUntilContext(ctx context.Context, n int) error {
|
||||
func (fc *FakeClock) BlockUntilContext(ctx context.Context, n int) error {
|
||||
b := fc.newBlocker(n)
|
||||
if b == nil {
|
||||
return nil
|
||||
@@ -243,7 +249,7 @@ func (fc *fakeClock) BlockUntilContext(ctx context.Context, n int) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (fc *fakeClock) newBlocker(n int) *blocker {
|
||||
func (fc *FakeClock) newBlocker(n int) *blocker {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
// Fast path: we already have >= n waiters.
|
||||
@@ -260,7 +266,7 @@ func (fc *fakeClock) newBlocker(n int) *blocker {
|
||||
}
|
||||
|
||||
// stop stops an expirer, returning true if the expirer was stopped.
|
||||
func (fc *fakeClock) stop(e expirer) bool {
|
||||
func (fc *FakeClock) stop(e expirer) bool {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
return fc.stopExpirer(e)
|
||||
@@ -269,81 +275,45 @@ func (fc *fakeClock) stop(e expirer) bool {
|
||||
// stopExpirer stops an expirer, returning true if the expirer was stopped.
|
||||
//
|
||||
// The caller must hold fc.l.
|
||||
func (fc *fakeClock) stopExpirer(e expirer) bool {
|
||||
for i, t := range fc.waiters {
|
||||
if t == e {
|
||||
// Remove element, maintaining order.
|
||||
copy(fc.waiters[i:], fc.waiters[i+1:])
|
||||
fc.waiters[len(fc.waiters)-1] = nil
|
||||
fc.waiters = fc.waiters[:len(fc.waiters)-1]
|
||||
return true
|
||||
}
|
||||
func (fc *FakeClock) stopExpirer(e expirer) bool {
|
||||
idx := slices.Index(fc.waiters, e)
|
||||
if idx == -1 {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// set sets an expirer to expire at a future point in time.
|
||||
func (fc *fakeClock) set(e expirer, d time.Duration) {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
fc.setExpirer(e, d)
|
||||
// Remove element, maintaining order, setting inaccessible elements to nil so
|
||||
// they can be garbage collected.
|
||||
copy(fc.waiters[idx:], fc.waiters[idx+1:])
|
||||
fc.waiters[len(fc.waiters)-1] = nil
|
||||
fc.waiters = fc.waiters[:len(fc.waiters)-1]
|
||||
return true
|
||||
}
|
||||
|
||||
// setExpirer sets an expirer to expire at a future point in time.
|
||||
//
|
||||
// The caller must hold fc.l.
|
||||
func (fc *fakeClock) setExpirer(e expirer, d time.Duration) {
|
||||
func (fc *FakeClock) setExpirer(e expirer, d time.Duration) {
|
||||
if d.Nanoseconds() <= 0 {
|
||||
// special case - trigger immediately, never reset.
|
||||
// Special case for timers with duration <= 0: trigger immediately, never
|
||||
// reset.
|
||||
//
|
||||
// TODO: Explain what cases this covers.
|
||||
// Tickers never get here, they panic if d is < 0.
|
||||
e.expire(fc.time)
|
||||
return
|
||||
}
|
||||
// Add the expirer to the set of waiters and notify any blockers.
|
||||
e.setExpiry(fc.time.Add(d))
|
||||
e.setExpiration(fc.time.Add(d))
|
||||
fc.waiters = append(fc.waiters, e)
|
||||
sort.Slice(fc.waiters, func(i int, j int) bool {
|
||||
return fc.waiters[i].expiry().Before(fc.waiters[j].expiry())
|
||||
slices.SortFunc(fc.waiters, func(a, b expirer) int {
|
||||
return a.expiration().Compare(b.expiration())
|
||||
})
|
||||
|
||||
// Notify blockers of our new waiter.
|
||||
var blocked []*blocker
|
||||
// Notify blockers of our new waiter.
|
||||
count := len(fc.waiters)
|
||||
for _, b := range fc.blockers {
|
||||
fc.blockers = slices.DeleteFunc(fc.blockers, func(b *blocker) bool {
|
||||
if b.count <= count {
|
||||
close(b.ch)
|
||||
continue
|
||||
return true
|
||||
}
|
||||
blocked = append(blocked, b)
|
||||
}
|
||||
fc.blockers = blocked
|
||||
}
|
||||
|
||||
// firer is used by fakeTimer and fakeTicker used to help implement expirer.
|
||||
type firer struct {
|
||||
// The channel associated with the firer, used to send expriation times.
|
||||
c chan time.Time
|
||||
|
||||
// The time when the firer expires. Only meaningful if the firer is currently
|
||||
// one of a fakeClock's waiters.
|
||||
exp time.Time
|
||||
}
|
||||
|
||||
func newFirer() firer {
|
||||
return firer{c: make(chan time.Time, 1)}
|
||||
}
|
||||
|
||||
func (f *firer) Chan() <-chan time.Time {
|
||||
return f.c
|
||||
}
|
||||
|
||||
// expiry implements expirer.
|
||||
func (f *firer) expiry() time.Time {
|
||||
return f.exp
|
||||
}
|
||||
|
||||
// setExpiry implements expirer.
|
||||
func (f *firer) setExpiry(t time.Time) {
|
||||
f.exp = t
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
148
vendor/github.com/jonboulle/clockwork/context.go
generated
vendored
148
vendor/github.com/jonboulle/clockwork/context.go
generated
vendored
@@ -2,24 +2,168 @@ package clockwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// contextKey is private to this package so we can ensure uniqueness here. This
|
||||
// type identifies context values provided by this package.
|
||||
type contextKey string
|
||||
|
||||
// keyClock provides a clock for injecting during tests. If absent, a real clock should be used.
|
||||
// keyClock provides a clock for injecting during tests. If absent, a real clock
|
||||
// should be used.
|
||||
var keyClock = contextKey("clock") // clockwork.Clock
|
||||
|
||||
// AddToContext creates a derived context that references the specified clock.
|
||||
//
|
||||
// Be aware this doesn't change the behavior of standard library functions, such
|
||||
// as [context.WithTimeout] or [context.WithDeadline]. For this reason, users
|
||||
// should prefer passing explicit [clockwork.Clock] variables rather can passing
|
||||
// the clock via the context.
|
||||
func AddToContext(ctx context.Context, clock Clock) context.Context {
|
||||
return context.WithValue(ctx, keyClock, clock)
|
||||
}
|
||||
|
||||
// FromContext extracts a clock from the context. If not present, a real clock is returned.
|
||||
// FromContext extracts a clock from the context. If not present, a real clock
|
||||
// is returned.
|
||||
func FromContext(ctx context.Context) Clock {
|
||||
if clock, ok := ctx.Value(keyClock).(Clock); ok {
|
||||
return clock
|
||||
}
|
||||
return NewRealClock()
|
||||
}
|
||||
|
||||
// ErrFakeClockDeadlineExceeded is the error returned by [context.Context] when
|
||||
// the deadline passes on a context which uses a [FakeClock].
|
||||
//
|
||||
// It wraps a [context.DeadlineExceeded] error, i.e.:
|
||||
//
|
||||
// // The following is true for any Context whose deadline has been exceeded,
|
||||
// // including contexts made with clockwork.WithDeadline or clockwork.WithTimeout.
|
||||
//
|
||||
// errors.Is(ctx.Err(), context.DeadlineExceeded)
|
||||
//
|
||||
// // The following can only be true for contexts made
|
||||
// // with clockwork.WithDeadline or clockwork.WithTimeout.
|
||||
//
|
||||
// errors.Is(ctx.Err(), clockwork.ErrFakeClockDeadlineExceeded)
|
||||
var ErrFakeClockDeadlineExceeded error = fmt.Errorf("clockwork.FakeClock: %w", context.DeadlineExceeded)
|
||||
|
||||
// WithDeadline returns a context with a deadline based on a [FakeClock].
|
||||
//
|
||||
// The returned context ignores parent cancelation if the parent was cancelled
|
||||
// with a [context.DeadlineExceeded] error. Any other error returned by the
|
||||
// parent is treated normally, cancelling the returned context.
|
||||
//
|
||||
// If the parent is cancelled with a [context.DeadlineExceeded] error, the only
|
||||
// way to then cancel the returned context is by calling the returned
|
||||
// context.CancelFunc.
|
||||
func WithDeadline(parent context.Context, clock Clock, t time.Time) (context.Context, context.CancelFunc) {
|
||||
if fc, ok := clock.(*FakeClock); ok {
|
||||
return newFakeClockContext(parent, t, fc.newTimerAtTime(t, nil).Chan())
|
||||
}
|
||||
return context.WithDeadline(parent, t)
|
||||
}
|
||||
|
||||
// WithTimeout returns a context with a timeout based on a [FakeClock].
|
||||
//
|
||||
// The returned context follows the same behaviors as [WithDeadline].
|
||||
func WithTimeout(parent context.Context, clock Clock, d time.Duration) (context.Context, context.CancelFunc) {
|
||||
if fc, ok := clock.(*FakeClock); ok {
|
||||
t, deadline := fc.newTimer(d, nil)
|
||||
return newFakeClockContext(parent, deadline, t.Chan())
|
||||
}
|
||||
return context.WithTimeout(parent, d)
|
||||
}
|
||||
|
||||
// fakeClockContext implements context.Context, using a fake clock for its
|
||||
// deadline.
|
||||
//
|
||||
// It ignores parent cancellation if the parent is cancelled with
|
||||
// context.DeadlineExceeded.
|
||||
type fakeClockContext struct {
|
||||
parent context.Context
|
||||
deadline time.Time // The user-facing deadline based on the fake clock's time.
|
||||
|
||||
// Tracks timeout/deadline cancellation.
|
||||
timerDone <-chan time.Time
|
||||
|
||||
// Tracks manual calls to the cancel function.
|
||||
cancel func() // Closes cancelCalled wrapped in a sync.Once.
|
||||
cancelCalled chan struct{}
|
||||
|
||||
// The user-facing data from the context.Context interface.
|
||||
ctxDone chan struct{} // Returned by Done().
|
||||
err error // nil until ctxDone is ready to be closed.
|
||||
}
|
||||
|
||||
func newFakeClockContext(parent context.Context, deadline time.Time, timer <-chan time.Time) (context.Context, context.CancelFunc) {
|
||||
cancelCalled := make(chan struct{})
|
||||
ctx := &fakeClockContext{
|
||||
parent: parent,
|
||||
deadline: deadline,
|
||||
timerDone: timer,
|
||||
cancelCalled: cancelCalled,
|
||||
ctxDone: make(chan struct{}),
|
||||
cancel: sync.OnceFunc(func() {
|
||||
close(cancelCalled)
|
||||
}),
|
||||
}
|
||||
ready := make(chan struct{}, 1)
|
||||
go ctx.runCancel(ready)
|
||||
<-ready // Wait until the cancellation goroutine is running.
|
||||
return ctx, ctx.cancel
|
||||
}
|
||||
|
||||
func (c *fakeClockContext) Deadline() (time.Time, bool) {
|
||||
return c.deadline, true
|
||||
}
|
||||
|
||||
func (c *fakeClockContext) Done() <-chan struct{} {
|
||||
return c.ctxDone
|
||||
}
|
||||
|
||||
func (c *fakeClockContext) Err() error {
|
||||
<-c.Done() // Don't return the error before it is ready.
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *fakeClockContext) Value(key any) any {
|
||||
return c.parent.Value(key)
|
||||
}
|
||||
|
||||
// runCancel runs the fakeClockContext's cancel goroutine and returns the
|
||||
// fakeClockContext's cancel function.
|
||||
//
|
||||
// fakeClockContext is then cancelled when any of the following occur:
|
||||
//
|
||||
// - The fakeClockContext.done channel is closed by its timer.
|
||||
// - The returned CancelFunc is executed.
|
||||
// - The fakeClockContext's parent context is cancelled with an error other
|
||||
// than context.DeadlineExceeded.
|
||||
func (c *fakeClockContext) runCancel(ready chan struct{}) {
|
||||
parentDone := c.parent.Done()
|
||||
|
||||
// Close ready when done, just in case the ready signal races with other
|
||||
// branches of our select statement below.
|
||||
defer close(ready)
|
||||
|
||||
for c.err == nil {
|
||||
select {
|
||||
case <-c.timerDone:
|
||||
c.err = ErrFakeClockDeadlineExceeded
|
||||
case <-c.cancelCalled:
|
||||
c.err = context.Canceled
|
||||
case <-parentDone:
|
||||
c.err = c.parent.Err()
|
||||
|
||||
case ready <- struct{}{}:
|
||||
// Signals the cancellation goroutine has begun, in an attempt to minimize
|
||||
// race conditions related to goroutine startup time.
|
||||
ready = nil // This case statement can only fire once.
|
||||
}
|
||||
}
|
||||
close(c.ctxDone)
|
||||
return
|
||||
}
|
||||
|
||||
35
vendor/github.com/jonboulle/clockwork/ticker.go
generated
vendored
35
vendor/github.com/jonboulle/clockwork/ticker.go
generated
vendored
@@ -19,7 +19,12 @@ func (r realTicker) Chan() <-chan time.Time {
|
||||
}
|
||||
|
||||
type fakeTicker struct {
|
||||
firer
|
||||
// The channel associated with the firer, used to send expiration times.
|
||||
c chan time.Time
|
||||
|
||||
// The time when the ticker expires. Only meaningful if the ticker is currently
|
||||
// one of a FakeClock's waiters.
|
||||
exp time.Time
|
||||
|
||||
// reset and stop provide the implementation of the respective exported
|
||||
// functions.
|
||||
@@ -30,13 +35,27 @@ type fakeTicker struct {
|
||||
d time.Duration
|
||||
}
|
||||
|
||||
func (f *fakeTicker) Reset(d time.Duration) {
|
||||
f.reset(d)
|
||||
func newFakeTicker(fc *FakeClock, d time.Duration) *fakeTicker {
|
||||
var ft *fakeTicker
|
||||
ft = &fakeTicker{
|
||||
c: make(chan time.Time, 1),
|
||||
d: d,
|
||||
reset: func(d time.Duration) {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
ft.d = d
|
||||
fc.setExpirer(ft, d)
|
||||
},
|
||||
stop: func() { fc.stop(ft) },
|
||||
}
|
||||
return ft
|
||||
}
|
||||
|
||||
func (f *fakeTicker) Stop() {
|
||||
f.stop()
|
||||
}
|
||||
func (f *fakeTicker) Chan() <-chan time.Time { return f.c }
|
||||
|
||||
func (f *fakeTicker) Reset(d time.Duration) { f.reset(d) }
|
||||
|
||||
func (f *fakeTicker) Stop() { f.stop() }
|
||||
|
||||
func (f *fakeTicker) expire(now time.Time) *time.Duration {
|
||||
// Never block on expiration.
|
||||
@@ -46,3 +65,7 @@ func (f *fakeTicker) expire(now time.Time) *time.Duration {
|
||||
}
|
||||
return &f.d
|
||||
}
|
||||
|
||||
func (f *fakeTicker) expiration() time.Time { return f.exp }
|
||||
|
||||
func (f *fakeTicker) setExpiration(t time.Time) { f.exp = t }
|
||||
|
||||
40
vendor/github.com/jonboulle/clockwork/timer.go
generated
vendored
40
vendor/github.com/jonboulle/clockwork/timer.go
generated
vendored
@@ -18,9 +18,14 @@ func (r realTimer) Chan() <-chan time.Time {
|
||||
}
|
||||
|
||||
type fakeTimer struct {
|
||||
firer
|
||||
// The channel associated with the firer, used to send expiration times.
|
||||
c chan time.Time
|
||||
|
||||
// reset and stop provide the implmenetation of the respective exported
|
||||
// The time when the firer expires. Only meaningful if the firer is currently
|
||||
// one of a FakeClock's waiters.
|
||||
exp time.Time
|
||||
|
||||
// reset and stop provide the implementation of the respective exported
|
||||
// functions.
|
||||
reset func(d time.Duration) bool
|
||||
stop func() bool
|
||||
@@ -30,13 +35,30 @@ type fakeTimer struct {
|
||||
afterFunc func()
|
||||
}
|
||||
|
||||
func (f *fakeTimer) Reset(d time.Duration) bool {
|
||||
return f.reset(d)
|
||||
func newFakeTimer(fc *FakeClock, afterfunc func()) *fakeTimer {
|
||||
var ft *fakeTimer
|
||||
ft = &fakeTimer{
|
||||
c: make(chan time.Time, 1),
|
||||
reset: func(d time.Duration) bool {
|
||||
fc.l.Lock()
|
||||
defer fc.l.Unlock()
|
||||
// fc.l must be held across the calls to stopExpirer & setExpirer.
|
||||
stopped := fc.stopExpirer(ft)
|
||||
fc.setExpirer(ft, d)
|
||||
return stopped
|
||||
},
|
||||
stop: func() bool { return fc.stop(ft) },
|
||||
|
||||
afterFunc: afterfunc,
|
||||
}
|
||||
return ft
|
||||
}
|
||||
|
||||
func (f *fakeTimer) Stop() bool {
|
||||
return f.stop()
|
||||
}
|
||||
func (f *fakeTimer) Chan() <-chan time.Time { return f.c }
|
||||
|
||||
func (f *fakeTimer) Reset(d time.Duration) bool { return f.reset(d) }
|
||||
|
||||
func (f *fakeTimer) Stop() bool { return f.stop() }
|
||||
|
||||
func (f *fakeTimer) expire(now time.Time) *time.Duration {
|
||||
if f.afterFunc != nil {
|
||||
@@ -51,3 +73,7 @@ func (f *fakeTimer) expire(now time.Time) *time.Duration {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeTimer) expiration() time.Time { return f.exp }
|
||||
|
||||
func (f *fakeTimer) setExpiration(t time.Time) { f.exp = t }
|
||||
|
||||
Reference in New Issue
Block a user