func GetAllBoards() ([]Board, error) {
	const sql = `SELECT id, section_id, dir, navbar_position, title, subtitle, description, max_file_size, default_style, locked, created_at, anonymous_name, force_anonymous, autosage_after, no_images_after, max_message_length, allow_embeds, redirect_to_thread, require_file, enable_catalog FROM DBPREFIXboards
	ORDER BY navbar_position ASC, dir ASC`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var boards []Board
	for rows.Next() {
		var board Board
		err = rows.Scan(&board.ID, &board.Section, &board.Dir, &board.ListOrder, &board.Title, &board.Subtitle, &board.Description, &board.MaxFilesize, &board.DefaultStyle, &board.Locked, &board.CreatedOn, &board.Anonymous, &board.ForcedAnon, &board.AutosageAfter, &board.NoImagesAfter, &board.MaxMessageLength, &board.EmbedsAllowed, &board.RedirectToThread, &board.RequireFile, &board.EnableCatalog)
		if err != nil {
			return nil, err
		}
		boards = append(boards, board)
	}
	return boards, nil
}
GetAllBoards gets a list of all existing boards
Deprecated: This method was created to support old functionality during the database refactor of april 2020
The code should be changed to reflect the new database design
package gcsql
import (
	"database/sql"
	"errors"
	"fmt"
	"html/template"
	"os"
	"path"
	"strings"
	"time"
	"github.com/gochan-org/gochan/pkg/config"
	"github.com/gochan-org/gochan/pkg/gcutil"
)
const GochanVersionKeyConstant = "gochan"
var (
	ErrNilBoard = errors.New("Board is nil")
)
func GetAllNondeletedMessageRaw() ([]MessagePostContainer, error) {
	const sql = `select posts.id, posts.message, posts.message_raw from DBPREFIXposts as posts
	WHERE posts.is_deleted = FALSE`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var messages []MessagePostContainer
	for rows.Next() {
		var message MessagePostContainer
		var formattedHTML template.HTML
		if err = rows.Scan(&message.ID, &formattedHTML, &message.MessageRaw); err != nil {
			return nil, err
		}
		message.Message = template.HTML(formattedHTML)
		messages = append(messages, message)
	}
	return messages, nil
}
func SetFormattedInDatabase(messages []MessagePostContainer) error {
	const sql = `UPDATE DBPREFIXposts
	SET message = ?
	WHERE id = ?`
	stmt, err := PrepareSQL(sql)
	if err != nil {
		return err
	}
	defer stmt.Close()
	for _, message := range messages {
		if _, err = stmt.Exec(string(message.Message), message.ID); err != nil {
			return err
		}
	}
	return err
}
func GetReplyCount(postID int) (int, error) {
	const sql = `SELECT COUNT(posts.id) FROM DBPREFIXposts as posts
	JOIN (
		SELECT threads.id FROM DBPREFIXthreads as threads
		JOIN DBPREFIXposts as posts
		ON posts.thread_id = threads.id
		WHERE posts.id = ?
	) as thread
	ON posts.thread_id = thread.id
	WHERE posts.is_deleted = FALSE`
	var count int
	err := QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&count))
	return count, err
}
func GetReplyFileCount(postID int) (int, error) {
	const sql = `SELECT COUNT(files.id) from DBPREFIXfiles as files
	JOIN (SELECT posts.id FROM DBPREFIXposts as posts
		JOIN (
			SELECT threads.id FROM DBPREFIXthreads as threads
			JOIN DBPREFIXposts as posts
			ON posts.thread_id = threads.id
			WHERE posts.id = ?
		) as thread
		ON posts.thread_id = thread.id
		WHERE posts.is_deleted = FALSE) as posts
	ON posts.id = files.post_id`
	var count int
	err := QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&count))
	return count, err
}
func GetStaffName(session string) (string, error) {
	const sql = `SELECT staff.username from DBPREFIXstaff as staff
	JOIN DBPREFIXsessions as sessions
	ON sessions.staff_id = staff.id
	WHERE sessions.data = ?`
	var username string
	err := QueryRowSQL(sql, interfaceSlice(session), interfaceSlice(&username))
	return username, err
}
func GetStaffBySession(session string) (*Staff, error) {
	const sql = `SELECT 
		staff.id, 
		staff.username, 
		staff.password_checksum, 
		staff.global_rank,
		staff.added_on,
		staff.last_login 
	FROM DBPREFIXstaff as staff
	JOIN DBPREFIXsessions as sessions
	ON sessions.staff_id = staff.id
	WHERE sessions.data = ?`
	staff := new(Staff)
	err := QueryRowSQL(sql, interfaceSlice(session), interfaceSlice(&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn, &staff.LastActive))
	return staff, err
}
func GetStaffByName(name string) (*Staff, error) {
	const sql = `SELECT 
		staff.id, 
		staff.username, 
		staff.password_checksum, 
		staff.global_rank,
		staff.added_on,
		staff.last_login 
	FROM DBPREFIXstaff as staff
	WHERE staff.username = ?`
	staff := new(Staff)
	err := QueryRowSQL(sql, interfaceSlice(name), interfaceSlice(&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn, &staff.LastActive))
	return staff, err
}
func getStaffByID(id int) (*Staff, error) {
	const sql = `SELECT 
		staff.id, 
		staff.username, 
		staff.password_checksum, 
		staff.global_rank,
		staff.added_on,
		staff.last_login 
	FROM DBPREFIXstaff as staff
	WHERE staff.id = ?`
	staff := new(Staff)
	err := QueryRowSQL(sql, interfaceSlice(id), interfaceSlice(&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn, &staff.LastActive))
	return staff, err
}
func NewStaff(username, password string, rank int) error {
	const sql = `INSERT INTO DBPREFIXstaff (username, password_checksum, global_rank)
	VALUES (?, ?, ?)`
	_, err := ExecSQL(sql, username, gcutil.BcryptSum(password), rank)
	return err
}
func DeleteStaff(username string) error {
	const sql = `UPDATE DBPREFIXstaff SET username = ?, is_active = FALSE WHERE username = ?`
	_, err := ExecSQL(sql, gcutil.RandomString(45), username)
	return err
}
func getStaffID(username string) (int, error) {
	staff, err := GetStaffByName(username)
	if err != nil {
		return -1, err
	}
	return staff.ID, nil
}
func CreateSession(key, username string) error {
	const sql1 = `INSERT INTO DBPREFIXsessions (staff_id,data,expires) VALUES(?,?,?)`
	const sql2 = `UPDATE DBPREFIXstaff SET last_login = CURRENT_TIMESTAMP WHERE id = ?`
	staffID, err := getStaffID(username)
	if err != nil {
		return err
	}
	_, err = ExecSQL(sql1, staffID, key, time.Now().Add(time.Duration(time.Hour*730)))  
	if err != nil {
		return err
	}
	_, err = ExecSQL(sql2, staffID)
	return err
}
func PermanentlyRemoveDeletedPosts() error {
	const sql1 = `DELETE FROM DBPREFIXposts WHERE is_deleted`
	const sql2 = `DELETE FROM DBPREFIXthreads WHERE is_deleted`
	_, err := ExecSQL(sql1)
	if err != nil {
		return err
	}
	_, err = ExecSQL(sql2)
	return err
}
func OptimizeDatabase() error {
	tableRows, tablesErr := QuerySQL("SHOW TABLES")
	if tablesErr != nil {
		return tablesErr
	}
	for tableRows.Next() {
		var table string
		tableRows.Scan(&table)
		if _, err := ExecSQL("OPTIMIZE TABLE " + table); err != nil {
			return err
		}
	}
	return nil
}
func getBoardIDFromURIOrNil(URI string) *int {
	ID, err := getBoardIDFromURI(URI)
	if err != nil {
		return nil
	}
	return &ID
}
func CreateFileBan(fileChecksum, staffName string, permaban bool, staffNote, boardURI string) error {
	const sql = `INSERT INTO DBPREFIXfile_ban (board_id, staff_id, staff_note, checksum) VALUES board_id = ?, staff_id = ?, staff_note = ?, checksum = ?`
	staffID, err := getStaffID(staffName)
	if err != nil {
		return err
	}
	boardID := getBoardIDFromURIOrNil(boardURI)
	_, err = ExecSQL(sql, boardID, staffID, staffNote, fileChecksum)
	return err
}
func CreateFileNameBan(fileName string, isRegex bool, staffName string, permaban bool, staffNote, boardURI string) error {
	const sql = `INSERT INTO DBPREFIXfilename_ban (board_id, staff_id, staff_note, filename, is_regex) VALUES board_id = ?, staff_id = ?, staff_note = ?, filename = ?, is_regex = ?`
	staffID, err := getStaffID(staffName)
	if err != nil {
		return err
	}
	boardID := getBoardIDFromURIOrNil(boardURI)
	_, err = ExecSQL(sql, boardID, staffID, staffNote, fileName, isRegex)
	return err
}
func CreateUserNameBan(userName string, isRegex bool, staffName string, permaban bool, staffNote, boardURI string) error {
	const sql = `INSERT INTO DBPREFIXusername_ban (board_id, staff_id, staff_note, username, is_regex) VALUES board_id = ?, staff_id = ?, staff_note = ?, username = ?, is_regex = ?`
	staffID, err := getStaffID(staffName)
	if err != nil {
		return err
	}
	boardID := getBoardIDFromURIOrNil(boardURI)
	_, err = ExecSQL(sql, boardID, staffID, staffNote, userName, isRegex)
	return err
}
func CreateUserBan(IP string, threadBan bool, staffName, boardURI string, expires time.Time, permaban bool,
	staffNote, message string, canAppeal bool, appealAt time.Time) error {
	const sql = `INSERT INTO DBPREFIXip_ban (board_id, staff_id, staff_note, is_thread_ban, ip, appeal_at, expires_at, permanent, message, can_appeal, issued_at, copy_posted_text, is_active)
	VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP,'OLD SYSTEM BAN, NO TEXT AVAILABLE',TRUE)`
	staffID, err := getStaffID(staffName)
	if err != nil {
		return err
	}
	boardID := getBoardIDFromURIOrNil(boardURI)
	_, err = ExecSQL(sql, boardID, staffID, staffNote, threadBan, IP, appealAt, expires, permaban, message, canAppeal)
	return err
}
func GetAllAccouncements() ([]Announcement, error) {
	const sql = `SELECT s.username, a.timestamp, a.subject, a.message FROM DBPREFIXannouncements AS a
	JOIN DBPREFIXstaff AS s
	ON a.staff_id = s.id
	ORDER BY a.id DESC`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var announcements []Announcement
	for rows.Next() {
		var announcement Announcement
		err = rows.Scan(&announcement.Poster, &announcement.Timestamp, &announcement.Subject, &announcement.Message)
		if err != nil {
			return nil, err
		}
		announcements = append(announcements, announcement)
	}
	return announcements, nil
}
func CreateBoard(values *Board) error {
	const maxThreads = 300
	const sqlINSERT = `INSERT INTO DBPREFIXboards (navbar_position, dir, uri, title, subtitle, description, max_file_size, max_threads, default_style, locked, anonymous_name, force_anonymous, autosage_after, no_images_after, max_message_length, min_message_length, allow_embeds, redirect_to_thread, require_file, enable_catalog, section_id)
	VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
	const sqlSELECT = "SELECT id FROM DBPREFIXboards WHERE dir = ?"
	if values == nil {
		return ErrNilBoard
	}
	_, err := ExecSQL(sqlINSERT, values.ListOrder, values.Dir, values.Dir, values.Title, values.Subtitle, values.Description, values.MaxFilesize, maxThreads, values.DefaultStyle, values.Locked, values.Anonymous, values.ForcedAnon, values.AutosageAfter, values.NoImagesAfter, values.MaxMessageLength, 1, values.EmbedsAllowed, values.RedirectToThread, values.RequireFile, values.EnableCatalog, values.Section)
	if err != nil {
		return err
	}
	return QueryRowSQL(sqlSELECT, interfaceSlice(values.Dir), interfaceSlice(&values.ID))
}
func GetBoardUris() (URIS []string, err error) {
	const sql = `SELECT uri FROM DBPREFIXboards`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var uris []string
	for rows.Next() {
		var uri string
		if err = rows.Scan(&uri); err != nil {
			return nil, err
		}
		uris = append(uris, uri)
	}
	return uris, nil
}
func GetAllSections() ([]BoardSection, error) {
	const sql = `SELECT id, name, abbreviation, position, hidden FROM DBPREFIXsections ORDER BY position ASC, name ASC`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var sections []BoardSection
	for rows.Next() {
		var section BoardSection
		err = rows.Scan(&section.ID, &section.Name, &section.Abbreviation, &section.ListOrder, &section.Hidden)
		if err != nil {
			return nil, err
		}
		sections = append(sections, section)
	}
	return sections, nil
}
func GetAllSectionsOrCreateDefault() ([]BoardSection, error) {
	_, err := GetOrCreateDefaultSectionID()
	if err != nil {
		return nil, err
	}
	return GetAllSections()
}
func getNextSectionListOrder() (int, error) {
	const sql = `SELECT COALESCE(MAX(position) + 1, 0) FROM DBPREFIXsections`
	var ID int
	err := QueryRowSQL(sql, interfaceSlice(), interfaceSlice(&ID))
	return ID, err
}
func GetOrCreateDefaultSectionID() (sectionID int, err error) {
	const SQL = `SELECT id FROM DBPREFIXsections WHERE name = 'Main'`
	var ID int
	err = QueryRowSQL(SQL, interfaceSlice(), interfaceSlice(&ID))
	if err == sql.ErrNoRows {
		ID, err := getNextSectionListOrder()
		if err != nil {
			return 0, err
		}
		board := BoardSection{Name: "Main", Abbreviation: "Main", Hidden: false, ListOrder: ID}
		err = CreateSection(&board)
		return board.ID, err
	}
	if err != nil {
		return 0, err  
	}
	return ID, nil
}
func CreateSection(section *BoardSection) error {
	const sqlINSERT = `INSERT INTO DBPREFIXsections (name, abbreviation, hidden, position) VALUES (?,?,?,?)`
	const sqlSELECT = `SELECT id FROM DBPREFIXsections WHERE position = ?`
	_, err := ExecSQL(sqlINSERT, section.Name, section.Abbreviation, section.Hidden, section.ListOrder)
	if err != nil {
		return err
	}
	return QueryRowSQL(
		sqlSELECT,
		interfaceSlice(section.ListOrder),
		interfaceSlice(&section.ID))
}
func GetAllStaffNopass() ([]Staff, error) {
	const sql = `SELECT id, username, global_rank, added_on, last_login FROM DBPREFIXstaff`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var staffs []Staff
	for rows.Next() {
		var staff Staff
		err = rows.Scan(&staff.ID, &staff.Username, &staff.Rank, &staff.AddedOn, &staff.LastActive)
		if err != nil {
			return nil, err
		}
		staffs = append(staffs, staff)
	}
	return staffs, nil
}
func GetAllBans() ([]BanInfo, error) {
	const sql = `SELECT 
	ban.id, 
	ban.ip, 
	COALESCE(board.title, '') as boardname,
	staff.username as staff,
	ban.issued_at,
	ban.expires_at,
	ban.permanent,
	ban.message,
	ban.staff_note,
	ban.appeal_at,
	ban.can_appeal
FROM DBPREFIXip_ban as ban
JOIN DBPREFIXstaff as staff
ON ban.staff_id = staff.id
JOIN DBPREFIXboards as board
ON ban.board_id = board.id`
	rows, err := QuerySQL(sql)
	if err != nil {
		return nil, err
	}
	var bans []BanInfo
	for rows.Next() {
		var ban BanInfo
		err = rows.Scan(&ban.ID, &ban.IP, &ban.Boards, &ban.Staff, &ban.Timestamp, &ban.Expires, &ban.Permaban, &ban.Reason, &ban.StaffNote, &ban.AppealAt, &ban.CanAppeal)
		if err != nil {
			return nil, err
		}
		bans = append(bans, ban)
	}
	return bans, nil
}
func CheckBan(ip, name, filename, checksum string) (*BanInfo, error) {
	ban := new(BanInfo)
	ipban, err1 := checkIPBan(ip)
	err1NoRows := (err1 == sql.ErrNoRows)
	_, err2 := checkFileBan(checksum)
	err2NoRows := (err2 == sql.ErrNoRows)
	_, err3 := checkFilenameBan(filename)
	err3NoRows := (err3 == sql.ErrNoRows)
	_, err4 := checkUsernameBan(name)
	err4NoRows := (err4 == sql.ErrNoRows)
	if err1NoRows && err2NoRows && err3NoRows && err4NoRows {
		return nil, sql.ErrNoRows
	}
	if err1NoRows {
		return nil, err1
	}
	if err2NoRows {
		return nil, err2
	}
	if err3NoRows {
		return nil, err3
	}
	if err4NoRows {
		return nil, err4
	}
	if ipban != nil {
		ban.ID = 0
		ban.IP = string(ipban.IP)
		staff, _ := getStaffByID(ipban.StaffID)
		ban.Staff = staff.Username
		ban.Timestamp = ipban.IssuedAt
		ban.Expires = ipban.ExpiresAt
		ban.Permaban = ipban.Permanent
		ban.Reason = ipban.Message
		ban.StaffNote = ipban.StaffNote
		ban.AppealAt = ipban.AppealAt
		ban.CanAppeal = ipban.CanAppeal
		return ban, nil
	}
	return nil, gcutil.ErrNotImplemented
}
func checkIPBan(ip string) (*IPBan, error) {
	const sql = `SELECT id, staff_id, board_id, banned_for_post_id, copy_post_text, is_thread_ban, is_active, ip, issued_at, appeal_at, expires_at, permanent, staff_note, message, can_appeal
	FROM DBPREFIXip_ban WHERE ip = ?`
	var ban = new(IPBan)
	var formattedHTMLcopyposttest template.HTML
	err := QueryRowSQL(sql, interfaceSlice(ip), interfaceSlice(&ban.ID, &ban.StaffID, &ban.BoardID, &ban.BannedForPostID, &formattedHTMLcopyposttest, &ban.IsThreadBan, &ban.IsActive, &ban.IP, &ban.IssuedAt, &ban.AppealAt, &ban.ExpiresAt, &ban.Permanent, &ban.StaffNote, &ban.Message, &ban.CanAppeal))
	ban.CopyPostText = formattedHTMLcopyposttest
	return ban, err
}
func checkUsernameBan(name string) (*UsernameBan, error) {
	const sql = `SELECT id, board_id, staff_id, staff_note, issued_at, username, is_regex 
	FROM DBPREFIXusername_ban WHERE username = ?`
	var ban = new(UsernameBan)
	err := QueryRowSQL(sql, interfaceSlice(name), interfaceSlice(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Username, &ban.IsRegex))
	return ban, err
}
func checkFilenameBan(filename string) (*FilenameBan, error) {
	const sql = `SELECT id, board_id, staff_id, staff_note, issued_at, filename, is_regex 
	FROM DBPREFIXfilename_ban WHERE filename = ?`
	var ban = new(FilenameBan)
	err := QueryRowSQL(sql, interfaceSlice(filename), interfaceSlice(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Filename, &ban.IsRegex))
	return ban, err
}
func checkFileBan(checksum string) (*FileBan, error) {
	const sql = `SELECT id, board_id, staff_id, staff_note, issued_at, checksum 
	FROM DBPREFIXfile_ban WHERE checksum = ?`
	var ban = new(FileBan)
	err := QueryRowSQL(sql, interfaceSlice(checksum), interfaceSlice(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Checksum))
	return ban, err
}
func SinceLastPost(postID int) (int, error) {
	const sql = `SELECT MAX(created_on) FROM DBPREFIXposts as posts
	JOIN (SELECT ip FROM DBPREFIXposts as sp
		 WHERE sp.id = ?) as ip
	ON posts.ip = ip.ip`
	var when time.Time
	err := QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&when))
	if err != nil {
		return -1, err
	}
	return int(time.Now().Sub(when).Seconds()), nil
}
func InsertPost(post *Post, bump bool) error {
	const sql = `INSERT INTO DBPREFIXposts (id, thread_id, name, tripcode, is_role_signature, email, subject, ip, is_top_post, message, message_raw, banned_message, password)
	VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
	isNewThread := post.ParentID == 0
	var threadID int
	var err error
	if isNewThread {
		threadID, err = createThread(post.BoardID, post.Locked, post.Stickied, post.Autosage, false)
	} else {
		threadID, err = getThreadID(post.ParentID)
	}
	if err != nil {
		return err
	}
	isPrimaryKeyError := true
	for isPrimaryKeyError {
		nextFreeID, err := getNextFreeID("DBPREFIXposts")
		if err != nil {
			return err
		}
		_, err = ExecSQL(sql, nextFreeID, threadID, post.Name, post.Tripcode, false, post.Email, post.Subject, post.IP, isNewThread, string(post.MessageHTML), post.MessageText, "", post.Password)
		isPrimaryKeyError, err = errFilterDuplicatePrimaryKey(err)
		if err != nil {
			return err
		}
		if !isPrimaryKeyError {
			post.ID = nextFreeID
		}
	}
	if post.Filename != "" {
		err = appendFile(post.ID, post.FilenameOriginal, post.Filename, post.FileChecksum, post.Filesize, false, post.ImageW, post.ImageH, post.ThumbW, post.ThumbH)
	}
	if err != nil {
		return err
	}
	if bump {
		return bumpThread(threadID)
	}
	return nil
}
func createThread(boardID int, locked, stickied, anchored, cyclical bool) (threadID int, err error) {
	const sql = `INSERT INTO DBPREFIXthreads (board_id, locked, stickied, anchored, cyclical) VALUES (?,?,?,?,?)`
	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
		}
	}
	return threadID, nil
}
func bumpThreadOfPost(postID int) error {
	id, err := getThreadID(postID)
	if err != nil {
		return err
	}
	return bumpThread(id)
}
func bumpThread(threadID int) error {
	const sql = "UPDATE DBPREFIXthreads SET last_bump = CURRENT_TIMESTAMP WHERE id = ?"
	_, err := ExecSQL(sql, threadID)
	return err
}
func appendFile(postID int, originalFilename, filename, checksum string, fileSize int, isSpoilered bool, width, height, thumbnailWidth, thumbnailHeight int) error {
	const nextIDSQL = `SELECT COALESCE(MAX(file_order) + 1, 0) FROM DBPREFIXfiles WHERE post_id = ?`
	var nextID int
	err := QueryRowSQL(nextIDSQL, interfaceSlice(postID), interfaceSlice(&nextID))
	if err != nil {
		return err
	}
	const insertSQL = `INSERT INTO DBPREFIXfiles (file_order, post_id, original_filename, filename, checksum, file_size, is_spoilered, width, height, thumbnail_width, thumbnail_height)
	VALUES (?,?,?,?,?,?,?,?,?,?,?)`
	_, err = ExecSQL(insertSQL, nextID, postID, originalFilename, filename, checksum, fileSize, isSpoilered, width, height, thumbnailWidth, thumbnailHeight)
	return err
}
func GetMaxMessageLength(boardID int) (length int, err error) {
	const sql = `SELECT max_message_length FROM DBPREFIXboards
	WHERE id = ?`
	err = QueryRowSQL(sql, interfaceSlice(boardID), interfaceSlice(&length))
	return length, err
}
func GetEmbedsAllowed(boardID int) (allowed bool, err error) {
	const sql = `SELECT allow_embeds FROM DBPREFIXboards
	WHERE id = ?`
	err = QueryRowSQL(sql, interfaceSlice(boardID), interfaceSlice(&allowed))
	return allowed, err
}
func GetBoardFromPostID(postID int) (boardURI string, wasFound bool, err error) {
	const query = `SELECT board.uri FROM DBPREFIXboards as board
	JOIN (
		SELECT threads.board_id FROM DBPREFIXthreads as threads
		JOIN DBPREFIXposts as posts ON posts.thread_id = threads.id
		WHERE posts.id = ?
	) as threads ON threads.board_id = board.id`
	err = QueryRowSQL(query, interfaceSlice(postID), interfaceSlice(&boardURI))
	if err == sql.ErrNoRows {
		return "", false, nil
	}
	return boardURI, true, err
}
func GetThreadIDZeroIfTopPost(postID int) (ID int, err error) {
	const sql = `SELECT t1.id FROM DBPREFIXposts as t1
	JOIN (SELECT thread_id FROM DBPREFIXposts where id = ?) as t2 ON t1.thread_id = t2.thread_id
	WHERE t1.is_top_post`
	err = QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&ID))
	if err != nil {
		return 0, err
	}
	if ID == postID {
		return 0, nil
	}
	return ID, nil
}
func getThreadID(postID int) (ID int, err error) {
	const sql = `SELECT thread_id FROM DBPREFIXposts WHERE id = ?`
	err = QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&ID))
	return ID, err
}
func AddBanAppeal(banID uint, message string) error {
	const sql1 = `
	INSERT INTO DBPREFIXip_ban_appeals_audit (appeal_id, staff_id, appeal_text, staff_response, is_denied)
	SELECT id, staff_id, appeal_text, staff_response, is_denied
	FROM DBPREFIXip_ban_appeals
	WHERE DBPREFIXip_ban_appeals.ip_ban_id = ?`
	const sql2 = `
	UPDATE DBPREFIXip_ban_appeals SET appeal_text = ? WHERE ip_ban_id = ?
	`
	_, err := ExecSQL(sql1, banID)
	if err != nil {
		return err
	}
	_, err = ExecSQL(sql2, message, banID)
	return err
}
func GetPostPassword(postID int) (password string, err error) {
	const sql = `SELECT password_checksum FROM DBPREFIXposts WHERE id = ?`
	err = QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&password))
	return password, err
}
func UpdatePost(postID int, email, subject string, message template.HTML, messageRaw string) error {
	const sql = `UPDATE DBPREFIXposts SET email = ?, subject = ?, message = ?, message_raw = ? WHERE id = ?`
	_, err := ExecSQL(sql, email, subject, string(message), messageRaw)
	return err
}
func DeleteFilesFromPost(postID int) error {
	board, boardWasFound, err := GetBoardFromPostID(postID)
	if err != nil {
		return err
	}
	if !boardWasFound {
		return fmt.Errorf("could not find board for post %v", postID)
	}
	const filenameSQL = `SELECT filename FROM DBPREFIXfiles WHERE post_id = ?`
	rows, err := QuerySQL(filenameSQL)
	if err != nil {
		return err
	}
	var filenames []string
	for rows.Next() {
		var filename string
		if err = rows.Scan(&filename); err != nil {
			return err
		}
		filenames = append(filenames, filename)
	}
	systemCriticalCfg := config.GetSystemCriticalConfig()
	for _, fileName := range filenames {
		fileName = fileName[:strings.Index(fileName, ".")]
		fileType := fileName[strings.Index(fileName, ".")+1:]
		var thumbType string
		if fileType == "gif" || fileType == "webm" || fileType == "mp4" {
			thumbType = "jpg"
		}
		os.Remove(path.Join(systemCriticalCfg.DocumentRoot, board, "/src/"+fileName+"."+fileType))
		os.Remove(path.Join(systemCriticalCfg.DocumentRoot, board, "/thumb/"+fileName+"t."+thumbType))
		os.Remove(path.Join(systemCriticalCfg.DocumentRoot, board, "/thumb/"+fileName+"c."+thumbType))
	}
	const removeFilesSQL = `DELETE FROM DBPREFIXfiles WHERE post_id = ?`
	_, err = ExecSQL(removeFilesSQL, postID)
	return err
}
func DeletePost(postID int, checkIfTopPost bool) error {
	if checkIfTopPost {
		isTopPost, err := isTopPost(postID)
		if err != nil {
			return err
		}
		if isTopPost {
			threadID, err := getThreadID(postID)
			if err != nil {
				return err
			}
			return deleteThread(threadID)
		}
	}
	DeleteFilesFromPost(postID)
	const sql = `UPDATE DBPREFIXposts SET is_deleted = TRUE, deleted_at = CURRENT_TIMESTAMP WHERE id = ?`
	_, err := ExecSQL(sql, postID)
	return err
}
func isTopPost(postID int) (val bool, err error) {
	const sql = `SELECT is_top_post FROM DBPREFIXposts WHERE id = ?`
	err = QueryRowSQL(sql, interfaceSlice(postID), interfaceSlice(&val))
	return val, err
}
func deleteThread(threadID int) error {
	const sql1 = `UPDATE DBPREFIXthreads SET is_deleted = TRUE, deleted_at = CURRENT_TIMESTAMP WHERE id = ?`
	const sql2 = `SELECT id FROM DBPREFIXposts WHERE thread_id = ?`
	_, err := QuerySQL(sql1, threadID)
	if err != nil {
		return err
	}
	rows, err := QuerySQL(sql2, threadID)
	if err != nil {
		return err
	}
	var ids []int
	for rows.Next() {
		var id int
		if err = rows.Scan(&id); err != nil {
			return err
		}
		ids = append(ids, id)
	}
	for _, id := range ids {
		if err = DeletePost(id, false); err != nil {
			return err
		}
	}
	return nil
}
func CreateDefaultBoardIfNoneExist() error {
	const sqlStr = `SELECT COUNT(id) FROM DBPREFIXboards`
	var count int
	QueryRowSQL(sqlStr, interfaceSlice(), interfaceSlice(&count))
	if count > 0 {
		return nil
	}
	defaultSectionID, err := GetOrCreateDefaultSectionID()
	if err != nil && err != sql.ErrNoRows {
		return err
	}
	var board = Board{
		Dir:         "test",
		Title:       "Testing board",
		Subtitle:    "Board for testing",
		Description: "Board for testing",
		Section:     defaultSectionID}
	board.SetDefaults()
	err = CreateBoard(&board)
	if err != nil {
		panic(err)
	}
	return nil  
}
func CreateDefaultAdminIfNoStaff() error {
	const sql = `SELECT COUNT(id) FROM DBPREFIXstaff`
	var count int
	QueryRowSQL(sql, interfaceSlice(), interfaceSlice(&count))
	if count > 0 {
		return nil
	}
	_, err := createUser("admin", gcutil.BcryptSum("password"), 3)
	return err
}
func createUser(username, passwordEncrypted string, globalRank int) (userID int, err error) {
	const sqlInsert = `INSERT INTO DBPREFIXstaff (username, password_checksum, global_rank) VALUES (?,?,?)`
	const sqlSelect = `SELECT id FROM DBPREFIXstaff WHERE username = ?`
	_, err = ExecSQL(sqlInsert, username, passwordEncrypted, globalRank)
	if err != nil {
		return 0, err
	}
	err = QueryRowSQL(sqlSelect, interfaceSlice(username), interfaceSlice(&userID))
	return userID, err
}
func (board *Board) UpdateID() error {
	const sql = `SELECT id FROM DBPREFIXboards WHERE dir = ?`
	return QueryRowSQL(sql, interfaceSlice(board.Dir), interfaceSlice(&board.ID))
}
func (board *Board) PopulateData(id int) error {
	const sql = "SELECT id, section_id, dir, navbar_position, title, subtitle, description, max_file_size, default_style, locked, created_at, anonymous_name, force_anonymous, autosage_after, no_images_after, max_message_length, allow_embeds, redirect_to_thread, require_file, enable_catalog FROM DBPREFIXboards WHERE id = ?"
	return QueryRowSQL(sql, interfaceSlice(id), interfaceSlice(&board.ID, &board.Section, &board.Dir, &board.ListOrder, &board.Title, &board.Subtitle, &board.Description, &board.MaxFilesize, &board.DefaultStyle, &board.Locked, &board.CreatedOn, &board.Anonymous, &board.ForcedAnon, &board.AutosageAfter, &board.NoImagesAfter, &board.MaxMessageLength, &board.EmbedsAllowed, &board.RedirectToThread, &board.RequireFile, &board.EnableCatalog))
}
func DoesBoardExistByID(ID int) (bool, error) {
	const sql = `SELECT COUNT(id) FROM DBPREFIXboards WHERE id = ?`
	var count int
	err := QueryRowSQL(sql, interfaceSlice(ID), interfaceSlice(&count))
	return count > 0, err
}