<commit_before>package sqlsess

import (
	"database/sql"
	"net/http"
	"time"

	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"

	"github.com/BurntSushi/locker"
)

var (
	SessionLastUpdated = "__sess_last_updated"
	CookieIdName       = "sess_sessionid"
	SqlTableName       = "sess_session"
	SqlCreateSession   = `
	CREATE TABLE ` + SqlTableName + ` IF NOT EXISTS (
		id BYTEA NOT NULL,
		name VARCHAR (255) NOT NULL,
		key TEXT NOT NULL,
		value TEXT NOT NULL,
		PRIMARY KEY (id, name, key)
	)
	`
)

type Store struct {
	*sql.DB
	hashKey, blockKey []byte
}

func Open(db *sql.DB) (*Store, error) {
	if _, err := db.Exec(SqlCreateSession); err != nil {
		return nil, err
	}

	s := &Store{
		DB:       db,
		hashKey:  securecookie.GenerateRandomKey(64),
		blockKey: securecookie.GenerateRandomKey(32),
	}
	return s, nil
}

func (s *Store) Clean(inactive time.Duration) error {
	locker.Lock("clean")
	defer locker.Unlock("clean")

	rows, err := s.Query(`
		SELECT id, value
		FROM ` + SqlTableName + `
		WHERE key = $1
	`, SessionLastUpdated)
	if err != nil {
		return err
	}
	defer rows.Close()

	cutoff := time.Now().UTC().Add(-inactive)
	for rows.Next() {
		var id []byte
		var last string
		if err := rows.Scan(&id, &last); err != nil {
			return err
		}
		lastUp, err := time.Parse(time.RFC3339Nano, last)
		if err != nil {
			return err
		}
		if lastUp.Before(cutoff) {
			_, err = s.Exec(
				"DELETE FROM " + SqlTableName + " WHERE id = $1", id)
			if err != nil {
				return err
			}
		}
	}
	return rows.Err()
}

func (s *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

func (s *Store) New(r *http.Request, name string) (*sessions.Session, error) {
	sess := sessions.NewSession(s, name)
	sess.ID = s.id(r)

	RLock(sess)
	defer RUnlock(sess)

	rows, err := s.Query(`
		SELECT key, value
		FROM ` + SqlTableName + `
		WHERE id = $1 AND name = $2
	`, []byte(sess.ID), name)
	if err != nil {
		return nil, err
	}<commit_msg>Support session removal.<commit_after>package sqlsess

import (
	"database/sql"
	"net/http"
	"time"

	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"

	"github.com/BurntSushi/locker"
)

var (
	SessionLastUpdated = "__sess_last_updated"
	CookieIdName       = "sess_sessionid"
	SqlTableName       = "sess_session"
	SqlCreateSession   = `
	CREATE TABLE ` + SqlTableName + ` IF NOT EXISTS (
		id BYTEA NOT NULL,
		name VARCHAR (255) NOT NULL,
		key TEXT NOT NULL,
		value TEXT NOT NULL,
		PRIMARY KEY (id, name, key)
	)
	`
)

type Store struct {
	*sql.DB
	hashKey, blockKey []byte
}

func Open(db *sql.DB) (*Store, error) {
	if _, err := db.Exec(SqlCreateSession); err != nil {
		return nil, err
	}

	s := &Store{
		DB:       db,
		hashKey:  securecookie.GenerateRandomKey(64),
		blockKey: securecookie.GenerateRandomKey(32),
	}
	return s, nil
}

func (s *Store) Clean(inactive time.Duration) error {
	locker.Lock("clean")
	defer locker.Unlock("clean")

	rows, err := s.Query(`
		SELECT id, value
		FROM ` + SqlTableName + `
		WHERE key = $1
	`, SessionLastUpdated)
	if err != nil {
		return err
	}
	defer rows.Close()

	cutoff := time.Now().UTC().Add(-inactive)
	for rows.Next() {
		var id []byte
		var last string
		if err := rows.Scan(&id, &last); err != nil {
			return err
		}
		lastUp, err := time.Parse(time.RFC3339Nano, last)
		if err != nil {
			return err
		}
		if lastUp.Before(cutoff) {
			_, err = s.Exec(
				"DELETE FROM " + SqlTableName + " WHERE id = $1", id)
			if err != nil {
				return err
			}
		}
	}
	return rows.Err()
}

func (s *Store) Delete(sess *sessions.Session) error {
	id := []byte(sess.ID)
	_, err := s.Exec("DELETE FROM " + SqlTableName + " WHERE id = $1", id)
	return err
}

func (s *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

func (s *Store) New(r *http.Request, name string) (*sessions.Session, error) {
	sess := sessions.NewSession(s, name)
	sess.ID = s.id(r)

	RLock(sess)
	defer RUnlock(sess)

	rows, err := s.Query(`
		SELECT key, value
		FROM ` + SqlTableName + `
		WHERE id = $1 AND name = $2
	`, []byte(sess.ID), name)
	if err != nil {
		return nil, err
	}
{'fraction_non_alphanumeric': 0.14857142857142858, 'fraction_numerical': 0.007380952380952381, 'mean_word_length': 3.2911133810010216, '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': '5838147', 'n_tokens_mistral': 1731, 'n_tokens_neox': 1605, 'n_words': 420}