mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-16 12:06:23 -07:00
Use more transactions, and replace getNextFreeID with getLatestID
This commit is contained in:
parent
34d50970db
commit
e87915ecb9
11 changed files with 194 additions and 96 deletions
|
@ -32,15 +32,27 @@ func NewIPBan(ban *IPBan) error {
|
|||
if ban.ID > 0 {
|
||||
return ErrBanAlreadyInserted
|
||||
}
|
||||
var err error
|
||||
ban.ID, err = getNextFreeID("DBPREFIXip_ban")
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = ExecSQL(query,
|
||||
defer tx.Rollback()
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err = stmt.Exec(
|
||||
ban.StaffID, ban.BoardID, ban.BannedForPostID, ban.CopyPostText, ban.IsThreadBan, ban.IsActive, ban.IP,
|
||||
ban.AppealAt, ban.ExpiresAt, ban.Permanent, ban.StaffNote, ban.Message, ban.CanAppeal)
|
||||
return err
|
||||
ban.AppealAt, ban.ExpiresAt, ban.Permanent, ban.StaffNote, ban.Message, ban.CanAppeal,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
ban.ID, err = getLatestID("DBPREFIXip_ban", tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// CheckIPBan returns the latest active IP ban for the given IP, as well as any errors. If the
|
||||
|
@ -135,7 +147,6 @@ func (ipb *IPBan) Deactivate(staffID int) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt1.Close()
|
||||
|
||||
if _, err = stmt1.Exec(ipb.ID); err != nil {
|
||||
return err
|
||||
|
@ -145,7 +156,6 @@ func (ipb *IPBan) Deactivate(staffID int) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt2.Close()
|
||||
if _, err = stmt2.Exec(ipb.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -201,17 +211,31 @@ func CheckNameBan(name string, boardID int) (*UsernameBan, error) {
|
|||
func NewNameBan(name string, isRegex bool, boardID int, staffID int, staffNote string) (*UsernameBan, error) {
|
||||
const query = `INSERT INTO DBPREFIXusername_ban (board_id, staff_id, staff_note, username, is_regex) VALUES(?,?,?,?,?)`
|
||||
var ban UsernameBan
|
||||
var err error
|
||||
if ban.ID, err = getNextFreeID("DBPREFIXusername_ban"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if boardID > 0 {
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = boardID
|
||||
}
|
||||
if _, err = ExecSQL(query, ban.BoardID, staffID, staffNote, name, isRegex); err != nil {
|
||||
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, name, isRegex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.ID, err = getLatestID("DBPREFIXusername_ban", tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ban.StaffID = staffID
|
||||
ban.StaffNote = staffNote
|
||||
ban.Username = name
|
||||
|
@ -318,17 +342,30 @@ func GetFilenameBans(boardID int, limit int) ([]FilenameBan, error) {
|
|||
func NewFilenameBan(filename string, isRegex bool, boardID int, staffID int, staffNote string) (*FilenameBan, error) {
|
||||
const query = `INSERT INTO DBPREFIXfilename_ban (board_id, staff_id, staff_note, filename, is_regex) VALUES(?,?,?,?,?)`
|
||||
var ban FilenameBan
|
||||
var err error
|
||||
if ban.ID, err = getNextFreeID("DBPREFIXfilename_ban"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if boardID > 0 {
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = boardID
|
||||
}
|
||||
if _, err = ExecSQL(query, ban.BoardID, staffID, staffNote, filename, isRegex); err != nil {
|
||||
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, filename, isRegex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.ID, err = getLatestID("DBPREFIXfilename_ban", tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ban.StaffID = staffID
|
||||
ban.StaffNote = staffNote
|
||||
ban.Filename = filename
|
||||
|
@ -422,14 +459,27 @@ func NewFileChecksumBan(checksum string, boardID int, staffID int, staffNote str
|
|||
const query = `INSERT INTO DBPREFIXfile_ban (board_id, staff_id, staff_note, checksum) VALUES(?,?,?,?)`
|
||||
var ban FileBan
|
||||
var err error
|
||||
if ban.ID, err = getNextFreeID("DBPREFIXfile_ban"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if boardID > 0 {
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = boardID
|
||||
}
|
||||
if _, err = ExecSQL(query, ban.BoardID, staffID, staffNote, checksum); err != nil {
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, checksum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.ID, err = getLatestID("DBPREFIXfile_ban", tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ban.StaffID = staffID
|
||||
|
|
|
@ -56,11 +56,14 @@ func tmpSqlAdjust() error {
|
|||
}
|
||||
if numConstraints > 0 {
|
||||
query = `ALTER TABLE DBPREFIXwordfilters DROP FOREIGN KEY IF EXISTS wordfilters_board_id_fk`
|
||||
} else {
|
||||
query = ""
|
||||
}
|
||||
case "postgres":
|
||||
query = `ALTER TABLE DBPREFIXwordfilters DROP CONSTRAINT IF EXISTS board_id_fk`
|
||||
case "sqlite3":
|
||||
return nil
|
||||
_, err = ExecSQL(`PRAGMA foreign_keys = ON`)
|
||||
return err
|
||||
}
|
||||
if _, err = gcdb.ExecSQL(query); err != nil {
|
||||
return err
|
||||
|
|
|
@ -328,33 +328,47 @@ func (p *Post) Insert(bumpThread bool, boardID int, locked bool, stickied bool,
|
|||
message, message_raw, password)
|
||||
VALUES(?,?,?,CURRENT_TIMESTAMP,?,?,?,?,?,?,?,?)`
|
||||
bumpSQL := `UPDATE DBPREFIXthreads SET last_bump = CURRENT_TIMESTAMP WHERE id = ?`
|
||||
var err error
|
||||
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if p.ThreadID == 0 {
|
||||
// thread doesn't exist yet, this is a new post
|
||||
p.IsTopPost = true
|
||||
var threadID int
|
||||
threadID, err = createThread(boardID, locked, stickied, anchored, cyclical)
|
||||
threadID, err = createThread(tx, boardID, locked, stickied, anchored, cyclical)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.ThreadID = threadID
|
||||
}
|
||||
|
||||
id, err := getNextFreeID("DBPREFIXposts")
|
||||
stmt, err := PrepareSQL(insertSQL, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = ExecSQL(insertSQL,
|
||||
if _, err = stmt.Exec(
|
||||
p.ThreadID, p.IsTopPost, p.IP, p.Name, p.Tripcode, p.IsRoleSignature, p.Email, p.Subject,
|
||||
p.Message, p.MessageRaw, p.Password,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
p.ID = id
|
||||
if bumpThread {
|
||||
_, err = ExecSQL(bumpSQL, p.ThreadID)
|
||||
if p.ID, err = getLatestID("DBPREFIXposts", tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
if bumpThread {
|
||||
stmt2, err := PrepareSQL(bumpSQL, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = stmt2.Exec(p.ThreadID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (p *Post) WebPath() string {
|
||||
|
|
|
@ -115,11 +115,11 @@ func buildNewDatabase(dbType string) error {
|
|||
if err = initDB("initdb_" + dbType + ".sql"); err != nil {
|
||||
return errors.New("database initialization failed: " + err.Error())
|
||||
}
|
||||
if err = createDefaultBoardIfNoneExist(); err != nil {
|
||||
return errors.New("failed creating default board if non already exists: " + err.Error())
|
||||
}
|
||||
if err = createDefaultAdminIfNoStaff(); err != nil {
|
||||
return errors.New("failed creating default admin account: " + err.Error())
|
||||
}
|
||||
if err = createDefaultBoardIfNoneExist(); err != nil {
|
||||
return errors.New("failed creating default board if non already exists: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -35,13 +35,6 @@ func GetAllSections(onlyNonHidden bool) ([]Section, error) {
|
|||
return sections, nil
|
||||
}
|
||||
|
||||
func getNextSectionListOrder() (int, error) {
|
||||
const query = `SELECT COALESCE(MAX(position) + 1, 0) FROM DBPREFIXsections`
|
||||
var id int
|
||||
err := QueryRowSQL(query, interfaceSlice(), interfaceSlice(&id))
|
||||
return id, err
|
||||
}
|
||||
|
||||
// getOrCreateDefaultSectionID creates the default section if no sections have been created yet,
|
||||
// returns default section ID if it exists
|
||||
func getOrCreateDefaultSectionID() (sectionID int, err error) {
|
||||
|
@ -86,16 +79,37 @@ func DeleteSection(id int) error {
|
|||
// If position < 0, it will use the ID
|
||||
func NewSection(name string, abbreviation string, hidden bool, position int) (*Section, error) {
|
||||
const sqlINSERT = `INSERT INTO DBPREFIXsections (name, abbreviation, hidden, position) VALUES (?,?,?,?)`
|
||||
const sqlPosition = `SELECT COALESCE(MAX(position) + 1, 1) FROM DBPREFIXsections`
|
||||
|
||||
id, err := getNextFreeID("DBPREFIXsections")
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if position < 0 {
|
||||
// position not specified, use the ID
|
||||
position = id
|
||||
defer tx.Rollback()
|
||||
|
||||
stmt, err := PrepareSQL(sqlINSERT, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = ExecSQL(sqlINSERT, name, abbreviation, hidden, position); err != nil {
|
||||
|
||||
if position < 0 {
|
||||
// position not specified
|
||||
stmt2, err := PrepareSQL(sqlPosition, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = stmt2.QueryRow().Scan(&position); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err = stmt.Exec(name, abbreviation, hidden, position); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := getLatestID("DBPREFIXsections", tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Section{
|
||||
|
|
|
@ -17,24 +17,28 @@ var (
|
|||
ErrThreadDoesNotExist = errors.New("thread does not exist")
|
||||
)
|
||||
|
||||
func createThread(boardID int, locked bool, stickied bool, anchored bool, cyclical bool) (threadID int, err error) {
|
||||
const sql = `INSERT INTO DBPREFIXthreads (board_id, locked, stickied, anchored, cyclical) VALUES (?,?,?,?,?)`
|
||||
//Retrieves next free ID, explicitly inserts it, keeps retrying until succesfull insert or until a non-pk error is encountered.
|
||||
//This is done because mysql doesnt support RETURNING and both LAST_INSERT_ID() and last_row_id() are not thread-safe
|
||||
isPrimaryKeyError := true
|
||||
for isPrimaryKeyError {
|
||||
threadID, err = getNextFreeID("DBPREFIXthreads")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = ExecSQL(sql, boardID, locked, stickied, anchored, cyclical)
|
||||
|
||||
isPrimaryKeyError, err = errFilterDuplicatePrimaryKey(err)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
func createThread(tx *sql.Tx, boardID int, locked bool, stickied bool, anchored bool, cyclical bool) (threadID int, err error) {
|
||||
const insertQuery = `INSERT INTO DBPREFIXthreads (board_id, locked, stickied, anchored, cyclical) VALUES (?,?,?,?,?)`
|
||||
stmt, err := PrepareSQL(insertQuery, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return threadID, nil
|
||||
if tx == nil {
|
||||
defer stmt.Close()
|
||||
}
|
||||
if _, err = stmt.Exec(boardID, locked, stickied, anchored, cyclical); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stmt2, err := PrepareSQL(`SELECT MAX(id) FROM DBPREFIXthreads`, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if tx == nil {
|
||||
defer stmt2.Close()
|
||||
}
|
||||
|
||||
err = stmt2.QueryRow().Scan(&threadID)
|
||||
return threadID, err
|
||||
}
|
||||
|
||||
// GetThread returns a a thread object from the database
|
||||
|
|
|
@ -50,7 +50,7 @@ func (p *Post) nextFileOrder() (int, error) {
|
|||
|
||||
func (p *Post) AttachFile(upload *Upload) error {
|
||||
if upload == nil {
|
||||
return nil //
|
||||
return nil // no upload to attach, so no error
|
||||
}
|
||||
const query = `INSERT INTO DBPREFIXfiles (
|
||||
post_id, file_order, original_filename, filename, checksum, file_size,
|
||||
|
@ -59,10 +59,17 @@ func (p *Post) AttachFile(upload *Upload) error {
|
|||
if upload.ID > 0 {
|
||||
return ErrAlreadyAttached
|
||||
}
|
||||
uploadID, err := getNextFreeID("DBPREFIXfiles")
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if upload.FileOrder < 1 {
|
||||
upload.FileOrder, err = p.nextFileOrder()
|
||||
if err != nil {
|
||||
|
@ -70,14 +77,16 @@ func (p *Post) AttachFile(upload *Upload) error {
|
|||
}
|
||||
}
|
||||
upload.PostID = p.ID
|
||||
if _, err = ExecSQL(query,
|
||||
if _, err = stmt.Exec(
|
||||
&upload.PostID, &upload.FileOrder, &upload.OriginalFilename, &upload.Filename, &upload.Checksum, &upload.FileSize,
|
||||
&upload.IsSpoilered, &upload.ThumbnailWidth, &upload.ThumbnailHeight, &upload.Width, &upload.Height,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
upload.ID = uploadID
|
||||
return nil
|
||||
if upload.ID, err = getLatestID("DBPREFIXfiles", tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// ThumbnailPath returns the thumbnail path of the upload, given an thumbnail type ("thumbnail" or "catalog")
|
||||
|
|
|
@ -20,6 +20,18 @@ var (
|
|||
ErrNotConnected = errors.New("error connecting to database")
|
||||
)
|
||||
|
||||
// BeginTx begins a new transaction for the gochan database
|
||||
func BeginTx() (*sql.Tx, error) {
|
||||
if gcdb == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
ctx := context.Background()
|
||||
return gcdb.BeginTx(ctx, &sql.TxOptions{
|
||||
Isolation: 0,
|
||||
ReadOnly: false,
|
||||
})
|
||||
}
|
||||
|
||||
// PrepareSQL is used for generating a prepared SQL statement formatted according to the configured database driver
|
||||
func PrepareSQL(query string, tx *sql.Tx) (*sql.Stmt, error) {
|
||||
if gcdb == nil {
|
||||
|
@ -128,17 +140,6 @@ func QuerySQL(query string, a ...interface{}) (*sql.Rows, error) {
|
|||
return gcdb.QuerySQL(query, a...)
|
||||
}
|
||||
|
||||
func BeginTx() (*sql.Tx, error) {
|
||||
if gcdb == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
ctx := context.Background()
|
||||
return gcdb.BeginTx(ctx, &sql.TxOptions{
|
||||
Isolation: 0,
|
||||
ReadOnly: false,
|
||||
})
|
||||
}
|
||||
|
||||
func ParseSQLTimeString(str string) (time.Time, error) {
|
||||
var t time.Time
|
||||
var err error
|
||||
|
@ -150,10 +151,20 @@ func ParseSQLTimeString(str string) (time.Time, error) {
|
|||
return t, fmt.Errorf("unrecognized timestamp string format %q", str)
|
||||
}
|
||||
|
||||
func getNextFreeID(tableName string) (ID int, err error) {
|
||||
var sql = `SELECT COALESCE(MAX(id), 0) + 1 FROM ` + tableName
|
||||
err = QueryRowSQL(sql, interfaceSlice(), interfaceSlice(&ID))
|
||||
return ID, err
|
||||
// getLatestID returns the latest inserted id column value from the given table
|
||||
func getLatestID(tableName string, tx *sql.Tx) (id int, err error) {
|
||||
query := `SELECT MAX(id) FROM ` + tableName
|
||||
if tx != nil {
|
||||
var stmt *sql.Stmt
|
||||
stmt, err = PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = stmt.QueryRow().Scan(&id)
|
||||
} else {
|
||||
err = QueryRowSQL(query, nil, interfaceSlice(&id))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func doesTableExist(tableName string) (bool, error) {
|
||||
|
@ -219,7 +230,7 @@ func interfaceSlice(args ...interface{}) []interface{} {
|
|||
return args
|
||||
}
|
||||
|
||||
func errFilterDuplicatePrimaryKey(err error) (isPKerror bool, nonPKerror error) {
|
||||
/* func errFilterDuplicatePrimaryKey(err error) (isPKerror bool, nonPKerror error) {
|
||||
if err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -235,4 +246,4 @@ func errFilterDuplicatePrimaryKey(err error) (isPKerror bool, nonPKerror error)
|
|||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
} */
|
||||
|
|
|
@ -238,6 +238,9 @@ var funcMap = template.FuncMap{
|
|||
}
|
||||
return dir
|
||||
},
|
||||
"boardPagePath": func(board *gcsql.Board, page int) string {
|
||||
return config.WebPath(board.Dir, strconv.Itoa(page)+".html")
|
||||
},
|
||||
"webPath": func(part ...string) string {
|
||||
return config.WebPath(part...)
|
||||
},
|
||||
|
|
|
@ -180,6 +180,7 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
|
|||
gcutil.LogWarning().
|
||||
Str("spam", "badReferer").
|
||||
Str("IP", post.IP).
|
||||
Int("threadID", post.ThreadID).
|
||||
Msg("Rejected post from possible spambot")
|
||||
serverutil.ServeError(writer, "Your post looks like spam", wantsJSON, nil)
|
||||
return
|
||||
|
|
|
@ -40,24 +40,13 @@
|
|||
<a href="#">Scroll to top</a><br/>
|
||||
<table id="pages">
|
||||
<tr>
|
||||
{{/*<td>{{if gt $.currentPage 1}}
|
||||
<form method="GET" action='{{.board.PagePath "prev"}}'>
|
||||
<input type="submit" value="Previous" />
|
||||
</form>
|
||||
{{- else}}Previous{{end}}</td> */}}
|
||||
<td>{{range $_,$i := makeLoop .numPages 1 -}}
|
||||
{{- if eq $.currentPage $i -}}
|
||||
[<b>{{$i}}</b>]
|
||||
{{- else -}}
|
||||
[<a href="{{$.board.PagePath $i }}">{{$i}}</a>]
|
||||
[<a href="{{boardPagePath $.board $i}}">{{$i}}</a>]
|
||||
{{- end -}}
|
||||
{{- end}}</td>
|
||||
{{/*
|
||||
<td>{{if lt $.currentPage .numPages}}
|
||||
<form method="GET" action="{{.board.PagePath `next` }}">
|
||||
<input type="submit" value="Next" />
|
||||
</form>
|
||||
{{else}}Next{{end}}</td> */}}
|
||||
</tr>
|
||||
</table>
|
||||
<span id="boardmenu-bottom">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue