<commit_before>package datalayer

import (
	"context"
	"fmt"
	"time"

	"github.com/dollarshaveclub/furan/generated/lib"
	"github.com/dollarshaveclub/furan/lib/db"
	"github.com/gocql/gocql"
	"github.com/golang/protobuf/proto"
	gocqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gocql/gocql"
)

// DataLayer describes an object that interacts with the persistent data store
type DataLayer interface {
	CreateBuild(context.Context, *lib.BuildRequest) (gocql.UUID, error)
	GetBuildByID(context.Context, gocql.UUID) (*lib.BuildStatusResponse, error)
	SetBuildFlags(context.Context, gocql.UUID, map[string]bool) error
	SetBuildCompletedTimestamp(context.Context, gocql.UUID) error
	SetBuildState(context.Context, gocql.UUID, lib.BuildStatusResponse_BuildState) error
	DeleteBuild(context.Context, gocql.UUID) error
	SetBuildTimeMetric(context.Context, gocql.UUID, string) error
	SetDockerImageSizesMetric(context.Context, gocql.UUID, int64, int64) error
	SaveBuildOutput(context.Context, gocql.UUID, []lib.BuildEvent, string) error
	GetBuildOutput(context.Context, gocql.UUID, string) ([]lib.BuildEvent, error)
}

// DBLayer is an DataLayer instance that interacts with the Cassandra database
type DBLayer struct {
	s     *gocql.Session
	sname string
}

// NewDBLayer returns a data layer object
func NewDBLayer(s *gocql.Session, sname string) *DBLayer {
	return &DBLayer{s: s, sname: sname}
}

func (dl *DBLayer) wrapQuery(ctx context.Context, query *gocql.Query) *gocqltrace.Query {
	return gocqltrace.WrapQuery(query, gocqltrace.WithServiceName(dl.sname)).WithContext(ctx)
}

// CreateBuild inserts a new build into the DB returning the ID
func (dl *DBLayer) CreateBuild(ctx context.Context, req *lib.BuildRequest) (id gocql.UUID, err error) {
	q := `INSERT INTO builds_by_id (id, request, state, finished, failed, cancelled, started)
        VALUES (?,{github_repo: ?, dockerfile_path: ?, tags: ?, tag_with_commit_sha: ?, ref: ?,
					push_registry_repo: ?, push_s3_region: ?, push_s3_bucket: ?,
					push_s3_key_prefix: ?},?,?,?,?,?);`
	id, err = gocql.RandomUUID()
	if err != nil {
		return id, err
	}
	udt := db.UDTFromBuildRequest(req)
	query := dl.s.Query(q, id, udt.GithubRepo, udt.DockerfilePath, udt.Tags, udt.TagWithCommitSha, udt.Ref,
		udt.PushRegistryRepo, udt.PushS3Region, udt.PushS3Bucket, udt.PushS3KeyPrefix,
		lib.BuildStatusResponse_STARTED.String(), false, false, false, time.Now())
	err = dl.wrapQuery(ctx, query).Exec()
	if err != nil {
		return id, err
	}
	q = `INSERT INTO build_metrics_by_id (id) VALUES (?);`
	err = dl.wrapQuery(ctx, dl.s.Query(q, id)).Exec()
	if err != nil {
		return id, err
	}
	q = `INSERT INTO build_events_by_id (id) VALUES (?);`
	return id, dl.wrapQuery(ctx, dl.s.Query(q, id)).Exec()
}

// GetBuildByID fetches a build object from the DB
func (dl *DBLayer) GetBuildByID(ctx context.Context, id gocql.UUID) (bi *lib.BuildStatusResponse, err error) {
	q := `SELECT request, state, finished, failed, cancelled, started, completed,
	      duration FROM builds_by_id WHERE id = ?;`
	var udt db.BuildRequestUDT
	var state string
	var started, completed time.Time
	bi = &lib.BuildStatusResponse{
		BuildId: id.String(),
	}
	query := dl.s.Query(q, id)
	err = dl.wrapQuery(ctx, query).Scan(&udt.GithubRepo, &udt.DockerfilePath, udt.Tags, udt.TagWithCommitSha, udt.Ref,
		udt.PushRegistryRepo, udt.PushS3Region, udt.PushS3Bucket, udt.PushS3KeyPrefix, &state, &bi.Finished, &bi.Failed,
		&bi.Cancelled, &started, &completed, &bi.Duration)
	if err != nil {
		return bi, err
	}<commit_msg>fix: use pointers for unmarshaling<commit_after>package datalayer

import (
	"context"
	"fmt"
	"time"

	"github.com/dollarshaveclub/furan/generated/lib"
	"github.com/dollarshaveclub/furan/lib/db"
	"github.com/gocql/gocql"
	"github.com/golang/protobuf/proto"
	gocqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gocql/gocql"
)

// DataLayer describes an object that interacts with the persistent data store
type DataLayer interface {
	CreateBuild(context.Context, *lib.BuildRequest) (gocql.UUID, error)
	GetBuildByID(context.Context, gocql.UUID) (*lib.BuildStatusResponse, error)
	SetBuildFlags(context.Context, gocql.UUID, map[string]bool) error
	SetBuildCompletedTimestamp(context.Context, gocql.UUID) error
	SetBuildState(context.Context, gocql.UUID, lib.BuildStatusResponse_BuildState) error
	DeleteBuild(context.Context, gocql.UUID) error
	SetBuildTimeMetric(context.Context, gocql.UUID, string) error
	SetDockerImageSizesMetric(context.Context, gocql.UUID, int64, int64) error
	SaveBuildOutput(context.Context, gocql.UUID, []lib.BuildEvent, string) error
	GetBuildOutput(context.Context, gocql.UUID, string) ([]lib.BuildEvent, error)
}

// DBLayer is an DataLayer instance that interacts with the Cassandra database
type DBLayer struct {
	s     *gocql.Session
	sname string
}

// NewDBLayer returns a data layer object
func NewDBLayer(s *gocql.Session, sname string) *DBLayer {
	return &DBLayer{s: s, sname: sname}
}

func (dl *DBLayer) wrapQuery(ctx context.Context, query *gocql.Query) *gocqltrace.Query {
	return gocqltrace.WrapQuery(query, gocqltrace.WithServiceName(dl.sname)).WithContext(ctx)
}

// CreateBuild inserts a new build into the DB returning the ID
func (dl *DBLayer) CreateBuild(ctx context.Context, req *lib.BuildRequest) (id gocql.UUID, err error) {
	q := `INSERT INTO builds_by_id (id, request, state, finished, failed, cancelled, started)
        VALUES (?,{github_repo: ?, dockerfile_path: ?, tags: ?, tag_with_commit_sha: ?, ref: ?,
					push_registry_repo: ?, push_s3_region: ?, push_s3_bucket: ?,
					push_s3_key_prefix: ?},?,?,?,?,?);`
	id, err = gocql.RandomUUID()
	if err != nil {
		return id, err
	}
	udt := db.UDTFromBuildRequest(req)
	query := dl.s.Query(q, id, udt.GithubRepo, udt.DockerfilePath, udt.Tags, udt.TagWithCommitSha, udt.Ref,
		udt.PushRegistryRepo, udt.PushS3Region, udt.PushS3Bucket, udt.PushS3KeyPrefix,
		lib.BuildStatusResponse_STARTED.String(), false, false, false, time.Now())
	err = dl.wrapQuery(ctx, query).Exec()
	if err != nil {
		return id, err
	}
	q = `INSERT INTO build_metrics_by_id (id) VALUES (?);`
	err = dl.wrapQuery(ctx, dl.s.Query(q, id)).Exec()
	if err != nil {
		return id, err
	}
	q = `INSERT INTO build_events_by_id (id) VALUES (?);`
	return id, dl.wrapQuery(ctx, dl.s.Query(q, id)).Exec()
}

// GetBuildByID fetches a build object from the DB
func (dl *DBLayer) GetBuildByID(ctx context.Context, id gocql.UUID) (bi *lib.BuildStatusResponse, err error) {
	q := `SELECT request, state, finished, failed, cancelled, started, completed,
	      duration FROM builds_by_id WHERE id = ?;`
	var udt db.BuildRequestUDT
	var state string
	var started, completed time.Time
	bi = &lib.BuildStatusResponse{
		BuildId: id.String(),
	}
	query := dl.s.Query(q, id)
	err = dl.wrapQuery(ctx, query).Scan(&udt.GithubRepo, &udt.DockerfilePath, &udt.Tags, &udt.TagWithCommitSha, &udt.Ref,
		&udt.PushRegistryRepo, &udt.PushS3Region, &udt.PushS3Bucket, &udt.PushS3KeyPrefix, &state, &bi.Finished, &bi.Failed,
		&bi.Cancelled, &started, &completed, &bi.Duration)
	if err != nil {
		return bi, err
	}
{'fraction_non_alphanumeric': 0.1319581802769144, 'fraction_numerical': 0.003955919751342187, 'mean_word_length': 5.139635732870772, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3485243', 'n_tokens_mistral': 2661, 'n_tokens_neox': 2637, 'n_words': 592}