Optimise the database layer.

Refactor database adapters.
Experimental last ip cutoff.

More parser test cases.
This commit is contained in:
Azareal 2020-01-01 07:57:54 +10:00
parent d22021b022
commit 35ddc89009
48 changed files with 877 additions and 795 deletions

View File

@ -44,7 +44,7 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
alertStmts = AlertStmts{ alertStmts = AlertStmts{
notifyWatchers: acc.SimpleInsertInnerJoin( notifyWatchers: acc.SimpleInsertInnerJoin(
qgen.DBInsert{"activity_stream_matches", "watcher, asid", ""}, qgen.DBInsert{"activity_stream_matches", "watcher,asid", ""},
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""}, qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
), ),
notifyOne: acc.Insert("activity_stream_matches").Columns("watcher,asid").Fields("?,?").Prepare(), notifyOne: acc.Insert("activity_stream_matches").Columns("watcher,asid").Fields("?,?").Prepare(),

View File

@ -23,18 +23,17 @@ type DefaultIPSearcher struct {
func NewDefaultIPSearcher() (*DefaultIPSearcher, error) { func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
acc := qgen.NewAcc() acc := qgen.NewAcc()
return &DefaultIPSearcher{ return &DefaultIPSearcher{
searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(), searchUsers: acc.Select("users").Columns("uid").Where("last_ip=? OR last_ip LIKE CONCAT('%-',?)").Prepare(),
searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(), searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress=?")).Prepare(),
searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(), searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress=?")).Prepare(),
searchUsersReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(), searchUsersReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ipaddress=?")).Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
func (s *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) { func (s *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
var uid int var uid int
reqUserList := make(map[int]bool) reqUserList := make(map[int]bool)
runQuery := func(stmt *sql.Stmt) error { runQuery2 := func(rows *sql.Rows, err error) error {
rows, err := stmt.Query(ip)
if err != nil { if err != nil {
return err return err
} }
@ -49,8 +48,11 @@ func (s *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
} }
return rows.Err() return rows.Err()
} }
runQuery := func(stmt *sql.Stmt) error {
return runQuery2(stmt.Query(ip))
}
err = runQuery(s.searchUsers) err = runQuery2(s.searchUsers.Query(ip, ip))
if err != nil { if err != nil {
return uids, err return uids, err
} }

View File

@ -17,11 +17,24 @@ type DefaultPasswordResetter struct {
delete *sql.Stmt delete *sql.Stmt
} }
/*
type PasswordReset struct {
Email string `q:"email"`
Uid int `q:"uid"`
Validated bool `q:"validated"`
Token string `q:"token"`
CreatedAt time.Time `q:"createdAt"`
}
*/
func NewDefaultPasswordResetter(acc *qgen.Accumulator) (*DefaultPasswordResetter, error) { func NewDefaultPasswordResetter(acc *qgen.Accumulator) (*DefaultPasswordResetter, error) {
pr := "password_resets"
return &DefaultPasswordResetter{ return &DefaultPasswordResetter{
getTokens: acc.Select("password_resets").Columns("token").Where("uid = ?").Prepare(), getTokens: acc.Select(pr).Columns("token").Where("uid = ?").Prepare(),
create: acc.Insert("password_resets").Columns("email, uid, validated, token, createdAt").Fields("?,?,0,?,UTC_TIMESTAMP()").Prepare(), create: acc.Insert(pr).Columns("email, uid, validated, token, createdAt").Fields("?,?,0,?,UTC_TIMESTAMP()").Prepare(),
delete: acc.Delete("password_resets").Where("uid =?").Prepare(), //create: acc.Insert(pr).Cols("email,uid,validated=0,token,createdAt=UTC_TIMESTAMP()").Prep(),
delete: acc.Delete(pr).Where("uid=?").Prepare(),
//model: acc.Model(w).Cols("email,uid,validated=0,token").Key("uid").CreatedAt("createdAt").Prep(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -45,16 +58,14 @@ func (r *DefaultPasswordResetter) ValidateToken(uid int, token string) error {
success := false success := false
for rows.Next() { for rows.Next() {
var rtoken string var rtoken string
err := rows.Scan(&rtoken) if err := rows.Scan(&rtoken); err != nil {
if err != nil {
return err return err
} }
if subtle.ConstantTimeCompare([]byte(token), []byte(rtoken)) == 1 { if subtle.ConstantTimeCompare([]byte(token), []byte(rtoken)) == 1 {
success = true success = true
} }
} }
err = rows.Err() if err = rows.Err(); err != nil {
if err != nil {
return err return err
} }

View File

@ -44,7 +44,7 @@ func (s *DefaultReportStore) Create(title string, content string, u *User, itemT
return 0, ErrAlreadyReported return 0, ErrAlreadyReported
} }
res, err := s.create.Exec(title, content, ParseMessage(content, 0, "", nil), u.LastIP, u.ID, u.ID, itemType+"_"+strconv.Itoa(itemID), ReportForumID) res, err := s.create.Exec(title, content, ParseMessage(content, 0, "", nil), u.GetIP(), u.ID, u.ID, itemType+"_"+strconv.Itoa(itemID), ReportForumID)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -316,15 +316,15 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
} }
} }
usercpy.LastIP = host if usercpy.Loggedin && host != usercpy.GetIP() {
mon := time.Now().Month()
if usercpy.Loggedin && host != usercpy.LastIP { err = usercpy.UpdateIP(strconv.Itoa(int(mon)) + "-" + host)
err = usercpy.UpdateIP(host)
if err != nil { if err != nil {
InternalError(err, w, r) InternalError(err, w, r)
return *usercpy, false return *usercpy, false
} }
} }
usercpy.LastIP = host
return *usercpy, true return *usercpy, true
} }
@ -350,11 +350,11 @@ func UploadAvatar(w http.ResponseWriter, r *http.Request, user User, tuid int) (
if hdr.Filename == "" { if hdr.Filename == "" {
continue continue
} }
infile, err := hdr.Open() inFile, err := hdr.Open()
if err != nil { if err != nil {
return "", LocalError("Upload failed", w, r, user) return "", LocalError("Upload failed", w, r, user)
} }
defer infile.Close() defer inFile.Close()
if ext == "" { if ext == "" {
extarr := strings.Split(hdr.Filename, ".") extarr := strings.Split(hdr.Filename, ".")
@ -377,13 +377,13 @@ func UploadAvatar(w http.ResponseWriter, r *http.Request, user User, tuid int) (
} }
// TODO: Centralise this string, so we don't have to change it in two different places when it changes // TODO: Centralise this string, so we don't have to change it in two different places when it changes
outfile, err := os.Create("./uploads/avatar_" + strconv.Itoa(tuid) + "." + ext) outFile, err := os.Create("./uploads/avatar_" + strconv.Itoa(tuid) + "." + ext)
if err != nil { if err != nil {
return "", LocalError("Upload failed [File Creation Failed]", w, r, user) return "", LocalError("Upload failed [File Creation Failed]", w, r, user)
} }
defer outfile.Close() defer outFile.Close()
_, err = io.Copy(outfile, infile) _, err = io.Copy(outFile, inFile)
if err != nil { if err != nil {
return "", LocalError("Upload failed [Copy Failed]", w, r, user) return "", LocalError("Upload failed [Copy Failed]", w, r, user)
} }

View File

@ -222,6 +222,12 @@ func ProcessConfig() (err error) {
if Config.LogPruneCutoff == 0 { if Config.LogPruneCutoff == 0 {
Config.LogPruneCutoff = 180 // Default cutoff Config.LogPruneCutoff = 180 // Default cutoff
} }
if Config.LastIPCutoff == 0 {
Config.LastIPCutoff = 3 // Default cutoff
}
if Config.LastIPCutoff > 12 {
Config.LastIPCutoff = 12
}
if Config.NoEmbed { if Config.NoEmbed {
DefaultParseSettings.NoEmbed = true DefaultParseSettings.NoEmbed = true
} }

View File

@ -201,7 +201,7 @@ func (u *User) Unban() error {
} }
func (u *User) deleteScheduleGroupTx(tx *sql.Tx) error { func (u *User) deleteScheduleGroupTx(tx *sql.Tx) error {
deleteScheduleGroupStmt, err := qgen.Builder.SimpleDeleteTx(tx, "users_groups_scheduler", "uid = ?") deleteScheduleGroupStmt, err := qgen.Builder.SimpleDeleteTx(tx, "users_groups_scheduler", "uid=?")
if err != nil { if err != nil {
return err return err
} }
@ -338,9 +338,14 @@ func (u *User) ChangeGroup(group int) (err error) {
return u.bindStmt(userStmts.changeGroup, group) return u.bindStmt(userStmts.changeGroup, group)
} }
func (u *User) GetIP() string {
spl := strings.Split(u.LastIP,"-")
return spl[len(spl)-1]
}
// ! Only updates the database not the *User for safety reasons // ! Only updates the database not the *User for safety reasons
func (u *User) UpdateIP(host string) error { func (u *User) UpdateIP(ip string) error {
_, err := userStmts.updateLastIP.Exec(host, u.ID) _, err := userStmts.updateLastIP.Exec(ip, u.ID)
if uc := Users.GetCache(); uc != nil { if uc := Users.GetCache(); uc != nil {
uc.Remove(u.ID) uc.Remove(u.ID)
} }

View File

@ -84,9 +84,11 @@ BuildSlugs - Whether you want the title appear in the URL. For instance: `/topic
ServerCount - The number of instances you're running. This setting is currently experimental. ServerCount - The number of instances you're running. This setting is currently experimental.
PostIPCutoff - The number of days which need to pass before the IP data for a post is automatically deleted. 0 defaults to whatever the current default is, currently 180 and -1 disables this feature. Default: 0 LastIPCutoff - The number of months which need to pass before the last IP stored for a user is automatically deleted. Capped at 12. 0 defaults to whatever the current default is, currently 3 and -1 disables this feature.
LogPruneCutoff - The number of days which need to pass before the login and registration logs are pruned. 0 defaults to whatever the current default is, currently 365 and -1 disables this feature. Default: 0 PostIPCutoff - The number of days which need to pass before the IP data for a post is automatically deleted. 0 defaults to whatever the current default is, currently 120 and -1 disables this feature. Default: 0
LogPruneCutoff - The number of days which need to pass before the login and registration logs are pruned. 0 defaults to whatever the current default is, currently 180 and -1 disables this feature. Default: 0
DisableLiveTopicList - This switch allows you to disable the live topic list. Default: false DisableLiveTopicList - This switch allows you to disable the live topic list. Default: false

View File

@ -74,24 +74,25 @@ func BbcodeRegexParse(msg string) string {
// Only does the simple BBCode like [u], [b], [i] and [s] // Only does the simple BBCode like [u], [b], [i] and [s]
func bbcodeSimpleParse(msg string) string { func bbcodeSimpleParse(msg string) string {
var hasU, hasB, hasI, hasS bool var hasU, hasB, hasI, hasS bool
msgbytes := []byte(msg) mbytes := []byte(msg)
for i := 0; (i + 2) < len(msgbytes); i++ { for i := 0; (i + 2) < len(mbytes); i++ {
if msgbytes[i] == '[' && msgbytes[i+2] == ']' { if mbytes[i] == '[' && mbytes[i+2] == ']' {
if msgbytes[i+1] == 'b' && !hasB { ch := mbytes[i+1]
msgbytes[i] = '<' if ch == 'b' && !hasB {
msgbytes[i+2] = '>' mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true hasB = true
} else if msgbytes[i+1] == 'i' && !hasI { } else if ch == 'i' && !hasI {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasI = true hasI = true
} else if msgbytes[i+1] == 'u' && !hasU { } else if ch == 'u' && !hasU {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasU = true hasU = true
} else if msgbytes[i+1] == 's' && !hasS { } else if ch == 's' && !hasS {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasS = true hasS = true
} }
i += 2 i += 2
@ -105,46 +106,47 @@ func bbcodeSimpleParse(msg string) string {
closeBold := []byte("</b>") closeBold := []byte("</b>")
closeStrike := []byte("</s>") closeStrike := []byte("</s>")
if hasI { if hasI {
msgbytes = append(msgbytes, closeItalic...) mbytes = append(mbytes, closeItalic...)
} }
if hasU { if hasU {
msgbytes = append(msgbytes, closeUnder...) mbytes = append(mbytes, closeUnder...)
} }
if hasB { if hasB {
msgbytes = append(msgbytes, closeBold...) mbytes = append(mbytes, closeBold...)
} }
if hasS { if hasS {
msgbytes = append(msgbytes, closeStrike...) mbytes = append(mbytes, closeStrike...)
} }
} }
return string(msgbytes) return string(mbytes)
} }
// Here for benchmarking purposes. Might add a plugin setting for disabling [code] as it has it's paws everywhere // Here for benchmarking purposes. Might add a plugin setting for disabling [code] as it has it's paws everywhere
func BbcodeParseWithoutCode(msg string) string { func BbcodeParseWithoutCode(msg string) string {
var hasU, hasB, hasI, hasS bool var hasU, hasB, hasI, hasS bool
var complexBbc bool var complexBbc bool
msgbytes := []byte(msg) mbytes := []byte(msg)
for i := 0; (i + 3) < len(msgbytes); i++ { for i := 0; (i + 3) < len(mbytes); i++ {
if msgbytes[i] == '[' { if mbytes[i] == '[' {
if msgbytes[i+2] != ']' { if mbytes[i+2] != ']' {
if msgbytes[i+1] == '/' { if mbytes[i+1] == '/' {
if msgbytes[i+3] == ']' { if mbytes[i+3] == ']' {
if msgbytes[i+2] == 'b' { switch mbytes[i+2] {
msgbytes[i] = '<' case 'b':
msgbytes[i+3] = '>' mbytes[i] = '<'
mbytes[i+3] = '>'
hasB = false hasB = false
} else if msgbytes[i+2] == 'i' { case 'i':
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+3] = '>' mbytes[i+3] = '>'
hasI = false hasI = false
} else if msgbytes[i+2] == 'u' { case 'u':
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+3] = '>' mbytes[i+3] = '>'
hasU = false hasU = false
} else if msgbytes[i+2] == 's' { case 's':
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+3] = '>' mbytes[i+3] = '>'
hasS = false hasS = false
} }
i += 3 i += 3
@ -155,21 +157,22 @@ func BbcodeParseWithoutCode(msg string) string {
complexBbc = true complexBbc = true
} }
} else { } else {
if msgbytes[i+1] == 'b' && !hasB { ch := mbytes[i+1]
msgbytes[i] = '<' if ch == 'b' && !hasB {
msgbytes[i+2] = '>' mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true hasB = true
} else if msgbytes[i+1] == 'i' && !hasI { } else if ch == 'i' && !hasI {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasI = true hasI = true
} else if msgbytes[i+1] == 'u' && !hasU { } else if ch == 'u' && !hasU {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasU = true hasU = true
} else if msgbytes[i+1] == 's' && !hasS { } else if ch == 's' && !hasS {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasS = true hasS = true
} }
i += 2 i += 2
@ -184,29 +187,29 @@ func BbcodeParseWithoutCode(msg string) string {
closeBold := []byte("</b>") closeBold := []byte("</b>")
closeStrike := []byte("</s>") closeStrike := []byte("</s>")
if hasI { if hasI {
msgbytes = append(bytes.TrimSpace(msgbytes), closeItalic...) mbytes = append(bytes.TrimSpace(mbytes), closeItalic...)
} }
if hasU { if hasU {
msgbytes = append(bytes.TrimSpace(msgbytes), closeUnder...) mbytes = append(bytes.TrimSpace(mbytes), closeUnder...)
} }
if hasB { if hasB {
msgbytes = append(bytes.TrimSpace(msgbytes), closeBold...) mbytes = append(bytes.TrimSpace(mbytes), closeBold...)
} }
if hasS { if hasS {
msgbytes = append(bytes.TrimSpace(msgbytes), closeStrike...) mbytes = append(bytes.TrimSpace(mbytes), closeStrike...)
} }
} }
// Copy the new complex parser over once the rough edges have been smoothed over // Copy the new complex parser over once the rough edges have been smoothed over
if complexBbc { if complexBbc {
msg = string(msgbytes) msg = string(mbytes)
msg = bbcodeURL.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$1$2//$3</i>") msg = bbcodeURL.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$1$2//$3</i>")
msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$4</i>") msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$4</i>")
msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>") msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeQuotes.ReplaceAllString(msg, "<blockquote>$1</blockquote>") msg = bbcodeQuotes.ReplaceAllString(msg, "<blockquote>$1</blockquote>")
return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>") return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
} }
return string(msgbytes) return string(mbytes)
} }
// Does every type of BBCode // Does every type of BBCode
@ -214,63 +217,66 @@ func BbcodeFullParse(msg string) string {
var hasU, hasB, hasI, hasS, hasC bool var hasU, hasB, hasI, hasS, hasC bool
var complexBbc bool var complexBbc bool
msgbytes := []byte(msg) mbytes := []byte(msg)
msgbytes = append(msgbytes, c.SpaceGap...) mbytes = append(mbytes, c.SpaceGap...)
for i := 0; i < len(msgbytes); i++ { for i := 0; i < len(mbytes); i++ {
if msgbytes[i] == '[' { if mbytes[i] == '[' {
if msgbytes[i+2] != ']' { if mbytes[i+2] != ']' {
if msgbytes[i+1] == '/' { if mbytes[i+1] == '/' {
if msgbytes[i+3] == ']' { if mbytes[i+3] == ']' {
if !hasC { if !hasC {
if msgbytes[i+2] == 'b' { switch mbytes[i+2] {
msgbytes[i] = '<' case 'b':
msgbytes[i+3] = '>' mbytes[i] = '<'
mbytes[i+3] = '>'
hasB = false hasB = false
} else if msgbytes[i+2] == 'i' { case 'i':
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+3] = '>' mbytes[i+3] = '>'
hasI = false hasI = false
} else if msgbytes[i+2] == 'u' { case 'u':
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+3] = '>' mbytes[i+3] = '>'
hasU = false hasU = false
} else if msgbytes[i+2] == 's' { case 's':
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+3] = '>' mbytes[i+3] = '>'
hasS = false hasS = false
} }
i += 3 i += 3
} }
} else { } else {
if msgbytes[i+2] == 'c' && msgbytes[i+3] == 'o' && msgbytes[i+4] == 'd' && msgbytes[i+5] == 'e' && msgbytes[i+6] == ']' { if mbytes[i+6] == ']' && mbytes[i+2] == 'c' && mbytes[i+3] == 'o' && mbytes[i+4] == 'd' && mbytes[i+5] == 'e' {
hasC = false hasC = false
i += 7 i += 7
} }
complexBbc = true complexBbc = true
} }
} else { } else {
if msgbytes[i+1] == 'c' && msgbytes[i+2] == 'o' && msgbytes[i+3] == 'd' && msgbytes[i+4] == 'e' && msgbytes[i+5] == ']' { // Put the biggest index first to avoid unnecessary bounds checks
if mbytes[i+5] == ']' && mbytes[i+1] == 'c' && mbytes[i+2] == 'o' && mbytes[i+3] == 'd' && mbytes[i+4] == 'e' {
hasC = true hasC = true
i += 6 i += 6
} }
complexBbc = true complexBbc = true
} }
} else if !hasC { } else if !hasC {
if msgbytes[i+1] == 'b' && !hasB { ch := mbytes[i+1]
msgbytes[i] = '<' if ch == 'b' && !hasB {
msgbytes[i+2] = '>' mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true hasB = true
} else if msgbytes[i+1] == 'i' && !hasI { } else if ch == 'i' && !hasI {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasI = true hasI = true
} else if msgbytes[i+1] == 'u' && !hasU { } else if ch == 'u' && !hasU {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasU = true hasU = true
} else if msgbytes[i+1] == 's' && !hasS { } else if ch == 's' && !hasS {
msgbytes[i] = '<' mbytes[i] = '<'
msgbytes[i+2] = '>' mbytes[i+2] = '>'
hasS = true hasS = true
} }
i += 2 i += 2
@ -285,46 +291,46 @@ func BbcodeFullParse(msg string) string {
closeBold := []byte("</b>") closeBold := []byte("</b>")
closeStrike := []byte("</s>") closeStrike := []byte("</s>")
if hasI { if hasI {
msgbytes = append(bytes.TrimSpace(msgbytes), closeItalic...) mbytes = append(bytes.TrimSpace(mbytes), closeItalic...)
} }
if hasU { if hasU {
msgbytes = append(bytes.TrimSpace(msgbytes), closeUnder...) mbytes = append(bytes.TrimSpace(mbytes), closeUnder...)
} }
if hasB { if hasB {
msgbytes = append(bytes.TrimSpace(msgbytes), closeBold...) mbytes = append(bytes.TrimSpace(mbytes), closeBold...)
} }
if hasS { if hasS {
msgbytes = append(bytes.TrimSpace(msgbytes), closeStrike...) mbytes = append(bytes.TrimSpace(mbytes), closeStrike...)
} }
msgbytes = append(msgbytes, c.SpaceGap...) mbytes = append(mbytes, c.SpaceGap...)
} }
if complexBbc { if complexBbc {
i := 0 i := 0
var start, lastTag int var start, lastTag int
var outbytes []byte var outbytes []byte
for ; i < len(msgbytes); i++ { for ; i < len(mbytes); i++ {
if msgbytes[i] == '[' { if mbytes[i] == '[' {
if msgbytes[i+1] == 'u' { if mbytes[i+1] == 'u' {
if msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' { if mbytes[i+4] == ']' && mbytes[i+2] == 'r' && mbytes[i+3] == 'l' {
i, start, lastTag, outbytes = bbcodeParseURL(i, start, lastTag, msgbytes, outbytes) i, start, lastTag, outbytes = bbcodeParseURL(i, start, lastTag, mbytes, outbytes)
continue continue
} }
} else if msgbytes[i+1] == 'r' { } else if mbytes[i+1] == 'r' {
if bytes.Equal(msgbytes[i+2:i+6], []byte("and]")) { if bytes.Equal(mbytes[i+2:i+6], []byte("and]")) {
i, start, lastTag, outbytes = bbcodeParseRand(i, start, lastTag, msgbytes, outbytes) i, start, lastTag, outbytes = bbcodeParseRand(i, start, lastTag, mbytes, outbytes)
} }
} }
} }
} }
if lastTag != i { if lastTag != i {
outbytes = append(outbytes, msgbytes[lastTag:]...) outbytes = append(outbytes, mbytes[lastTag:]...)
} }
if len(outbytes) != 0 { if len(outbytes) != 0 {
msg = string(outbytes[0 : len(outbytes)-10]) msg = string(outbytes[0 : len(outbytes)-10])
} else { } else {
msg = string(msgbytes[0 : len(msgbytes)-10]) msg = string(mbytes[0 : len(mbytes)-10])
} }
// TODO: Optimise these // TODO: Optimise these
@ -335,27 +341,27 @@ func BbcodeFullParse(msg string) string {
msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>") msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeH1.ReplaceAllString(msg, "<h2>$1</h2>") msg = bbcodeH1.ReplaceAllString(msg, "<h2>$1</h2>")
} else { } else {
msg = string(msgbytes[0 : len(msgbytes)-10]) msg = string(mbytes[0 : len(mbytes)-10])
} }
return msg return msg
} }
// TODO: Strip the containing [url] so the media parser can work it's magic instead? Or do we want to allow something like [url=]label[/url] here? // TODO: Strip the containing [url] so the media parser can work it's magic instead? Or do we want to allow something like [url=]label[/url] here?
func bbcodeParseURL(i int, start int, lastTag int, msgbytes []byte, outbytes []byte) (int, int, int, []byte) { func bbcodeParseURL(i int, start int, lastTag int, mbytes []byte, outbytes []byte) (int, int, int, []byte) {
start = i + 5 start = i + 5
outbytes = append(outbytes, msgbytes[lastTag:i]...) outbytes = append(outbytes, mbytes[lastTag:i]...)
i = start i = start
i += c.PartialURLStringLen2(string(msgbytes[start:])) i += c.PartialURLStringLen2(string(mbytes[start:]))
if !bytes.Equal(msgbytes[i:i+6], []byte("[/url]")) { if !bytes.Equal(mbytes[i:i+6], []byte("[/url]")) {
outbytes = append(outbytes, c.InvalidURL...) outbytes = append(outbytes, c.InvalidURL...)
return i, start, lastTag, outbytes return i, start, lastTag, outbytes
} }
outbytes = append(outbytes, c.URLOpen...) outbytes = append(outbytes, c.URLOpen...)
outbytes = append(outbytes, msgbytes[start:i]...) outbytes = append(outbytes, mbytes[start:i]...)
outbytes = append(outbytes, c.URLOpen2...) outbytes = append(outbytes, c.URLOpen2...)
outbytes = append(outbytes, msgbytes[start:i]...) outbytes = append(outbytes, mbytes[start:i]...)
outbytes = append(outbytes, c.URLClose...) outbytes = append(outbytes, c.URLClose...)
i += 6 i += 6
lastTag = i lastTag = i

View File

@ -61,26 +61,26 @@ func _gen_mssql() (err error) {
} }
common.DebugLog("Preparing updateEmail statement.") common.DebugLog("Preparing updateEmail statement.")
stmts.updateEmail, err = db.Prepare("UPDATE [emails] SET [email] = ?,[uid] = ?,[validated] = ?,[token] = ? WHERE [email] = ?") stmts.updateEmail, err = db.Prepare("UPDATE [emails] SET [email]= ?,[uid]= ?,[validated]= ?,[token]= ? WHERE [email] = ?")
if err != nil { if err != nil {
log.Print("Error in updateEmail statement.") log.Print("Error in updateEmail statement.")
log.Print("Bad Query: ","UPDATE [emails] SET [email] = ?,[uid] = ?,[validated] = ?,[token] = ? WHERE [email] = ?") log.Print("Bad Query: ","UPDATE [emails] SET [email]= ?,[uid]= ?,[validated]= ?,[token]= ? WHERE [email] = ?")
return err return err
} }
common.DebugLog("Preparing setTempGroup statement.") common.DebugLog("Preparing setTempGroup statement.")
stmts.setTempGroup, err = db.Prepare("UPDATE [users] SET [temp_group] = ? WHERE [uid] = ?") stmts.setTempGroup, err = db.Prepare("UPDATE [users] SET [temp_group]= ? WHERE [uid] = ?")
if err != nil { if err != nil {
log.Print("Error in setTempGroup statement.") log.Print("Error in setTempGroup statement.")
log.Print("Bad Query: ","UPDATE [users] SET [temp_group] = ? WHERE [uid] = ?") log.Print("Bad Query: ","UPDATE [users] SET [temp_group]= ? WHERE [uid] = ?")
return err return err
} }
common.DebugLog("Preparing bumpSync statement.") common.DebugLog("Preparing bumpSync statement.")
stmts.bumpSync, err = db.Prepare("UPDATE [sync] SET [last_update] = GETUTCDATE()") stmts.bumpSync, err = db.Prepare("UPDATE [sync] SET [last_update]= GETUTCDATE()")
if err != nil { if err != nil {
log.Print("Error in bumpSync statement.") log.Print("Error in bumpSync statement.")
log.Print("Bad Query: ","UPDATE [sync] SET [last_update] = GETUTCDATE()") log.Print("Bad Query: ","UPDATE [sync] SET [last_update]= GETUTCDATE()")
return err return err
} }

View File

@ -59,21 +59,21 @@ func _gen_mysql() (err error) {
} }
common.DebugLog("Preparing updateEmail statement.") common.DebugLog("Preparing updateEmail statement.")
stmts.updateEmail, err = db.Prepare("UPDATE `emails` SET `email` = ?,`uid` = ?,`validated` = ?,`token` = ? WHERE `email` = ?") stmts.updateEmail, err = db.Prepare("UPDATE `emails` SET `email`= ?,`uid`= ?,`validated`= ?,`token`= ? WHERE `email` = ?")
if err != nil { if err != nil {
log.Print("Error in updateEmail statement.") log.Print("Error in updateEmail statement.")
return err return err
} }
common.DebugLog("Preparing setTempGroup statement.") common.DebugLog("Preparing setTempGroup statement.")
stmts.setTempGroup, err = db.Prepare("UPDATE `users` SET `temp_group` = ? WHERE `uid` = ?") stmts.setTempGroup, err = db.Prepare("UPDATE `users` SET `temp_group`= ? WHERE `uid` = ?")
if err != nil { if err != nil {
log.Print("Error in setTempGroup statement.") log.Print("Error in setTempGroup statement.")
return err return err
} }
common.DebugLog("Preparing bumpSync statement.") common.DebugLog("Preparing bumpSync statement.")
stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = UTC_TIMESTAMP()") stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update`= UTC_TIMESTAMP()")
if err != nil { if err != nil {
log.Print("Error in bumpSync statement.") log.Print("Error in bumpSync statement.")
return err return err

View File

@ -32,21 +32,21 @@ func _gen_pgsql() (err error) {
} }
common.DebugLog("Preparing updateEmail statement.") common.DebugLog("Preparing updateEmail statement.")
stmts.updateEmail, err = db.Prepare("UPDATE \"emails\" SET `email` = ?,`uid` = ?,`validated` = ?,`token` = ? WHERE `email` = ?") stmts.updateEmail, err = db.Prepare("UPDATE \"emails\" SET `email`= ?,`uid`= ?,`validated`= ?,`token`= ? WHERE `email` = ?")
if err != nil { if err != nil {
log.Print("Error in updateEmail statement.") log.Print("Error in updateEmail statement.")
return err return err
} }
common.DebugLog("Preparing setTempGroup statement.") common.DebugLog("Preparing setTempGroup statement.")
stmts.setTempGroup, err = db.Prepare("UPDATE \"users\" SET `temp_group` = ? WHERE `uid` = ?") stmts.setTempGroup, err = db.Prepare("UPDATE \"users\" SET `temp_group`= ? WHERE `uid` = ?")
if err != nil { if err != nil {
log.Print("Error in setTempGroup statement.") log.Print("Error in setTempGroup statement.")
return err return err
} }
common.DebugLog("Preparing bumpSync statement.") common.DebugLog("Preparing bumpSync statement.")
stmts.bumpSync, err = db.Prepare("UPDATE \"sync\" SET `last_update` = LOCALTIMESTAMP()") stmts.bumpSync, err = db.Prepare("UPDATE \"sync\" SET `last_update`= LOCALTIMESTAMP()")
if err != nil { if err != nil {
log.Print("Error in bumpSync statement.") log.Print("Error in bumpSync statement.")
return err return err

View File

@ -167,7 +167,14 @@ func TestParser(t *testing.T) {
l.Add("// t", "// t") l.Add("// t", "// t")
l.Add("http:// t", "<red>[Invalid URL]</red> t") l.Add("http:// t", "<red>[Invalid URL]</red> t")
l.Add("h", "h")
l.Add("ht", "ht")
l.Add("htt", "htt")
l.Add("http", "http")
l.Add("http:", "http:") l.Add("http:", "http:")
//t l.Add("http:/", "http:/")
//t l.Add("http:/d", "http:/d")
l.Add("http:d", "http:d")
l.Add("https:", "https:") l.Add("https:", "https:")
l.Add("ftp:", "ftp:") l.Add("ftp:", "ftp:")
l.Add("git:", "git:") l.Add("git:", "git:")

View File

@ -2,7 +2,7 @@
(() => { (() => {
addInitHook("end_init", () => { addInitHook("end_init", () => {
$("#dash_username input").click(function(){ $("#dash_username input").click(()=>{
$("#dash_username button").show(); $("#dash_username button").show();
}); });
}); });

View File

@ -97,7 +97,7 @@ function buildStatsChart(rawLabels, seriesData, timeRange, legendNames, bytes =
continue; continue;
} }
let date = new Date(rawLabels[i]*1000); let date = new Date(rawLabels[i]*1000);
console.log("date: ", date); console.log("date:", date);
let minutes = "0" + date.getMinutes(); let minutes = "0" + date.getMinutes();
let label = date.getHours() + ":" + minutes.substr(-2); let label = date.getHours() + ":" + minutes.substr(-2);
console.log("label:", label); console.log("label:", label);
@ -106,7 +106,7 @@ function buildStatsChart(rawLabels, seriesData, timeRange, legendNames, bytes =
} else { } else {
for(const i in rawLabels) { for(const i in rawLabels) {
let date = new Date(rawLabels[i]*1000); let date = new Date(rawLabels[i]*1000);
console.log("date: ", date); console.log("date:", date);
let minutes = "0" + date.getMinutes(); let minutes = "0" + date.getMinutes();
let label = date.getHours() + ":" + minutes.substr(-2); let label = date.getHours() + ":" + minutes.substr(-2);
console.log("label:", label); console.log("label:", label);

View File

@ -284,7 +284,7 @@ var imageExts = ["png", "jpg", "jpe","jpeg","jif","jfi","jfif", "svg", "bmp", "g
function addPollInput() { function addPollInput() {
console.log("clicked on pollinputinput"); console.log("clicked on pollinputinput");
let dataPollInput = $(this).parent().attr("data-pollinput"); let dataPollInput = $(this).parent().attr("data-pollinput");
console.log("dataPollInput: ", dataPollInput); console.log("dataPollInput:", dataPollInput);
if(dataPollInput == undefined) return; if(dataPollInput == undefined) return;
if(dataPollInput != (pollInputIndex-1)) return; if(dataPollInput != (pollInputIndex-1)) return;
$(".poll_content_row .formitem").append(Template_topic_c_poll_input({ $(".poll_content_row .formitem").append(Template_topic_c_poll_input({

View File

@ -13,14 +13,14 @@ console.log("forums:",forums);
Sortable.create(document.getElementById("panel_forums"), { Sortable.create(document.getElementById("panel_forums"), {
sort: true, sort: true,
onEnd: (evt) => { onEnd: (evt) => {
console.log("pre forums: ", forums) console.log("pre forums:", forums)
console.log("evt: ", evt) console.log("evt:", evt)
let oldFid = forums[evt.newIndex]; let oldFid = forums[evt.newIndex];
forums[evt.oldIndex] = oldFid; forums[evt.oldIndex] = oldFid;
let newFid = evt.item.getAttribute("data-fid"); let newFid = evt.item.getAttribute("data-fid");
console.log("newFid: ", newFid); console.log("newFid:", newFid);
forums[evt.newIndex] = newFid; forums[evt.newIndex] = newFid;
console.log("post forums: ", forums); console.log("post forums:", forums);
} }
}); });
@ -37,7 +37,7 @@ document.getElementById("panel_forums_order_button").addEventListener("click", (
if(req.status!==200) return; if(req.status!==200) return;
let resp = JSON.parse(req.responseText); let resp = JSON.parse(req.responseText);
console.log("resp: ", resp); console.log("resp:", resp);
// TODO: Should we move other notices into TmplPhrases like this one? // TODO: Should we move other notices into TmplPhrases like this one?
pushNotice(phraseBox["panel"]["panel.forums_order_updated"]); pushNotice(phraseBox["panel"]["panel.forums_order_updated"]);
if(resp.success==1) return; if(resp.success==1) return;

View File

@ -9,14 +9,14 @@ for(let i = 0; item = items[i]; i++) menuItems[i] = item.getAttribute("data-miid
Sortable.create(document.getElementById("panel_menu_item_holder"), { Sortable.create(document.getElementById("panel_menu_item_holder"), {
sort: true, sort: true,
onEnd: (evt) => { onEnd: (evt) => {
console.log("pre menuItems: ", menuItems) console.log("pre menuItems:", menuItems)
console.log("evt: ", evt) console.log("evt:", evt)
let oldMiid = menuItems[evt.newIndex]; let oldMiid = menuItems[evt.newIndex];
menuItems[evt.oldIndex] = oldMiid; menuItems[evt.oldIndex] = oldMiid;
let newMiid = evt.item.getAttribute("data-miid"); let newMiid = evt.item.getAttribute("data-miid");
console.log("newMiid: ", newMiid); console.log("newMiid:", newMiid);
menuItems[evt.newIndex] = newMiid; menuItems[evt.newIndex] = newMiid;
console.log("post menuItems: ", menuItems); console.log("post menuItems:", menuItems);
} }
}); });
@ -32,13 +32,13 @@ document.getElementById("panel_menu_items_order_button").addEventListener("click
// TODO: Signal the error with a notice // TODO: Signal the error with a notice
if(req.status===200) { if(req.status===200) {
let resp = JSON.parse(req.responseText); let resp = JSON.parse(req.responseText);
console.log("resp: ", resp); console.log("resp:", resp);
// TODO: Should we move other notices into TmplPhrases like this one? // TODO: Should we move other notices into TmplPhrases like this one?
pushNotice(phraseBox["panel"]["panel.themes_menus_items_order_updated"]); pushNotice(phraseBox["panel"]["panel.themes_menus_items_order_updated"]);
if(resp.success==1) return; if(resp.success==1) return;
} }
} catch(ex) { } catch(ex) {
console.error("exception: ", ex) console.error("exception:", ex)
} }
console.trace(); console.trace();
} }

View File

@ -1,23 +1,23 @@
"use strict"; "use strict";
$(document).ready(() => { $(document).ready(() => {
let clickHandle = function(event){ let clickHandle = function(ev){
console.log("in clickHandle") console.log("in clickHandle")
event.preventDefault(); ev.preventDefault();
let eparent = $(this).closest(".editable_parent"); let ep = $(this).closest(".editable_parent");
eparent.find(".hide_on_block_edit").addClass("edit_opened"); ep.find(".hide_on_block_edit").addClass("edit_opened");
eparent.find(".show_on_block_edit").addClass("edit_opened"); ep.find(".show_on_block_edit").addClass("edit_opened");
eparent.addClass("in_edit"); ep.addClass("in_edit");
eparent.find(".widget_save").click(() => { ep.find(".widget_save").click(() => {
eparent.find(".hide_on_block_edit").removeClass("edit_opened"); ep.find(".hide_on_block_edit").removeClass("edit_opened");
eparent.find(".show_on_block_edit").removeClass("edit_opened"); ep.find(".show_on_block_edit").removeClass("edit_opened");
eparent.removeClass("in_edit"); ep.removeClass("in_edit");
}); });
eparent.find(".widget_delete").click(function(event) { ep.find(".widget_delete").click(function(ev) {
event.preventDefault(); ev.preventDefault();
eparent.remove(); ep.remove();
let formData = new URLSearchParams(); let formData = new URLSearchParams();
formData.append("s",me.User.S); formData.append("s",me.User.S);
let req = new XMLHttpRequest(); let req = new XMLHttpRequest();
@ -29,15 +29,14 @@ $(document).ready(() => {
$(".widget_item a").click(clickHandle); $(".widget_item a").click(clickHandle);
let changeHandle = function(event){ let changeHandle = function(ev){
let wtype = this.options[this.selectedIndex].value; let wtype = this.options[this.selectedIndex].value;
let typeBlock = this.closest(".widget_edit").querySelector(".wtypes"); let typeBlock = this.closest(".widget_edit").querySelector(".wtypes");
typeBlock.className = "wtypes wtype_"+wtype; typeBlock.className = "wtypes wtype_"+wtype;
}; };
$(".wtype_sel").change(changeHandle); $(".wtype_sel").change(changeHandle);
$(".widget_new a").click(function(event){ $(".widget_new a").click(function(ev){
console.log("clicked widget_new a") console.log("clicked widget_new a")
let widgetList = this.closest(".panel_widgets"); let widgetList = this.closest(".panel_widgets");
let widgetNew = this.closest(".widget_new"); let widgetNew = this.closest(".widget_new");
@ -51,10 +50,10 @@ $(document).ready(() => {
$(".wtype_sel").change(changeHandle); $(".wtype_sel").change(changeHandle);
}); });
$(".widget_save").click(function(event){ $(".widget_save").click(function(ev){
console.log("in .widget_save") console.log("in .widget_save")
event.preventDefault(); ev.preventDefault();
event.stopPropagation(); ev.stopPropagation();
let pform = this.closest("form"); let pform = this.closest("form");
let data = new URLSearchParams(); let data = new URLSearchParams();
for (const pair of new FormData(pform)) data.append(pair[0], pair[1]); for (const pair of new FormData(pform)) data.append(pair[0], pair[1]);

View File

@ -99,19 +99,19 @@ func (build *Accumulator) Tx(handler func(*TransactionBuilder) error) {
build.RecordError(tx.Commit()) build.RecordError(tx.Commit())
} }
func (build *Accumulator) SimpleSelect(table string, columns string, where string, orderby string, limit string) *sql.Stmt { func (build *Accumulator) SimpleSelect(table, columns, where, orderby, limit string) *sql.Stmt {
return build.prepare(build.adapter.SimpleSelect("", table, columns, where, orderby, limit)) return build.prepare(build.adapter.SimpleSelect("", table, columns, where, orderby, limit))
} }
func (build *Accumulator) SimpleCount(table string, where string, limit string) *sql.Stmt { func (build *Accumulator) SimpleCount(table, where, limit string) *sql.Stmt {
return build.prepare(build.adapter.SimpleCount("", table, where, limit)) return build.prepare(build.adapter.SimpleCount("", table, where, limit))
} }
func (build *Accumulator) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) *sql.Stmt { func (build *Accumulator) SimpleLeftJoin(table1, table2, columns, joiners, where, orderby, limit string) *sql.Stmt {
return build.prepare(build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit)) return build.prepare(build.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit))
} }
func (build *Accumulator) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) *sql.Stmt { func (build *Accumulator) SimpleInnerJoin(table1, table2, columns, joiners, where, orderby, limit string) *sql.Stmt {
return build.prepare(build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit)) return build.prepare(build.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit))
} }
@ -119,7 +119,7 @@ func (build *Accumulator) CreateTable(table string, charset string, collation st
return build.prepare(build.adapter.CreateTable("", table, charset, collation, columns, keys)) return build.prepare(build.adapter.CreateTable("", table, charset, collation, columns, keys))
} }
func (build *Accumulator) SimpleInsert(table string, columns string, fields string) *sql.Stmt { func (build *Accumulator) SimpleInsert(table, columns, fields string) *sql.Stmt {
return build.prepare(build.adapter.SimpleInsert("", table, columns, fields)) return build.prepare(build.adapter.SimpleInsert("", table, columns, fields))
} }
@ -135,15 +135,16 @@ func (build *Accumulator) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) *sql.S
return build.prepare(build.adapter.SimpleInsertInnerJoin("", ins, sel)) return build.prepare(build.adapter.SimpleInsertInnerJoin("", ins, sel))
} }
func (build *Accumulator) SimpleUpdate(table string, set string, where string) *sql.Stmt { func (build *Accumulator) SimpleUpdate(table, set, where string) *sql.Stmt {
return build.prepare(build.adapter.SimpleUpdate(qUpdate(table, set, where))) return build.prepare(build.adapter.SimpleUpdate(qUpdate(table, set, where)))
} }
func (build *Accumulator) SimpleUpdateSelect(table string, set string, where string) *sql.Stmt { func (build *Accumulator) SimpleUpdateSelect(table, set, table2, cols, where, orderby, limit string) *sql.Stmt {
return build.prepare(build.adapter.SimpleUpdateSelect(qUpdate(table, set, where))) pre := qUpdate(table, set, "").WhereQ(build.GetAdapter().Builder().Select().Table(table2).Columns(cols).Where(where).Orderby(orderby).Limit(limit))
return build.prepare(build.adapter.SimpleUpdateSelect(pre))
} }
func (build *Accumulator) SimpleDelete(table string, where string) *sql.Stmt { func (build *Accumulator) SimpleDelete(table, where string) *sql.Stmt {
return build.prepare(build.adapter.SimpleDelete("", table, where)) return build.prepare(build.adapter.SimpleDelete("", table, where))
} }
@ -256,7 +257,7 @@ type SimpleModel struct {
func (build *Accumulator) SimpleModel(tbl, colstr, primary string) SimpleModel { func (build *Accumulator) SimpleModel(tbl, colstr, primary string) SimpleModel {
var qlist, uplist string var qlist, uplist string
for _, col := range strings.Split(colstr,",") { for _, col := range strings.Split(colstr, ",") {
qlist += "?," qlist += "?,"
uplist += col + "=?," uplist += col + "=?,"
} }
@ -267,9 +268,9 @@ func (build *Accumulator) SimpleModel(tbl, colstr, primary string) SimpleModel {
where := primary + "=?" where := primary + "=?"
return SimpleModel{ return SimpleModel{
delete: build.Delete(tbl).Where(where).Prepare(), delete: build.Delete(tbl).Where(where).Prepare(),
create: build.Insert(tbl).Columns(colstr).Fields(qlist).Prepare(), create: build.Insert(tbl).Columns(colstr).Fields(qlist).Prepare(),
update: build.Update(tbl).Set(uplist).Where(where).Prepare(), update: build.Update(tbl).Set(uplist).Where(where).Prepare(),
} }
} }
@ -298,11 +299,11 @@ func (m SimpleModel) CreateID(args ...interface{}) (int, error) {
} }
func (build *Accumulator) Model(table string) *accModelBuilder { func (build *Accumulator) Model(table string) *accModelBuilder {
return &accModelBuilder{table,"",build} return &accModelBuilder{table, "", build}
} }
type accModelBuilder struct { type accModelBuilder struct {
table string table string
primary string primary string
build *Accumulator build *Accumulator

View File

@ -44,7 +44,7 @@ func (a *MssqlAdapter) DbVersion() string {
return "SELECT CONCAT(SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'))" return "SELECT CONCAT(SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'))"
} }
func (a *MssqlAdapter) DropTable(name string, table string) (string, error) { func (a *MssqlAdapter) DropTable(name, table string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -150,7 +150,7 @@ func (a *MssqlAdapter) AddColumn(name string, table string, column DBTableColumn
// TODO: Implement this // TODO: Implement this
// TODO: Test to make sure everything works here // TODO: Test to make sure everything works here
func (a *MssqlAdapter) AddIndex(name string, table string, iname string, colname string) (string, error) { func (a *MssqlAdapter) AddIndex(name, table, iname, colname string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -194,27 +194,26 @@ func (a *MssqlAdapter) AddForeignKey(name string, table string, column string, f
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }
func (a *MssqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) { func (a *MssqlAdapter) SimpleInsert(name, table, cols, fields string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
q := "INSERT INTO [" + table + "] (" q := "INSERT INTO [" + table + "] ("
if columns == "" { if cols == "" {
q += ") VALUES ()" q += ") VALUES ()"
a.pushStatement(name, "insert", q) a.pushStatement(name, "insert", q)
return q, nil return q, nil
} }
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(columns) { for _, col := range processColumns(cols) {
if column.Type == "function" { if col.Type == TokenFunc {
q += column.Left + "," q += col.Left + ","
} else { } else {
q += "[" + column.Left + "]," q += "[" + col.Left + "],"
} }
} }
// Remove the trailing comma
q = q[0 : len(q)-1] q = q[0 : len(q)-1]
q += ") VALUES (" q += ") VALUES ("
@ -314,17 +313,17 @@ func (a *MssqlAdapter) SimpleUpsert(name string, table string, columns string, f
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
q += " ?" q += " ?"
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -337,14 +336,14 @@ func (a *MssqlAdapter) SimpleUpsert(name string, table string, columns string, f
var fieldList string var fieldList string
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for columnID, column := range processColumns(columns) { for columnID, col := range processColumns(columns) {
fieldList += "f" + strconv.Itoa(columnID) + "," fieldList += "f" + strconv.Itoa(columnID) + ","
if column.Type == "function" { if col.Type == TokenFunc {
matched += column.Left + " = f" + strconv.Itoa(columnID) + "," matched += col.Left + " = f" + strconv.Itoa(columnID) + ","
notMatched += column.Left + "," notMatched += col.Left + ","
} else { } else {
matched += "[" + column.Left + "] = f" + strconv.Itoa(columnID) + "," matched += "[" + col.Left + "] = f" + strconv.Itoa(columnID) + ","
notMatched += "[" + column.Left + "]," notMatched += "[" + col.Left + "],"
} }
} }
@ -373,20 +372,20 @@ func (a *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
q := "UPDATE [" + up.table + "] SET " q := "UPDATE [" + up.table + "] SET "
for _, item := range processSet(up.set) { for _, item := range processSet(up.set) {
q += "[" + item.Column + "] =" q += "[" + item.Column + "]="
for _, token := range item.Expr { for _, token := range item.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
q += " ?" q += " ?"
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -394,7 +393,6 @@ func (a *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
} }
q += "," q += ","
} }
// Remove the trailing comma
q = q[0 : len(q)-1] q = q[0 : len(q)-1]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
@ -403,15 +401,15 @@ func (a *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
for _, loc := range processWhere(up.where) { for _, loc := range processWhere(up.where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -443,17 +441,17 @@ func (a *MssqlAdapter) SimpleDelete(name string, table string, where string) (st
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
q += " ?" q += " ?"
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -492,7 +490,7 @@ func (a *MssqlAdapter) SimpleSelect(name string, table string, columns string, w
if len(orderby) == 0 && limit != "" { if len(orderby) == 0 && limit != "" {
return "", errors.New("Orderby needs to be set to use limit on Mssql") return "", errors.New("Orderby needs to be set to use limit on Mssql")
} }
substituteCount := 0 subCount := 0
q := "" q := ""
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
@ -508,19 +506,19 @@ func (a *MssqlAdapter) SimpleSelect(name string, table string, columns string, w
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
substituteCount++ subCount++
q += " ?" + strconv.Itoa(substituteCount) q += " ?" + strconv.Itoa(subCount)
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
// MSSQL seems to convert the formats? so we'll compare it with a regular date. Do this with the other methods too? // MSSQL seems to convert the formats? so we'll compare it with a regular date. Do this with the other methods too?
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETDATE()" token.Contents = "GETDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -546,8 +544,8 @@ func (a *MssqlAdapter) SimpleSelect(name string, table string, columns string, w
log.Printf("limiter: %+v\n", limiter) log.Printf("limiter: %+v\n", limiter)
if limiter.Offset != "" { if limiter.Offset != "" {
if limiter.Offset == "?" { if limiter.Offset == "?" {
substituteCount++ subCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS" q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else { } else {
q += " OFFSET " + limiter.Offset + " ROWS" q += " OFFSET " + limiter.Offset + " ROWS"
} }
@ -556,8 +554,8 @@ func (a *MssqlAdapter) SimpleSelect(name string, table string, columns string, w
// ! Does this work without an offset? // ! Does this work without an offset?
if limiter.MaxCount != "" { if limiter.MaxCount != "" {
if limiter.MaxCount == "?" { if limiter.MaxCount == "?" {
substituteCount++ subCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount) limiter.MaxCount = "?" + strconv.Itoa(subCount)
} }
q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY "
} }
@ -594,22 +592,22 @@ func (a *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
if len(orderby) == 0 && limit != "" { if len(orderby) == 0 && limit != "" {
return "", errors.New("Orderby needs to be set to use limit on Mssql") return "", errors.New("Orderby needs to be set to use limit on Mssql")
} }
substituteCount := 0 subCount := 0
q := "" q := ""
for _, column := range processColumns(columns) { for _, col := range processColumns(columns) {
var source, alias string var source, alias string
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
if column.Table != "" { if col.Table != "" {
source = "[" + column.Table + "].[" + column.Left + "]" source = "[" + col.Table + "].[" + col.Left + "]"
} else if column.Type == "function" { } else if col.Type == TokenFunc {
source = column.Left source = col.Left
} else { } else {
source = "[" + column.Left + "]" source = "[" + col.Left + "]"
} }
if column.Alias != "" { if col.Alias != "" {
alias = " AS '" + column.Alias + "'" alias = " AS '" + col.Alias + "'"
} }
q += source + alias + "," q += source + alias + ","
} }
@ -629,23 +627,23 @@ func (a *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
substituteCount++ subCount++
q += " ?" + strconv.Itoa(substituteCount) q += " ?" + strconv.Itoa(subCount)
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
halves := strings.Split(token.Contents, ".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
q += " [" + halves[0] + "].[" + halves[1] + "]" q += " [" + halves[0] + "].[" + halves[1] + "]"
} else { } else {
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
} }
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -676,8 +674,8 @@ func (a *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
limiter := processLimit(limit) limiter := processLimit(limit)
if limiter.Offset != "" { if limiter.Offset != "" {
if limiter.Offset == "?" { if limiter.Offset == "?" {
substituteCount++ subCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS" q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else { } else {
q += " OFFSET " + limiter.Offset + " ROWS" q += " OFFSET " + limiter.Offset + " ROWS"
} }
@ -686,8 +684,8 @@ func (a *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
// ! Does this work without an offset? // ! Does this work without an offset?
if limiter.MaxCount != "" { if limiter.MaxCount != "" {
if limiter.MaxCount == "?" { if limiter.MaxCount == "?" {
substituteCount++ subCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount) limiter.MaxCount = "?" + strconv.Itoa(subCount)
} }
q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY "
} }
@ -719,22 +717,22 @@ func (a *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
if len(orderby) == 0 && limit != "" { if len(orderby) == 0 && limit != "" {
return "", errors.New("Orderby needs to be set to use limit on Mssql") return "", errors.New("Orderby needs to be set to use limit on Mssql")
} }
substituteCount := 0 subCount := 0
q := "" q := ""
for _, column := range processColumns(columns) { for _, col := range processColumns(columns) {
var source, alias string var source, alias string
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
if column.Table != "" { if col.Table != "" {
source = "[" + column.Table + "].[" + column.Left + "]" source = "[" + col.Table + "].[" + col.Left + "]"
} else if column.Type == "function" { } else if col.Type == TokenFunc {
source = column.Left source = col.Left
} else { } else {
source = "[" + column.Left + "]" source = "[" + col.Left + "]"
} }
if column.Alias != "" { if col.Alias != "" {
alias = " AS '" + column.Alias + "'" alias = " AS '" + col.Alias + "'"
} }
q += source + alias + "," q += source + alias + ","
} }
@ -754,23 +752,23 @@ func (a *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
substituteCount++ subCount++
q += " ?" + strconv.Itoa(substituteCount) q += " ?" + strconv.Itoa(subCount)
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
halves := strings.Split(token.Contents, ".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
q += " [" + halves[0] + "].[" + halves[1] + "]" q += " [" + halves[0] + "].[" + halves[1] + "]"
} else { } else {
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
} }
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -802,8 +800,8 @@ func (a *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
limiter := processLimit(limit) limiter := processLimit(limit)
if limiter.Offset != "" { if limiter.Offset != "" {
if limiter.Offset == "?" { if limiter.Offset == "?" {
substituteCount++ subCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS" q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else { } else {
q += " OFFSET " + limiter.Offset + " ROWS" q += " OFFSET " + limiter.Offset + " ROWS"
} }
@ -812,8 +810,8 @@ func (a *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
// ! Does this work without an offset? // ! Does this work without an offset?
if limiter.MaxCount != "" { if limiter.MaxCount != "" {
if limiter.MaxCount == "?" { if limiter.MaxCount == "?" {
substituteCount++ subCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount) limiter.MaxCount = "?" + strconv.Itoa(subCount)
} }
q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY "
} }
@ -839,28 +837,28 @@ func (a *MssqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelec
q := "INSERT INTO [" + ins.Table + "] (" q := "INSERT INTO [" + ins.Table + "] ("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(ins.Columns) { for _, col := range processColumns(ins.Columns) {
if column.Type == "function" { if col.Type == TokenFunc {
q += column.Left + "," q += col.Left + ","
} else { } else {
q += "[" + column.Left + "]," q += "[" + col.Left + "],"
} }
} }
q = q[0:len(q)-1] + ") SELECT " q = q[0:len(q)-1] + ") SELECT "
/* Select */ /* Select */
substituteCount := 0 subCount := 0
for _, column := range processColumns(sel.Columns) { for _, col := range processColumns(sel.Columns) {
var source, alias string var source, alias string
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
if column.Type == "function" || column.Type == "substitute" { if col.Type == TokenFunc || col.Type == TokenSub {
source = column.Left source = col.Left
} else { } else {
source = "[" + column.Left + "]" source = "[" + col.Left + "]"
} }
if column.Alias != "" { if col.Alias != "" {
alias = " AS [" + column.Alias + "]" alias = " AS [" + col.Alias + "]"
} }
q += " " + source + alias + "," q += " " + source + alias + ","
} }
@ -872,18 +870,18 @@ func (a *MssqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelec
for _, loc := range processWhere(sel.Where) { for _, loc := range processWhere(sel.Where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
substituteCount++ subCount++
q += " ?" + strconv.Itoa(substituteCount) q += " ?" + strconv.Itoa(subCount)
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -913,8 +911,8 @@ func (a *MssqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelec
limiter := processLimit(sel.Limit) limiter := processLimit(sel.Limit)
if limiter.Offset != "" { if limiter.Offset != "" {
if limiter.Offset == "?" { if limiter.Offset == "?" {
substituteCount++ subCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS" q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else { } else {
q += " OFFSET " + limiter.Offset + " ROWS" q += " OFFSET " + limiter.Offset + " ROWS"
} }
@ -923,8 +921,8 @@ func (a *MssqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelec
// ! Does this work without an offset? // ! Does this work without an offset?
if limiter.MaxCount != "" { if limiter.MaxCount != "" {
if limiter.MaxCount == "?" { if limiter.MaxCount == "?" {
substituteCount++ subCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount) limiter.MaxCount = "?" + strconv.Itoa(subCount)
} }
q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY "
} }
@ -950,30 +948,30 @@ func (a *MssqlAdapter) simpleJoin(name string, ins DBInsert, sel DBJoin, joinTyp
q := "INSERT INTO [" + ins.Table + "] (" q := "INSERT INTO [" + ins.Table + "] ("
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(ins.Columns) { for _, col := range processColumns(ins.Columns) {
if column.Type == "function" { if col.Type == TokenFunc {
q += column.Left + "," q += col.Left + ","
} else { } else {
q += "[" + column.Left + "]," q += "[" + col.Left + "],"
} }
} }
q = q[0:len(q)-1] + ") SELECT " q = q[0:len(q)-1] + ") SELECT "
/* Select */ /* Select */
substituteCount := 0 subCount := 0
for _, column := range processColumns(sel.Columns) { for _, col := range processColumns(sel.Columns) {
var source, alias string var source, alias string
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
if column.Table != "" { if col.Table != "" {
source = "[" + column.Table + "].[" + column.Left + "]" source = "[" + col.Table + "].[" + col.Left + "]"
} else if column.Type == "function" { } else if col.Type == TokenFunc {
source = column.Left source = col.Left
} else { } else {
source = "[" + column.Left + "]" source = "[" + col.Left + "]"
} }
if column.Alias != "" { if col.Alias != "" {
alias = " AS '" + column.Alias + "'" alias = " AS '" + col.Alias + "'"
} }
q += source + alias + "," q += source + alias + ","
} }
@ -993,23 +991,23 @@ func (a *MssqlAdapter) simpleJoin(name string, ins DBInsert, sel DBJoin, joinTyp
for _, loc := range processWhere(sel.Where) { for _, loc := range processWhere(sel.Where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "substitute": case TokenSub:
substituteCount++ subCount++
q += " ?" + strconv.Itoa(substituteCount) q += " ?" + strconv.Itoa(subCount)
case "function", "operator", "number", "or": case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up // TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
halves := strings.Split(token.Contents, ".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
q += " [" + halves[0] + "].[" + halves[1] + "]" q += " [" + halves[0] + "].[" + halves[1] + "]"
} else { } else {
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
} }
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -1040,8 +1038,8 @@ func (a *MssqlAdapter) simpleJoin(name string, ins DBInsert, sel DBJoin, joinTyp
limiter := processLimit(sel.Limit) limiter := processLimit(sel.Limit)
if limiter.Offset != "" { if limiter.Offset != "" {
if limiter.Offset == "?" { if limiter.Offset == "?" {
substituteCount++ subCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS" q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else { } else {
q += " OFFSET " + limiter.Offset + " ROWS" q += " OFFSET " + limiter.Offset + " ROWS"
} }
@ -1050,8 +1048,8 @@ func (a *MssqlAdapter) simpleJoin(name string, ins DBInsert, sel DBJoin, joinTyp
// ! Does this work without an offset? // ! Does this work without an offset?
if limiter.MaxCount != "" { if limiter.MaxCount != "" {
if limiter.MaxCount == "?" { if limiter.MaxCount == "?" {
substituteCount++ subCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount) limiter.MaxCount = "?" + strconv.Itoa(subCount)
} }
q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY "
} }
@ -1074,7 +1072,7 @@ func (a *MssqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJo
return a.simpleJoin(name, ins, sel, "INNER") return a.simpleJoin(name, ins, sel, "INNER")
} }
func (a *MssqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) { func (a *MssqlAdapter) SimpleCount(name, table, where, limit string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -1086,14 +1084,14 @@ func (a *MssqlAdapter) SimpleCount(name string, table string, where string, limi
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()" token.Contents = "GETUTCDATE()"
} }
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " [" + token.Contents + "]" q += " [" + token.Contents + "]"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")

View File

@ -27,20 +27,20 @@ type MysqlAdapter struct {
} }
// GetName gives you the name of the database adapter. In this case, it's mysql // GetName gives you the name of the database adapter. In this case, it's mysql
func (adapter *MysqlAdapter) GetName() string { func (a *MysqlAdapter) GetName() string {
return adapter.Name return a.Name
} }
func (adapter *MysqlAdapter) GetStmt(name string) DBStmt { func (a *MysqlAdapter) GetStmt(name string) DBStmt {
return adapter.Buffer[name] return a.Buffer[name]
} }
func (adapter *MysqlAdapter) GetStmts() map[string]DBStmt { func (a *MysqlAdapter) GetStmts() map[string]DBStmt {
return adapter.Buffer return a.Buffer
} }
// TODO: Add an option to disable unix pipes // TODO: Add an option to disable unix pipes
func (adapter *MysqlAdapter) BuildConn(config map[string]string) (*sql.DB, error) { func (a *MysqlAdapter) BuildConn(config map[string]string) (*sql.DB, error) {
dbCollation, ok := config["collation"] dbCollation, ok := config["collation"]
if !ok { if !ok {
return nil, ErrNoCollation return nil, ErrNoCollation
@ -78,21 +78,21 @@ func (adapter *MysqlAdapter) BuildConn(config map[string]string) (*sql.DB, error
return db, db.Ping() return db, db.Ping()
} }
func (adapter *MysqlAdapter) DbVersion() string { func (a *MysqlAdapter) DbVersion() string {
return "SELECT VERSION()" return "SELECT VERSION()"
} }
func (adapter *MysqlAdapter) DropTable(name string, table string) (string, error) { func (a *MysqlAdapter) DropTable(name string, table string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
querystr := "DROP TABLE IF EXISTS `" + table + "`;" q := "DROP TABLE IF EXISTS `" + table + "`;"
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "drop-table", querystr) a.pushStatement(name, "drop-table", q)
return querystr, nil return q, nil
} }
func (adapter *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) { func (a *MysqlAdapter) CreateTable(name string, table string, charset string, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -100,49 +100,50 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri
return "", errors.New("You can't have a table with no columns") return "", errors.New("You can't have a table with no columns")
} }
var querystr = "CREATE TABLE `" + table + "` (" q := "CREATE TABLE `" + table + "` ("
for _, column := range columns { for _, column := range columns {
column, size, end := adapter.parseColumn(column) column, size, end := a.parseColumn(column)
querystr += "\n\t`" + column.Name + "` " + column.Type + size + end + "," q += "\n\t`" + column.Name + "` " + column.Type + size + end + ","
} }
if len(keys) > 0 { if len(keys) > 0 {
for _, key := range keys { for _, key := range keys {
querystr += "\n\t" + key.Type q += "\n\t" + key.Type
if key.Type != "unique" { if key.Type != "unique" {
querystr += " key" q += " key"
} }
if key.Type == "foreign" { if key.Type == "foreign" {
cols := strings.Split(key.Columns, ",") cols := strings.Split(key.Columns, ",")
querystr += "(`" + cols[0] + "`) REFERENCES `" + key.FTable + "`(`" + cols[1] + "`)" q += "(`" + cols[0] + "`) REFERENCES `" + key.FTable + "`(`" + cols[1] + "`)"
if key.Cascade { if key.Cascade {
querystr += " ON DELETE CASCADE" q += " ON DELETE CASCADE"
} }
querystr += "," q += ","
} else { } else {
querystr += "(" q += "("
for _, column := range strings.Split(key.Columns, ",") { for _, column := range strings.Split(key.Columns, ",") {
querystr += "`" + column + "`," q += "`" + column + "`,"
} }
querystr = querystr[0:len(querystr)-1] + ")," q = q[0:len(q)-1] + "),"
} }
} }
} }
querystr = querystr[0:len(querystr)-1] + "\n)" q = q[0:len(q)-1] + "\n)"
if charset != "" { if charset != "" {
querystr += " CHARSET=" + charset q += " CHARSET=" + charset
} }
if collation != "" { if collation != "" {
querystr += " COLLATE " + collation q += " COLLATE " + collation
} }
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "create-table", querystr+";") q += ";"
return querystr + ";", nil a.pushStatement(name, "create-table", q)
return q, nil
} }
func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, size string, end string) { func (a *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, size string, end string) {
// Make it easier to support Cassandra in the future // Make it easier to support Cassandra in the future
if column.Type == "createdAt" { if column.Type == "createdAt" {
column.Type = "datetime" column.Type = "datetime"
@ -162,7 +163,7 @@ func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum
end = " DEFAULT " end = " DEFAULT "
/*if column.Type == "datetime" && column.Default[len(column.Default)-1] == ')' { /*if column.Type == "datetime" && column.Default[len(column.Default)-1] == ')' {
end += column.Default end += column.Default
} else */if adapter.stringyType(column.Type) && column.Default != "''" { } else */if a.stringyType(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'" end += "'" + column.Default + "'"
} else { } else {
end += column.Default end += column.Default
@ -188,20 +189,20 @@ func (a *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn
} }
column, size, end := a.parseColumn(column) column, size, end := a.parseColumn(column)
querystr := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end q := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + column.Name + "` " + column.Type + size + end
if key != nil { if key != nil {
querystr += " " + key.Type q += " " + key.Type
if key.Type != "unique" { if key.Type != "unique" {
querystr += " key" q += " key"
} else if key.Type == "primary" { } else if key.Type == "primary" {
querystr += " first" q += " first"
} }
} }
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
a.pushStatement(name, "add-column", querystr) a.pushStatement(name, "add-column", q)
return querystr, nil return q, nil
} }
// TODO: Test to make sure everything works here // TODO: Test to make sure everything works here
@ -216,10 +217,10 @@ func (a *MysqlAdapter) AddIndex(name string, table string, iname string, colname
return "", errors.New("You need a name for the column") return "", errors.New("You need a name for the column")
} }
querystr := "ALTER TABLE `" + table + "` ADD INDEX " + "`i_" + iname + "` (`" + colname + "`);" q := "ALTER TABLE `" + table + "` ADD INDEX " + "`i_" + iname + "` (`" + colname + "`);"
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
a.pushStatement(name, "add-index", querystr) a.pushStatement(name, "add-index", q)
return querystr, nil return q, nil
} }
// TODO: Test to make sure everything works here // TODO: Test to make sure everything works here
@ -228,45 +229,46 @@ func (a *MysqlAdapter) AddKey(name string, table string, column string, key DBTa
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
var querystr string var q string
if key.Type == "fulltext" { if key.Type == "fulltext" {
querystr = "ALTER TABLE `" + table + "` ADD FULLTEXT(`" + column + "`)" q = "ALTER TABLE `" + table + "` ADD FULLTEXT(`" + column + "`)"
} else { } else {
return "", errors.New("Only fulltext is supported by AddKey right now") return "", errors.New("Only fulltext is supported by AddKey right now")
} }
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
a.pushStatement(name, "add-key", querystr) a.pushStatement(name, "add-key", q)
return querystr, nil return q, nil
} }
func (a *MysqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) { func (a *MysqlAdapter) AddForeignKey(name string, table string, column string, ftable string, fcolumn string, cascade bool) (out string, e error) {
var c = func(str string, val bool) { c := func(str string, val bool) {
if e != nil || !val { if e != nil || !val {
return return
} }
e = errors.New("You need a "+str+" for this table") e = errors.New("You need a " + str + " for this table")
} }
c("name",table=="") c("name", table == "")
c("column",column=="") c("column", column == "")
c("ftable",ftable=="") c("ftable", ftable == "")
c("fcolumn",fcolumn=="") c("fcolumn", fcolumn == "")
if e != nil { if e != nil {
return "", e return "", e
} }
querystr := "ALTER TABLE `"+table+"` ADD CONSTRAINT `fk_"+column+"` FOREIGN KEY(`"+column+"`) REFERENCES `"+ftable+"`(`"+fcolumn+"`)" q := "ALTER TABLE `" + table + "` ADD CONSTRAINT `fk_" + column + "` FOREIGN KEY(`" + column + "`) REFERENCES `" + ftable + "`(`" + fcolumn + "`)"
if cascade { if cascade {
querystr += " ON DELETE CASCADE" q += " ON DELETE CASCADE"
} }
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
a.pushStatement(name, "add-foreign-key", querystr) a.pushStatement(name, "add-foreign-key", q)
return querystr, nil return q, nil
} }
var silen1 = len("INSERT INTO ``() VALUES () ") var silen1 = len("INSERT INTO ``() VALUES () ")
func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
func (a *MysqlAdapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -277,7 +279,7 @@ func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns str
sb.WriteString(table) sb.WriteString(table)
sb.WriteString("`(") sb.WriteString("`(")
if columns != "" { if columns != "" {
sb.WriteString(adapter.buildColumns(columns)) sb.WriteString(a.buildColumns(columns))
sb.WriteString(") VALUES (") sb.WriteString(") VALUES (")
fs := processFields(fields) fs := processFields(fields)
sb.Grow(len(fs) * 3) sb.Grow(len(fs) * 3)
@ -301,27 +303,27 @@ func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns str
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
q := sb.String() q := sb.String()
adapter.pushStatement(name, "insert", q) a.pushStatement(name, "insert", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) buildColumns(columns string) (querystr string) { func (a *MysqlAdapter) buildColumns(columns string) (q string) {
if columns == "" { if columns == "" {
return "" return ""
} }
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(columns) { for _, col := range processColumns(columns) {
if column.Type == "function" { if col.Type == TokenFunc {
querystr += column.Left + "," q += col.Left + ","
} else { } else {
querystr += "`" + column.Left + "`," q += "`" + col.Left + "`,"
} }
} }
return querystr[0 : len(querystr)-1] return q[0 : len(q)-1]
} }
// ! DEPRECATED // ! DEPRECATED
func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) { func (a *MysqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -332,18 +334,18 @@ func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns st
return "", errors.New("No input data found for SimpleInsert") return "", errors.New("No input data found for SimpleInsert")
} }
var querystr = "REPLACE INTO `" + table + "`(" + adapter.buildColumns(columns) + ") VALUES (" q := "REPLACE INTO `" + table + "`(" + a.buildColumns(columns) + ") VALUES ("
for _, field := range processFields(fields) { for _, field := range processFields(fields) {
querystr += field.Name + "," q += field.Name + ","
} }
querystr = querystr[0 : len(querystr)-1] q = q[0 : len(q)-1]
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "replace", querystr+")") a.pushStatement(name, "replace", q+")")
return querystr + ")", nil return q + ")", nil
} }
func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) { func (a *MysqlAdapter) SimpleUpsert(name string, table string, columns string, fields string, where string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -357,23 +359,23 @@ func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns str
return "", errors.New("You need a where for this upsert") return "", errors.New("You need a where for this upsert")
} }
var querystr = "INSERT INTO `" + table + "`(" q := "INSERT INTO `" + table + "`("
var parsedFields = processFields(fields) parsedFields := processFields(fields)
var insertColumns string var insertColumns string
var insertValues string var insertValues string
var setBit = ") ON DUPLICATE KEY UPDATE " setBit := ") ON DUPLICATE KEY UPDATE "
for columnID, column := range processColumns(columns) { for columnID, col := range processColumns(columns) {
field := parsedFields[columnID] field := parsedFields[columnID]
if column.Type == "function" { if col.Type == TokenFunc {
insertColumns += column.Left + "," insertColumns += col.Left + ","
insertValues += field.Name + "," insertValues += field.Name + ","
setBit += column.Left + " = " + field.Name + " AND " setBit += col.Left + " = " + field.Name + " AND "
} else { } else {
insertColumns += "`" + column.Left + "`," insertColumns += "`" + col.Left + "`,"
insertValues += field.Name + "," insertValues += field.Name + ","
setBit += "`" + column.Left + "` = " + field.Name + " AND " setBit += "`" + col.Left + "` = " + field.Name + " AND "
} }
} }
insertColumns = insertColumns[0 : len(insertColumns)-1] insertColumns = insertColumns[0 : len(insertColumns)-1]
@ -381,15 +383,16 @@ func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns str
insertColumns += ") VALUES (" + insertValues insertColumns += ") VALUES (" + insertValues
setBit = setBit[0 : len(setBit)-5] setBit = setBit[0 : len(setBit)-5]
querystr += insertColumns + setBit q += insertColumns + setBit
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "upsert", querystr) a.pushStatement(name, "upsert", q)
return querystr, nil return q, nil
} }
var sulen1 = len("UPDATE `` SET ") var sulen1 = len("UPDATE `` SET ")
func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
func (a *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
if up.table == "" { if up.table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -411,17 +414,17 @@ func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error)
sb.WriteString("`") sb.WriteString("`")
} }
sb.WriteString(item.Column) sb.WriteString(item.Column)
sb.WriteString("` =") sb.WriteString("`=")
for _, token := range item.Expr { for _, token := range item.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr:
sb.WriteString(" ") sb.WriteString(" ")
sb.WriteString(token.Contents) sb.WriteString(token.Contents)
case "column": case TokenColumn:
sb.WriteString(" `") sb.WriteString(" `")
sb.WriteString(token.Contents) sb.WriteString(token.Contents)
sb.WriteString("`") sb.WriteString("`")
case "string": case TokenString:
sb.WriteString(" '") sb.WriteString(" '")
sb.WriteString(token.Contents) sb.WriteString(token.Contents)
sb.WriteString("'") sb.WriteString("'")
@ -429,7 +432,7 @@ func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error)
} }
} }
whereStr, err := adapter.buildFlexiWhere(up.where,up.dateCutoff) whereStr, err := a.buildFlexiWhere(up.where, up.dateCutoff)
sb.WriteString(whereStr) sb.WriteString(whereStr)
if err != nil { if err != nil {
return sb.String(), err return sb.String(), err
@ -437,29 +440,28 @@ func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error)
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
q := sb.String() q := sb.String()
adapter.pushStatement(up.name, "update", q) a.pushStatement(up.name, "update", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) SimpleDelete(name string, table string, where string) (string, error) { func (a *MysqlAdapter) SimpleDelete(name string, table string, where string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
if where == "" { if where == "" {
return "", errors.New("You need to specify what data you want to delete") return "", errors.New("You need to specify what data you want to delete")
} }
q := "DELETE FROM `" + table + "` WHERE"
var q = "DELETE FROM `" + table + "` WHERE"
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot:
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " `" + token.Contents + "`" q += " `" + token.Contents + "`"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")
@ -470,41 +472,41 @@ func (adapter *MysqlAdapter) SimpleDelete(name string, table string, where strin
q = strings.TrimSpace(q[0 : len(q)-4]) q = strings.TrimSpace(q[0 : len(q)-4])
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(name, "delete", q) a.pushStatement(name, "delete", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) { func (a *MysqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) {
if b.table == "" { if b.table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
if b.where == "" && b.dateCutoff == nil { if b.where == "" && b.dateCutoff == nil {
return "", errors.New("You need to specify what data you want to delete") return "", errors.New("You need to specify what data you want to delete")
} }
var q = "DELETE FROM `" + b.table + "`" q := "DELETE FROM `" + b.table + "`"
whereStr, err := adapter.buildFlexiWhere(b.where, b.dateCutoff) whereStr, err := a.buildFlexiWhere(b.where, b.dateCutoff)
if err != nil { if err != nil {
return q, err return q, err
} }
q += whereStr q += whereStr
// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator // TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
adapter.pushStatement(b.name, "delete", q) a.pushStatement(b.name, "delete", q)
return q, nil return q, nil
} }
// We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead // We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
func (adapter *MysqlAdapter) Purge(name string, table string) (string, error) { func (a *MysqlAdapter) Purge(name string, table string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
q := "DELETE FROM `"+table+"`" q := "DELETE FROM `" + table + "`"
adapter.pushStatement(name, "purge", q) a.pushStatement(name, "purge", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) buildWhere(where string) (q string, err error) { func (a *MysqlAdapter) buildWhere(where string) (q string, err error) {
if len(where) == 0 { if len(where) == 0 {
return "", nil return "", nil
} }
@ -512,11 +514,11 @@ func (adapter *MysqlAdapter) buildWhere(where string) (q string, err error) {
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " `" + token.Contents + "`" q += " `" + token.Contents + "`"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
return q, errors.New("This token doesn't exist o_o") return q, errors.New("This token doesn't exist o_o")
@ -528,11 +530,10 @@ func (adapter *MysqlAdapter) buildWhere(where string) (q string, err error) {
} }
// The new version of buildWhere() currently only used in ComplexSelect for complex OO builder queries // The new version of buildWhere() currently only used in ComplexSelect for complex OO builder queries
func (adapter *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutoff) (q string, err error) { func (a *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutoff) (q string, err error) {
if len(where) == 0 && dateCutoff == nil { if len(where) == 0 && dateCutoff == nil {
return "", nil return "", nil
} }
q = " WHERE" q = " WHERE"
if dateCutoff != nil { if dateCutoff != nil {
if dateCutoff.Type == 0 { if dateCutoff.Type == 0 {
@ -546,11 +547,11 @@ func (adapter *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutof
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " `" + token.Contents + "`" q += " `" + token.Contents + "`"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
return q, errors.New("This token doesn't exist o_o") return q, errors.New("This token doesn't exist o_o")
@ -563,7 +564,7 @@ func (adapter *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutof
return q[0 : len(q)-4], nil return q[0 : len(q)-4], nil
} }
func (adapter *MysqlAdapter) buildOrderby(orderby string) (q string) { func (a *MysqlAdapter) buildOrderby(orderby string) (q string) {
if len(orderby) != 0 { if len(orderby) != 0 {
q = " ORDER BY " q = " ORDER BY "
for _, column := range processOrderby(orderby) { for _, column := range processOrderby(orderby) {
@ -575,14 +576,14 @@ func (adapter *MysqlAdapter) buildOrderby(orderby string) (q string) {
return q return q
} }
func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) { func (a *MysqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
if len(columns) == 0 { if len(columns) == 0 {
return "", errors.New("No columns found for SimpleSelect") return "", errors.New("No columns found for SimpleSelect")
} }
var q = "SELECT " q := "SELECT "
// Slice up the user friendly strings into something easier to process // Slice up the user friendly strings into something easier to process
for _, column := range strings.Split(strings.TrimSpace(columns), ",") { for _, column := range strings.Split(strings.TrimSpace(columns), ",") {
@ -590,20 +591,20 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str
} }
q = q[0 : len(q)-1] q = q[0 : len(q)-1]
whereStr, err := adapter.buildWhere(where) whereStr, err := a.buildWhere(where)
if err != nil { if err != nil {
return q, err return q, err
} }
q += " FROM `" + table + "`" + whereStr + adapter.buildOrderby(orderby) + adapter.buildLimit(limit) q += " FROM `" + table + "`" + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "select", q) a.pushStatement(name, "select", q)
return q, nil return q, nil
} }
func (a *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string, err error) { func (a *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string, err error) {
sb := &strings.Builder{} sb := &strings.Builder{}
err = a.complexSelect(preBuilder,sb) err = a.complexSelect(preBuilder, sb)
out = sb.String() out = sb.String()
a.pushStatement(preBuilder.name, "select", out) a.pushStatement(preBuilder.name, "select", out)
return out, err return out, err
@ -611,6 +612,7 @@ func (a *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string,
var cslen1 = len("SELECT FROM ``") var cslen1 = len("SELECT FROM ``")
var cslen2 = len(" WHERE `` IN(") var cslen2 = len(" WHERE `` IN(")
func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.Builder) error { func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.Builder) error {
if preBuilder.table == "" { if preBuilder.table == "" {
return errors.New("You need a name for this table") return errors.New("You need a name for this table")
@ -633,7 +635,7 @@ func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.B
sb.WriteString(" WHERE `") sb.WriteString(" WHERE `")
sb.WriteString(preBuilder.inColumn) sb.WriteString(preBuilder.inColumn)
sb.WriteString("` IN(") sb.WriteString("` IN(")
err := a.complexSelect(preBuilder.inChain,sb) err := a.complexSelect(preBuilder.inChain, sb)
if err != nil { if err != nil {
return err return err
} }
@ -673,18 +675,18 @@ func (a *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
return "", err return "", err
} }
thalf1 := strings.Split(strings.Replace(table1," as ", " AS ",-1)," AS ") thalf1 := strings.Split(strings.Replace(table1, " as ", " AS ", -1), " AS ")
var as1 string var as1 string
if len(thalf1) == 2 { if len(thalf1) == 2 {
as1 = " AS `"+ thalf1[1]+"`" as1 = " AS `" + thalf1[1] + "`"
} }
thalf2 := strings.Split(strings.Replace(table2," as ", " AS ",-1)," AS ") thalf2 := strings.Split(strings.Replace(table2, " as ", " AS ", -1), " AS ")
var as2 string var as2 string
if len(thalf2) == 2 { if len(thalf2) == 2 {
as2 = " AS `"+ thalf2[1]+"`" as2 = " AS `" + thalf2[1] + "`"
} }
q := "SELECT" + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`"+as1+" LEFT JOIN `" + thalf2[0] + "`"+as2+" ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit) q := "SELECT" + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`" + as1 + " LEFT JOIN `" + thalf2[0] + "`" + as2 + " ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
a.pushStatement(name, "select", q) a.pushStatement(name, "select", q)
@ -710,107 +712,104 @@ func (a *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
return "", err return "", err
} }
thalf1 := strings.Split(strings.Replace(table1," as ", " AS ",-1)," AS ") thalf1 := strings.Split(strings.Replace(table1, " as ", " AS ", -1), " AS ")
var as1 string var as1 string
if len(thalf1) == 2 { if len(thalf1) == 2 {
as1 = " AS `"+ thalf1[1]+"`" as1 = " AS `" + thalf1[1] + "`"
} }
thalf2 := strings.Split(strings.Replace(table2," as ", " AS ",-1)," AS ") thalf2 := strings.Split(strings.Replace(table2, " as ", " AS ", -1), " AS ")
var as2 string var as2 string
if len(thalf2) == 2 { if len(thalf2) == 2 {
as2 = " AS `"+ thalf2[1]+"`" as2 = " AS `" + thalf2[1] + "`"
} }
q := "SELECT " + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`"+as1+" INNER JOIN `" + thalf2[0] + "`"+as2+" ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit) q := "SELECT " + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`" + as1 + " INNER JOIN `" + thalf2[0] + "`" + as2 + " ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
a.pushStatement(name, "select", q) a.pushStatement(name, "select", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) { func (a *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
sel := up.whereSubQuery sel := up.whereSubQuery
whereStr, err := adapter.buildWhere(sel.where) whereStr, err := a.buildWhere(sel.where)
if err != nil { if err != nil {
return "", err return "", err
} }
var setter string var setter string
for _, item := range processSet(up.set) { for _, item := range processSet(up.set) {
setter += "`" + item.Column + "` =" setter += "`" + item.Column + "`="
for _, token := range item.Expr { for _, token := range item.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr:
setter += " " + token.Contents setter += token.Contents
case "column": case TokenColumn:
setter += " `" + token.Contents + "`" setter += "`" + token.Contents + "`"
case "string": case TokenString:
setter += " '" + token.Contents + "'" setter += "'" + token.Contents + "'"
} }
} }
setter += "," setter += ","
} }
setter = setter[0 : len(setter)-1] setter = setter[0 : len(setter)-1]
var querystr = "UPDATE `" + up.table + "` SET " + setter + " WHERE (SELECT" + adapter.buildJoinColumns(sel.columns) + " FROM `" + sel.table + "`" + whereStr + adapter.buildOrderby(sel.orderby) + adapter.buildLimit(sel.limit) + ")" q := "UPDATE `" + up.table + "` SET " + setter + " WHERE (SELECT" + a.buildJoinColumns(sel.columns) + " FROM `" + sel.table + "`" + whereStr + a.buildOrderby(sel.orderby) + a.buildLimit(sel.limit) + ")"
querystr = strings.TrimSpace(querystr)
adapter.pushStatement(up.name, "update", querystr)
return querystr, nil
}
func (adapter *MysqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) {
whereStr, err := adapter.buildWhere(sel.Where)
if err != nil {
return "", err
}
var q = "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table + "`" + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "insert", q) a.pushStatement(up.name, "update", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DBInsert, sel DBJoin) (string, error) { func (a *MysqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) {
whereStr, err := adapter.buildJoinWhere(sel.Where) whereStr, err := a.buildWhere(sel.Where)
if err != nil { if err != nil {
return "", err return "", err
} }
q := "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) q := "INSERT INTO `" + ins.Table + "`(" + a.buildColumns(ins.Columns) + ") SELECT" + a.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table + "`" + whereStr + a.buildOrderby(sel.Orderby) + a.buildLimit(sel.Limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "insert", q) a.pushStatement(name, "insert", q)
return q, nil
}
func (a *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
whereStr, err := a.buildJoinWhere(sel.Where)
if err != nil {
return "", err
}
q := "INSERT INTO `" + ins.Table + "`(" + a.buildColumns(ins.Columns) + ") SELECT" + a.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " + a.buildJoiners(sel.Joiners) + whereStr + a.buildOrderby(sel.Orderby) + a.buildLimit(sel.Limit)
q = strings.TrimSpace(q)
a.pushStatement(name, "insert", q)
return q, nil return q, nil
} }
// TODO: Make this more consistent with the other build* methods? // TODO: Make this more consistent with the other build* methods?
func (adapter *MysqlAdapter) buildJoiners(joiners string) (q string) { func (a *MysqlAdapter) buildJoiners(joiners string) (q string) {
for _, joiner := range processJoiner(joiners) { for _, j := range processJoiner(joiners) {
q += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " q += "`" + j.LeftTable + "`.`" + j.LeftColumn + "` " + j.Operator + " `" + j.RightTable + "`.`" + j.RightColumn + "` AND "
} }
// Remove the trailing AND // Remove the trailing AND
return q[0 : len(q)-4] return q[0 : len(q)-4]
} }
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
func (adapter *MysqlAdapter) buildJoinWhere(where string) (q string, err error) { func (a *MysqlAdapter) buildJoinWhere(where string) (q string, err error) {
if len(where) != 0 { if len(where) != 0 {
q = " WHERE" q = " WHERE"
for _, loc := range processWhere(where) { for _, loc := range processWhere(where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function", "operator", "number", "substitute", "or": case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
halves := strings.Split(token.Contents, ".") halves := strings.Split(token.Contents, ".")
if len(halves) == 2 { if len(halves) == 2 {
q += " `" + halves[0] + "`.`" + halves[1] + "`" q += " `" + halves[0] + "`.`" + halves[1] + "`"
} else { } else {
q += " `" + token.Contents + "`" q += " `" + token.Contents + "`"
} }
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
return q, errors.New("This token doesn't exist o_o") return q, errors.New("This token doesn't exist o_o")
@ -823,74 +822,73 @@ func (adapter *MysqlAdapter) buildJoinWhere(where string) (q string, err error)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) buildLimit(limit string) (q string) { func (a *MysqlAdapter) buildLimit(limit string) (q string) {
if limit != "" { if limit != "" {
q = " LIMIT " + limit q = " LIMIT " + limit
} }
return q return q
} }
func (a *MysqlAdapter) buildJoinColumns(columns string) (q string) { func (a *MysqlAdapter) buildJoinColumns(cols string) (q string) {
for _, column := range processColumns(columns) { for _, col := range processColumns(cols) {
// TODO: Move the stirng and number logic to processColumns? // TODO: Move the stirng and number logic to processColumns?
// TODO: Error if [0] doesn't exist // TODO: Error if [0] doesn't exist
firstChar := column.Left[0] firstChar := col.Left[0]
if firstChar == '\'' { if firstChar == '\'' {
column.Type = "string" col.Type = TokenString
} else { } else {
_, err := strconv.Atoi(string(firstChar)) _, err := strconv.Atoi(string(firstChar))
if err == nil { if err == nil {
column.Type = "number" col.Type = TokenNumber
} }
} }
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
var source = column.Left source := col.Left
if column.Table != "" { if col.Table != "" {
source = "`" + column.Table + "`.`" + source + "`" source = "`" + col.Table + "`.`" + source + "`"
} else if column.Type != "function" && column.Type != "number" && column.Type != "substitute" && column.Type != "string" { } else if col.Type != TokenFunc && col.Type != TokenNumber && col.Type != TokenSub && col.Type != TokenString {
source = "`" + source + "`" source = "`" + source + "`"
} }
var alias string var alias string
if column.Alias != "" { if col.Alias != "" {
alias = " AS `" + column.Alias + "`" alias = " AS `" + col.Alias + "`"
} }
q += " " + source + alias + "," q += " " + source + alias + ","
} }
return q[0 : len(q)-1] return q[0 : len(q)-1]
} }
func (adapter *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJoin) (string, error) { func (a *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
whereStr, err := adapter.buildJoinWhere(sel.Where) whereStr, err := a.buildJoinWhere(sel.Where)
if err != nil { if err != nil {
return "", err return "", err
} }
q := "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) q := "INSERT INTO `" + ins.Table + "`(" + a.buildColumns(ins.Columns) + ") SELECT" + a.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + a.buildJoiners(sel.Joiners) + whereStr + a.buildOrderby(sel.Orderby) + a.buildLimit(sel.Limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "insert", q) a.pushStatement(name, "insert", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) SimpleCount(name string, table string, where string, limit string) (q string, err error) { func (a *MysqlAdapter) SimpleCount(name string, table string, where string, limit string) (q string, err error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
whereStr, err := adapter.buildWhere(where) whereStr, err := a.buildWhere(where)
if err != nil { if err != nil {
return "", err return "", err
} }
q = "SELECT COUNT(*) FROM `" + table + "`" + whereStr + adapter.buildLimit(limit) q = "SELECT COUNT(*) FROM `" + table + "`" + whereStr + a.buildLimit(limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "select", q) a.pushStatement(name, "select", q)
return q, nil return q, nil
} }
func (adapter *MysqlAdapter) Builder() *prebuilder { func (a *MysqlAdapter) Builder() *prebuilder {
return &prebuilder{adapter} return &prebuilder{a}
} }
func (a *MysqlAdapter) Write() error { func (a *MysqlAdapter) Write() error {
@ -964,7 +962,7 @@ func (a *MysqlAdapter) pushStatement(name string, stype string, querystr string)
a.BufferOrder = append(a.BufferOrder, name) a.BufferOrder = append(a.BufferOrder, name)
} }
func (adapter *MysqlAdapter) stringyType(ctype string) bool { func (a *MysqlAdapter) stringyType(ctype string) bool {
ctype = strings.ToLower(ctype) ctype = strings.ToLower(ctype)
return ctype == "varchar" || ctype == "tinytext" || ctype == "text" || ctype == "mediumtext" || ctype == "longtext" || ctype == "char" || ctype == "datetime" || ctype == "timestamp" || ctype == "time" || ctype == "date" return ctype == "varchar" || ctype == "tinytext" || ctype == "text" || ctype == "mediumtext" || ctype == "longtext" || ctype == "char" || ctype == "datetime" || ctype == "timestamp" || ctype == "time" || ctype == "date"
} }

View File

@ -195,23 +195,23 @@ func (a *PgsqlAdapter) SimpleInsert(name string, table string, columns string, f
return q, nil return q, nil
} }
func (a *PgsqlAdapter) buildColumns(columns string) (q string) { func (a *PgsqlAdapter) buildColumns(cols string) (q string) {
if columns == "" { if cols == "" {
return "" return ""
} }
// Escape the column names, just in case we've used a reserved keyword // Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(columns) { for _, col := range processColumns(cols) {
if column.Type == "function" { if col.Type == TokenFunc {
q += column.Left + "," q += col.Left + ","
} else { } else {
q += "\"" + column.Left + "\"," q += "\"" + col.Left + "\","
} }
} }
return q[0 : len(q)-1] return q[0 : len(q)-1]
} }
// TODO: Implement this // TODO: Implement this
func (a *PgsqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) { func (a *PgsqlAdapter) SimpleReplace(name, table, columns, fields string) (string, error) {
if table == "" { if table == "" {
return "", errors.New("You need a name for this table") return "", errors.New("You need a name for this table")
} }
@ -249,26 +249,25 @@ func (a *PgsqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
q := "UPDATE \"" + up.table + "\" SET " q := "UPDATE \"" + up.table + "\" SET "
for _, item := range processSet(up.set) { for _, item := range processSet(up.set) {
q += "`" + item.Column + "` =" q += "`" + item.Column + "`="
for _, token := range item.Expr { for _, token := range item.Expr {
switch token.Type { switch token.Type {
case "function": case TokenFunc:
// TODO: Write a more sophisticated function parser on the utils side. // TODO: Write a more sophisticated function parser on the utils side.
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
q += " " + token.Contents q += " " + token.Contents
case "operator", "number", "substitute", "or": case TokenOp, TokenNumber, TokenSub, TokenOr:
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " `" + token.Contents + "`" q += " `" + token.Contents + "`"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
} }
} }
q += "," q += ","
} }
// Remove the trailing comma
q = q[0 : len(q)-1] q = q[0 : len(q)-1]
// Add support for BETWEEN x.x // Add support for BETWEEN x.x
@ -277,17 +276,17 @@ func (a *PgsqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
for _, loc := range processWhere(up.where) { for _, loc := range processWhere(up.where) {
for _, token := range loc.Expr { for _, token := range loc.Expr {
switch token.Type { switch token.Type {
case "function": case TokenFunc:
// TODO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity? // TODO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity?
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
q += " " + token.Contents q += " " + token.Contents
case "operator", "number", "substitute", "or": case TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents q += " " + token.Contents
case "column": case TokenColumn:
q += " `" + token.Contents + "`" q += " `" + token.Contents + "`"
case "string": case TokenString:
q += " '" + token.Contents + "'" q += " '" + token.Contents + "'"
default: default:
panic("This token doesn't exist o_o") panic("This token doesn't exist o_o")

View File

@ -25,7 +25,7 @@ type DBTableKey struct {
Type string Type string
// Foreign keys only // Foreign keys only
FTable string FTable string
Cascade bool Cascade bool
} }
@ -57,7 +57,8 @@ type DBColumn struct {
Table string Table string
Left string // Could be a function or a column, so I'm naming this Left Left string // Could be a function or a column, so I'm naming this Left
Alias string // aka AS Blah, if it's present Alias string // aka AS Blah, if it's present
Type string // function or column //Type string // function or column
Type int
} }
type DBField struct { type DBField struct {
@ -82,9 +83,22 @@ type DBOrder struct {
Order string Order string
} }
const (
TokenFunc = iota
TokenOp
TokenColumn
TokenNumber
TokenString
TokenSub
TokenOr
TokenNot
TokenLike
)
type DBToken struct { type DBToken struct {
Contents string Contents string
Type string // function, operator, column, number, string, substitute //Type string // function, op, column, number, string, sub, not, like
Type int
} }
type DBSetter struct { type DBSetter struct {

View File

@ -13,18 +13,18 @@ import (
) )
// TODO: Add support for numbers and strings? // TODO: Add support for numbers and strings?
func processColumns(colstr string) (columns []DBColumn) { func processColumns(colStr string) (columns []DBColumn) {
if colstr == "" { if colStr == "" {
return columns return columns
} }
colstr = strings.Replace(colstr, " as ", " AS ", -1) colStr = strings.Replace(colStr, " as ", " AS ", -1)
for _, segment := range strings.Split(colstr, ",") { for _, segment := range strings.Split(colStr, ",") {
var outcol DBColumn var outCol DBColumn
dotHalves := strings.Split(strings.TrimSpace(segment), ".") dotHalves := strings.Split(strings.TrimSpace(segment), ".")
var halves []string var halves []string
if len(dotHalves) == 2 { if len(dotHalves) == 2 {
outcol.Table = dotHalves[0] outCol.Table = dotHalves[0]
halves = strings.Split(dotHalves[1], " AS ") halves = strings.Split(dotHalves[1], " AS ")
} else { } else {
halves = strings.Split(dotHalves[0], " AS ") halves = strings.Split(dotHalves[0], " AS ")
@ -32,132 +32,132 @@ func processColumns(colstr string) (columns []DBColumn) {
halves[0] = strings.TrimSpace(halves[0]) halves[0] = strings.TrimSpace(halves[0])
if len(halves) == 2 { if len(halves) == 2 {
outcol.Alias = strings.TrimSpace(halves[1]) outCol.Alias = strings.TrimSpace(halves[1])
} }
if halves[0][len(halves[0])-1] == ')' { if halves[0][len(halves[0])-1] == ')' {
outcol.Type = "function" outCol.Type = TokenFunc
} else if halves[0] == "?" { } else if halves[0] == "?" {
outcol.Type = "substitute" outCol.Type = TokenSub
} else { } else {
outcol.Type = "column" outCol.Type = TokenColumn
} }
outcol.Left = halves[0] outCol.Left = halves[0]
columns = append(columns, outcol) columns = append(columns, outCol)
} }
return columns return columns
} }
// TODO: Allow order by statements without a direction // TODO: Allow order by statements without a direction
func processOrderby(orderstr string) (order []DBOrder) { func processOrderby(orderStr string) (order []DBOrder) {
if orderstr == "" { if orderStr == "" {
return order return order
} }
for _, segment := range strings.Split(orderstr, ",") { for _, segment := range strings.Split(orderStr, ",") {
var outorder DBOrder var outOrder DBOrder
halves := strings.Split(strings.TrimSpace(segment), " ") halves := strings.Split(strings.TrimSpace(segment), " ")
if len(halves) != 2 { if len(halves) != 2 {
continue continue
} }
outorder.Column = halves[0] outOrder.Column = halves[0]
outorder.Order = strings.ToLower(halves[1]) outOrder.Order = strings.ToLower(halves[1])
order = append(order, outorder) order = append(order, outOrder)
} }
return order return order
} }
func processJoiner(joinstr string) (joiner []DBJoiner) { func processJoiner(joinStr string) (joiner []DBJoiner) {
if joinstr == "" { if joinStr == "" {
return joiner return joiner
} }
joinstr = strings.Replace(joinstr, " on ", " ON ", -1) joinStr = strings.Replace(joinStr, " on ", " ON ", -1)
joinstr = strings.Replace(joinstr, " and ", " AND ", -1) joinStr = strings.Replace(joinStr, " and ", " AND ", -1)
for _, segment := range strings.Split(joinstr, " AND ") { for _, segment := range strings.Split(joinStr, " AND ") {
var outjoin DBJoiner var outJoin DBJoiner
var parseOffset int var parseOffset int
var left, right string var left, right string
left, parseOffset = getIdentifier(segment, parseOffset) left, parseOffset = getIdentifier(segment, parseOffset)
outjoin.Operator, parseOffset = getOperator(segment, parseOffset+1) outJoin.Operator, parseOffset = getOperator(segment, parseOffset+1)
right, parseOffset = getIdentifier(segment, parseOffset+1) right, parseOffset = getIdentifier(segment, parseOffset+1)
leftColumn := strings.Split(left, ".") leftColumn := strings.Split(left, ".")
rightColumn := strings.Split(right, ".") rightColumn := strings.Split(right, ".")
outjoin.LeftTable = strings.TrimSpace(leftColumn[0]) outJoin.LeftTable = strings.TrimSpace(leftColumn[0])
outjoin.RightTable = strings.TrimSpace(rightColumn[0]) outJoin.RightTable = strings.TrimSpace(rightColumn[0])
outjoin.LeftColumn = strings.TrimSpace(leftColumn[1]) outJoin.LeftColumn = strings.TrimSpace(leftColumn[1])
outjoin.RightColumn = strings.TrimSpace(rightColumn[1]) outJoin.RightColumn = strings.TrimSpace(rightColumn[1])
joiner = append(joiner, outjoin) joiner = append(joiner, outJoin)
} }
return joiner return joiner
} }
func (where *DBWhere) parseNumber(segment string, i int) int { func (wh *DBWhere) parseNumber(segment string, i int) int {
var buffer string var buffer string
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
if '0' <= char && char <= '9' { if '0' <= ch && ch <= '9' {
buffer += string(char) buffer += string(ch)
} else { } else {
i-- i--
where.Expr = append(where.Expr, DBToken{buffer, "number"}) wh.Expr = append(wh.Expr, DBToken{buffer, TokenNumber})
return i return i
} }
} }
return i return i
} }
func (where *DBWhere) parseColumn(segment string, i int) int { func (wh *DBWhere) parseColumn(segment string, i int) int {
var buffer string var buffer string
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
switch { switch {
case ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '.' || char == '_': case ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '.' || ch == '_':
buffer += string(char) buffer += string(ch)
case char == '(': case ch == '(':
return where.parseFunction(segment, buffer, i) return wh.parseFunction(segment, buffer, i)
default: default:
i-- i--
where.Expr = append(where.Expr, DBToken{buffer, "column"}) wh.Expr = append(wh.Expr, DBToken{buffer, TokenColumn})
return i return i
} }
} }
return i return i
} }
func (where *DBWhere) parseFunction(segment string, buffer string, i int) int { func (wh *DBWhere) parseFunction(segment string, buffer string, i int) int {
var preI = i preI := i
i = skipFunctionCall(segment, i-1) i = skipFunctionCall(segment, i-1)
buffer += segment[preI:i] + string(segment[i]) buffer += segment[preI:i] + string(segment[i])
where.Expr = append(where.Expr, DBToken{buffer, "function"}) wh.Expr = append(wh.Expr, DBToken{buffer, TokenFunc})
return i return i
} }
func (where *DBWhere) parseString(segment string, i int) int { func (wh *DBWhere) parseString(segment string, i int) int {
var buffer string var buffer string
i++ i++
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
if char != '\'' { if ch != '\'' {
buffer += string(char) buffer += string(ch)
} else { } else {
where.Expr = append(where.Expr, DBToken{buffer, "string"}) wh.Expr = append(wh.Expr, DBToken{buffer, TokenString})
return i return i
} }
} }
return i return i
} }
func (where *DBWhere) parseOperator(segment string, i int) int { func (wh *DBWhere) parseOperator(segment string, i int) int {
var buffer string var buffer string
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
if isOpByte(char) { if isOpByte(ch) {
buffer += string(char) buffer += string(ch)
} else { } else {
i-- i--
where.Expr = append(where.Expr, DBToken{buffer, "operator"}) wh.Expr = append(wh.Expr, DBToken{buffer, TokenOp})
return i return i
} }
} }
@ -175,35 +175,41 @@ func normalizeOr(in string) string {
} }
// TODO: Write tests for this // TODO: Write tests for this
func processWhere(wherestr string) (where []DBWhere) { func processWhere(whereStr string) (where []DBWhere) {
if wherestr == "" { if whereStr == "" {
return where return where
} }
wherestr = normalizeAnd(wherestr) whereStr = normalizeAnd(whereStr)
wherestr = normalizeOr(wherestr) whereStr = normalizeOr(whereStr)
for _, segment := range strings.Split(wherestr, " AND ") { for _, seg := range strings.Split(whereStr, " AND ") {
var tmpWhere = &DBWhere{[]DBToken{}} tmpWhere := &DBWhere{[]DBToken{}}
segment += ")" seg += ")"
for i := 0; i < len(segment); i++ { for i := 0; i < len(seg); i++ {
char := segment[i] ch := seg[i]
switch { switch {
case '0' <= char && char <= '9': case '0' <= ch && ch <= '9':
i = tmpWhere.parseNumber(segment, i) i = tmpWhere.parseNumber(seg, i)
// TODO: Sniff the third byte offset from char or it's non-existent to avoid matching uppercase strings which start with OR // TODO: Sniff the third byte offset from char or it's non-existent to avoid matching uppercase strings which start with OR
case char == 'O' && (i+1) < len(segment) && segment[i+1] == 'R': case ch == 'O' && (i+1) < len(seg) && seg[i+1] == 'R':
tmpWhere.Expr = append(tmpWhere.Expr, DBToken{"OR", "or"}) tmpWhere.Expr = append(tmpWhere.Expr, DBToken{"OR", TokenOr})
i += 1 i += 1
case ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_': case ch == 'N' && (i+2) < len(seg) && seg[i+1] == 'O' && seg[i+2] == 'T':
i = tmpWhere.parseColumn(segment, i) tmpWhere.Expr = append(tmpWhere.Expr, DBToken{"NOT", TokenNot})
case char == '\'': i += 2
i = tmpWhere.parseString(segment, i) case ch == 'L' && (i+3) < len(seg) && seg[i+1] == 'I' && seg[i+2] == 'K' && seg[i+3] == 'E':
case char == ')' && i < (len(segment)-1): tmpWhere.Expr = append(tmpWhere.Expr, DBToken{"LIKE", TokenLike})
tmpWhere.Expr = append(tmpWhere.Expr, DBToken{")", "operator"}) i += 3
case isOpByte(char): case ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_':
i = tmpWhere.parseOperator(segment, i) i = tmpWhere.parseColumn(seg, i)
case char == '?': case ch == '\'':
tmpWhere.Expr = append(tmpWhere.Expr, DBToken{"?", "substitute"}) i = tmpWhere.parseString(seg, i)
case ch == ')' && i < (len(seg)-1):
tmpWhere.Expr = append(tmpWhere.Expr, DBToken{")", TokenOp})
case isOpByte(ch):
i = tmpWhere.parseOperator(seg, i)
case ch == '?':
tmpWhere.Expr = append(tmpWhere.Expr, DBToken{"?", TokenSub})
} }
} }
where = append(where, *tmpWhere) where = append(where, *tmpWhere)
@ -211,47 +217,47 @@ func processWhere(wherestr string) (where []DBWhere) {
return where return where
} }
func (setter *DBSetter) parseNumber(segment string, i int) int { func (set *DBSetter) parseNumber(segment string, i int) int {
var buffer string var buffer string
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
if '0' <= char && char <= '9' { if '0' <= ch && ch <= '9' {
buffer += string(char) buffer += string(ch)
} else { } else {
setter.Expr = append(setter.Expr, DBToken{buffer, "number"}) set.Expr = append(set.Expr, DBToken{buffer, TokenNumber})
return i return i
} }
} }
return i return i
} }
func (setter *DBSetter) parseColumn(segment string, i int) int { func (set *DBSetter) parseColumn(segment string, i int) int {
var buffer string var buffer string
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
switch { switch {
case ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_': case ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_':
buffer += string(char) buffer += string(ch)
case char == '(': case ch == '(':
return setter.parseFunction(segment, buffer, i) return set.parseFunction(segment, buffer, i)
default: default:
i-- i--
setter.Expr = append(setter.Expr, DBToken{buffer, "column"}) set.Expr = append(set.Expr, DBToken{buffer, TokenColumn})
return i return i
} }
} }
return i return i
} }
func (setter *DBSetter) parseFunction(segment string, buffer string, i int) int { func (set *DBSetter) parseFunction(segment string, buffer string, i int) int {
var preI = i preI := i
i = skipFunctionCall(segment, i-1) i = skipFunctionCall(segment, i-1)
buffer += segment[preI:i] + string(segment[i]) buffer += segment[preI:i] + string(segment[i])
setter.Expr = append(setter.Expr, DBToken{buffer, "function"}) set.Expr = append(set.Expr, DBToken{buffer, TokenFunc})
return i return i
} }
func (setter *DBSetter) parseString(segment string, i int) int { func (set *DBSetter) parseString(segment string, i int) int {
var buffer string var buffer string
i++ i++
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
@ -259,22 +265,22 @@ func (setter *DBSetter) parseString(segment string, i int) int {
if char != '\'' { if char != '\'' {
buffer += string(char) buffer += string(char)
} else { } else {
setter.Expr = append(setter.Expr, DBToken{buffer, "string"}) set.Expr = append(set.Expr, DBToken{buffer, TokenString})
return i return i
} }
} }
return i return i
} }
func (setter *DBSetter) parseOperator(segment string, i int) int { func (set *DBSetter) parseOperator(segment string, i int) int {
var buffer string var buffer string
for ; i < len(segment); i++ { for ; i < len(segment); i++ {
char := segment[i] ch := segment[i]
if isOpByte(char) { if isOpByte(ch) {
buffer += string(char) buffer += string(ch)
} else { } else {
i-- i--
setter.Expr = append(setter.Expr, DBToken{buffer, "operator"}) set.Expr = append(set.Expr, DBToken{buffer, TokenOp})
return i return i
} }
} }
@ -316,18 +322,18 @@ func processSet(setstr string) (setter []DBSetter) {
segment := halves[1] + ")" segment := halves[1] + ")"
for i := 0; i < len(segment); i++ { for i := 0; i < len(segment); i++ {
char := segment[i] ch := segment[i]
switch { switch {
case '0' <= char && char <= '9': case '0' <= ch && ch <= '9':
i = tmpSetter.parseNumber(segment, i) i = tmpSetter.parseNumber(segment, i)
case ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_': case ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_':
i = tmpSetter.parseColumn(segment, i) i = tmpSetter.parseColumn(segment, i)
case char == '\'': case ch == '\'':
i = tmpSetter.parseString(segment, i) i = tmpSetter.parseString(segment, i)
case isOpByte(char): case isOpByte(ch):
i = tmpSetter.parseOperator(segment, i) i = tmpSetter.parseOperator(segment, i)
case char == '?': case ch == '?':
tmpSetter.Expr = append(tmpSetter.Expr, DBToken{"?", "substitute"}) tmpSetter.Expr = append(tmpSetter.Expr, DBToken{"?", TokenSub})
} }
} }
setter = append(setter, *tmpSetter) setter = append(setter, *tmpSetter)
@ -335,86 +341,88 @@ func processSet(setstr string) (setter []DBSetter) {
return setter return setter
} }
func processLimit(limitstr string) (limiter DBLimit) { func processLimit(limitStr string) (limit DBLimit) {
halves := strings.Split(limitstr, ",") halves := strings.Split(limitStr, ",")
if len(halves) == 2 { if len(halves) == 2 {
limiter.Offset = halves[0] limit.Offset = halves[0]
limiter.MaxCount = halves[1] limit.MaxCount = halves[1]
} else { } else {
limiter.MaxCount = halves[0] limit.MaxCount = halves[0]
} }
return limiter return limit
} }
func isOpByte(char byte) bool { func isOpByte(ch byte) bool {
return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' || char == '(' || char == ')' return ch == '<' || ch == '>' || ch == '=' || ch == '!' || ch == '*' || ch == '%' || ch == '+' || ch == '-' || ch == '/' || ch == '(' || ch == ')'
} }
func isOpRune(char rune) bool { func isOpRune(ch rune) bool {
return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' || char == '(' || char == ')' return ch == '<' || ch == '>' || ch == '=' || ch == '!' || ch == '*' || ch == '%' || ch == '+' || ch == '-' || ch == '/' || ch == '(' || ch == ')'
} }
func processFields(fieldstr string) (fields []DBField) { func processFields(fieldStr string) (fields []DBField) {
if fieldstr == "" { if fieldStr == "" {
return fields return fields
} }
var buffer string var buffer string
var lastItem int var lastItem int
fieldstr += "," fieldStr += ","
for i := 0; i < len(fieldstr); i++ { for i := 0; i < len(fieldStr); i++ {
if fieldstr[i] == '(' { ch := fieldStr[i]
i = skipFunctionCall(fieldstr, i-1) if ch == '(' {
fields = append(fields, DBField{Name: fieldstr[lastItem : i+1], Type: getIdentifierType(fieldstr[lastItem : i+1])}) i = skipFunctionCall(fieldStr, i-1)
fields = append(fields, DBField{Name: fieldStr[lastItem : i+1], Type: getIdentifierType(fieldStr[lastItem : i+1])})
buffer = "" buffer = ""
lastItem = i + 2 lastItem = i + 2
} else if fieldstr[i] == ',' && buffer != "" { } else if ch == ',' && buffer != "" {
fields = append(fields, DBField{Name: buffer, Type: getIdentifierType(buffer)}) fields = append(fields, DBField{Name: buffer, Type: getIdentifierType(buffer)})
buffer = "" buffer = ""
lastItem = i + 1 lastItem = i + 1
} else if (fieldstr[i] >= 32) && fieldstr[i] != ',' && fieldstr[i] != ')' { } else if (ch >= 32) && ch != ',' && ch != ')' {
buffer += string(fieldstr[i]) buffer += string(ch)
} }
} }
return fields return fields
} }
func getIdentifierType(identifier string) string { func getIdentifierType(iden string) string {
if ('a' <= identifier[0] && identifier[0] <= 'z') || ('A' <= identifier[0] && identifier[0] <= 'Z') { if ('a' <= iden[0] && iden[0] <= 'z') || ('A' <= iden[0] && iden[0] <= 'Z') {
if identifier[len(identifier)-1] == ')' { if iden[len(iden)-1] == ')' {
return "function" return "function"
} }
return "column" return "column"
} }
if identifier[0] == '\'' || identifier[0] == '"' { if iden[0] == '\'' || iden[0] == '"' {
return "string" return "string"
} }
return "literal" return "literal"
} }
func getIdentifier(segment string, startOffset int) (out string, i int) { func getIdentifier(seg string, startOffset int) (out string, i int) {
segment = strings.TrimSpace(segment) seg = strings.TrimSpace(seg)
segment += " " // Avoid overflow bugs with slicing seg += " " // Avoid overflow bugs with slicing
for i = startOffset; i < len(segment); i++ { for i = startOffset; i < len(seg); i++ {
if segment[i] == '(' { ch := seg[i]
i = skipFunctionCall(segment, i) if ch == '(' {
return strings.TrimSpace(segment[startOffset:i]), (i - 1) i = skipFunctionCall(seg, i)
return strings.TrimSpace(seg[startOffset:i]), (i - 1)
} }
if (segment[i] == ' ' || isOpByte(segment[i])) && i != startOffset { if (ch == ' ' || isOpByte(ch)) && i != startOffset {
return strings.TrimSpace(segment[startOffset:i]), (i - 1) return strings.TrimSpace(seg[startOffset:i]), (i - 1)
} }
} }
return strings.TrimSpace(segment[startOffset:]), (i - 1) return strings.TrimSpace(seg[startOffset:]), (i - 1)
} }
func getOperator(segment string, startOffset int) (out string, i int) { func getOperator(seg string, startOffset int) (out string, i int) {
segment = strings.TrimSpace(segment) seg = strings.TrimSpace(seg)
segment += " " // Avoid overflow bugs with slicing seg += " " // Avoid overflow bugs with slicing
for i = startOffset; i < len(segment); i++ { for i = startOffset; i < len(seg); i++ {
if !isOpByte(segment[i]) && i != startOffset { if !isOpByte(seg[i]) && i != startOffset {
return strings.TrimSpace(segment[startOffset:i]), (i - 1) return strings.TrimSpace(seg[startOffset:i]), (i - 1)
} }
} }
return strings.TrimSpace(segment[startOffset:]), (i - 1) return strings.TrimSpace(seg[startOffset:]), (i - 1)
} }
func skipFunctionCall(data string, index int) int { func skipFunctionCall(data string, index int) int {

View File

@ -25,7 +25,7 @@ import (
// A blank list to fill out that parameter in Page for routes which don't use it // A blank list to fill out that parameter in Page for routes which don't use it
var tList []interface{} var tList []interface{}
var successJSONBytes = []byte(`{"success":"1"}`) var successJSONBytes = []byte(`{"success":1}`)
// TODO: Refactor this // TODO: Refactor this
// TODO: Use the phrase system // TODO: Use the phrase system
@ -115,8 +115,8 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var alert c.Alert var al c.Alert
err = rows.Scan(&alert.ASID, &alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID, &createdAt) err = rows.Scan(&al.ASID, &al.ActorID, &al.TargetUserID, &al.Event, &al.ElementType, &al.ElementID, &createdAt)
if err != nil { if err != nil {
return c.InternalErrorJS(err, w, r) return c.InternalErrorJS(err, w, r)
} }
@ -124,8 +124,8 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
uCreatedAt := createdAt.Unix() uCreatedAt := createdAt.Unix()
//log.Print("uCreatedAt", uCreatedAt) //log.Print("uCreatedAt", uCreatedAt)
//if rCreatedAt == 0 || rCreatedAt < uCreatedAt { //if rCreatedAt == 0 || rCreatedAt < uCreatedAt {
alerts = append(alerts, alert) alerts = append(alerts, al)
actors = append(actors, alert.ActorID) actors = append(actors, al.ActorID)
//} //}
if uCreatedAt > topCreatedAt { if uCreatedAt > topCreatedAt {
topCreatedAt = uCreatedAt topCreatedAt = uCreatedAt
@ -325,10 +325,10 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
func routeJSAntispam(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func routeJSAntispam(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
h := sha256.New() h := sha256.New()
h.Write([]byte(c.JSTokenBox.Load().(string))) h.Write([]byte(c.JSTokenBox.Load().(string)))
h.Write([]byte(user.LastIP)) h.Write([]byte(user.GetIP()))
jsToken := hex.EncodeToString(h.Sum(nil)) jsToken := hex.EncodeToString(h.Sum(nil))
var innerCode = "`document.getElementByld('golden-watch').value = '" + jsToken + "';`" innerCode := "`document.getElementByld('golden-watch').value = '" + jsToken + "';`"
io.WriteString(w, `let hihi = `+innerCode+`; io.WriteString(w, `let hihi = `+innerCode+`;
hihi = hihi.replace('ld','Id'); hihi = hihi.replace('ld','Id');
eval(hihi);`) eval(hihi);`)

View File

@ -36,11 +36,11 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
return c.LocalError("You're already logged in.", w, r, user) return c.LocalError("You're already logged in.", w, r, user)
} }
username := c.SanitiseSingleLine(r.PostFormValue("username")) name := c.SanitiseSingleLine(r.PostFormValue("username"))
uid, err, requiresExtraAuth := c.Auth.Authenticate(username, r.PostFormValue("password")) uid, err, requiresExtraAuth := c.Auth.Authenticate(name, r.PostFormValue("password"))
if err != nil { if err != nil {
// TODO: uid is currently set to 0 as authenticate fetches the user by username and password. Get the actual uid, so we can alert the user of attempted logins? What if someone takes advantage of the response times to deduce if an account exists? // TODO: uid is currently set to 0 as authenticate fetches the user by username and password. Get the actual uid, so we can alert the user of attempted logins? What if someone takes advantage of the response times to deduce if an account exists?
logItem := &c.LoginLogItem{UID: uid, Success: false, IP: user.LastIP} logItem := &c.LoginLogItem{UID: uid, Success: false, IP: user.GetIP()}
_, err := logItem.Create() _, err := logItem.Create()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
@ -49,7 +49,7 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
} }
// TODO: Take 2FA into account // TODO: Take 2FA into account
logItem := &c.LoginLogItem{UID: uid, Success: true, IP: user.LastIP} logItem := &c.LoginLogItem{UID: uid, Success: true, IP: user.GetIP()}
_, err = logItem.Create() _, err = logItem.Create()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
@ -91,7 +91,7 @@ func loginSuccess(uid int, w http.ResponseWriter, r *http.Request, user *c.User)
if user.IsAdmin { if user.IsAdmin {
// Is this error check redundant? We already check for the error in PreRoute for the same IP // Is this error check redundant? We already check for the error in PreRoute for the same IP
// TODO: Should we be logging this? // TODO: Should we be logging this?
log.Printf("#%d has logged in with IP %s", uid, user.LastIP) log.Printf("#%d has logged in with IP %s", uid, user.GetIP())
} }
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return nil return nil
@ -220,7 +220,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
if !c.Config.DisableJSAntispam { if !c.Config.DisableJSAntispam {
h := sha256.New() h := sha256.New()
h.Write([]byte(c.JSTokenBox.Load().(string))) h.Write([]byte(c.JSTokenBox.Load().(string)))
h.Write([]byte(user.LastIP)) h.Write([]byte(user.GetIP()))
if r.PostFormValue("golden-watch") != hex.EncodeToString(h.Sum(nil)) { if r.PostFormValue("golden-watch") != hex.EncodeToString(h.Sum(nil)) {
regError(p.GetErrorPhrase("register_might_be_machine"), "js-antispam") regError(p.GetErrorPhrase("register_might_be_machine"), "js-antispam")
} }
@ -261,7 +261,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
} }
} }
regLog := c.RegLogItem{Username: name, Email: email, FailureReason: regErrReason, Success: regSuccess, IP: user.LastIP} regLog := c.RegLogItem{Username: name, Email: email, FailureReason: regErrReason, Success: regSuccess, IP: user.GetIP()}
_, err = regLog.Create() _, err = regLog.Create()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)

View File

@ -42,7 +42,7 @@ func Backups(w http.ResponseWriter, r *http.Request, user c.User, backupURL stri
} }
// TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
http.ServeFile(w, r, "./backups/"+backupURL) http.ServeFile(w, r, "./backups/"+backupURL)
err = c.AdminLogs.Create("download", 0, "backup", user.LastIP, user.ID) err = c.AdminLogs.Create("download", 0, "backup", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -71,7 +71,7 @@ func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("create", fid, "forum", user.LastIP, user.ID) err = c.AdminLogs.Create("create", fid, "forum", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -130,7 +130,7 @@ func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, sfi
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("delete", fid, "forum", user.LastIP, user.ID) err = c.AdminLogs.Create("delete", fid, "forum", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -161,7 +161,7 @@ func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
} }
c.Forums.UpdateOrder(updateMap) c.Forums.UpdateOrder(updateMap)
err := c.AdminLogs.Create("reorder", 0, "forum", user.LastIP, user.ID) err := c.AdminLogs.Create("reorder", 0, "forum", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
@ -259,7 +259,7 @@ func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sfid
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", fid, "forum", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", fid, "forum", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -300,7 +300,7 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js) return c.LocalErrorJSQ(err.Error(), w, r, user, js)
} }
err = c.AdminLogs.Create("edit", fid, "forum", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", fid, "forum", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -348,7 +348,6 @@ func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user c.User,
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if forum.Preset == "" { if forum.Preset == "" {
forum.Preset = "custom" forum.Preset = "custom"
} }
@ -438,7 +437,7 @@ func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js) return c.LocalErrorJSQ(err.Error(), w, r, user, js)
} }
err = c.AdminLogs.Create("edit", fid, "forum", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", fid, "forum", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -229,7 +229,7 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("create", pid, "group_promotion", user.LastIP, user.ID) err = c.AdminLogs.Create("create", pid, "group_promotion", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -276,7 +276,7 @@ func GroupsPromotionsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("delete", pid, "group_promotion", user.LastIP, user.ID) err = c.AdminLogs.Create("delete", pid, "group_promotion", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -375,7 +375,6 @@ func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid
if err != nil { if err != nil {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
} }
group, err := c.Groups.Get(gid) group, err := c.Groups.Get(gid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
@ -386,24 +385,25 @@ func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid
return ferr return ferr
} }
gname := r.FormValue("name") name := r.FormValue("name")
if gname == "" { if name == "" {
return c.LocalError(p.GetErrorPhrase("panel_groups_need_name"), w, r, user) return c.LocalError(p.GetErrorPhrase("panel_groups_need_name"), w, r, user)
} }
gtag := r.FormValue("tag") tag := r.FormValue("tag")
rank := r.FormValue("type") rank := r.FormValue("type")
var originalRank string var originalRank string
// TODO: Use a switch for this // TODO: Use a switch for this
if group.IsAdmin { switch {
case group.IsAdmin:
originalRank = "Admin" originalRank = "Admin"
} else if group.IsMod { case group.IsMod:
originalRank = "Mod" originalRank = "Mod"
} else if group.IsBanned { case group.IsBanned:
originalRank = "Banned" originalRank = "Banned"
} else if group.ID == 6 { case group.ID == 6:
originalRank = "Guest" originalRank = "Guest"
} else { default:
originalRank = "Member" originalRank = "Member"
} }
@ -436,11 +436,11 @@ func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid
} }
} }
err = group.Update(gname, gtag) err = group.Update(name, tag)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("edit", group.ID, "group", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", group.ID, "group", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -489,7 +489,7 @@ func GroupsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("edit", group.ID, "group", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", group.ID, "group", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -536,7 +536,7 @@ func GroupsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("create", gid, "group", user.LastIP, user.ID) err = c.AdminLogs.Create("create", gid, "group", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -63,7 +63,7 @@ func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("create", pid, "page", user.LastIP, user.ID) err = c.AdminLogs.Create("create", pid, "page", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -130,7 +130,7 @@ func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid s
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("edit", pid, "page", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", pid, "page", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -153,7 +153,7 @@ func PagesDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("delete", pid, "page", user.LastIP, user.ID) err = c.AdminLogs.Create("delete", pid, "page", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -73,7 +73,7 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user c.User, uname
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, user)
} }
err = c.AdminLogs.CreateExtra("activate", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name)) err = c.AdminLogs.CreateExtra("activate", 0, "plugin", user.GetIP(), user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -111,7 +111,7 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user c.User, unam
if plugin.Deactivate != nil { if plugin.Deactivate != nil {
plugin.Deactivate(plugin) plugin.Deactivate(plugin)
} }
err = c.AdminLogs.CreateExtra("deactivate", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name)) err = c.AdminLogs.CreateExtra("deactivate", 0, "plugin", user.GetIP(), user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -181,7 +181,7 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user c.User, uname s
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, user)
} }
err = c.AdminLogs.CreateExtra("install", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name)) err = c.AdminLogs.CreateExtra("install", 0, "plugin", user.GetIP(), user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -109,7 +109,7 @@ func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, name
return rerr return rerr
} }
// TODO: Avoid this hack // TODO: Avoid this hack
err := c.AdminLogs.Create(name, 0, "setting", user.LastIP, user.ID) err := c.AdminLogs.Create(name, 0, "setting", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -58,7 +58,7 @@ func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user c.User, uname
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.CreateExtra("set_default", 0, "theme", user.LastIP, user.ID, c.SanitiseSingleLine(theme.Name)) err = c.AdminLogs.CreateExtra("set_default", 0, "theme", user.GetIP(), user.ID, c.SanitiseSingleLine(theme.Name))
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -233,7 +233,7 @@ func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user c.Use
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", menuItem.ID, "menu_item", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", menuItem.ID, "menu_item", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -266,7 +266,7 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("create", itemID, "menu_item", user.LastIP, user.ID) err = c.AdminLogs.Create("create", itemID, "menu_item", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -300,7 +300,7 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("delete", menuItem.ID, "menu_item", user.LastIP, user.ID) err = c.AdminLogs.Create("delete", menuItem.ID, "menu_item", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -342,7 +342,7 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user c.Us
} }
menuHold.UpdateOrder(updateMap) menuHold.UpdateOrder(updateMap)
err = c.AdminLogs.Create("suborder", menuHold.MenuID, "menu", user.LastIP, user.ID) err = c.AdminLogs.Create("suborder", menuHold.MenuID, "menu", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -446,7 +446,7 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", widget.ID, "widget", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", widget.ID, "widget", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -474,7 +474,7 @@ func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Us
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("create", wid, "widget", user.LastIP, user.ID) err = c.AdminLogs.Create("create", wid, "widget", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -507,7 +507,7 @@ func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.Us
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("delete", widget.ID, "widget", user.LastIP, user.ID) err = c.AdminLogs.Create("delete", widget.ID, "widget", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -153,7 +153,7 @@ func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid s
} }
targetUser.CacheRemove() targetUser.CacheRemove()
err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -209,7 +209,7 @@ func UsersAvatarSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -249,7 +249,7 @@ func UsersAvatarRemoveSubmit(w http.ResponseWriter, r *http.Request, user c.User
return ferr return ferr
} }
err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.LastIP, user.ID) err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -52,7 +52,7 @@ func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("create", wfid, "word_filter", user.LastIP, user.ID) err = c.AdminLogs.Create("create", wfid, "word_filter", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -111,7 +111,7 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.CreateExtra("edit", wfid, "word_filter", user.LastIP, user.ID, string(lBytes)) err = c.AdminLogs.CreateExtra("edit", wfid, "word_filter", user.GetIP(), user.ID, string(lBytes))
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
@ -125,7 +125,7 @@ func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := (r.PostFormValue("js") == "1") js := r.PostFormValue("js") == "1"
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, user, js)
} }
@ -138,7 +138,7 @@ func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, js) return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, js)
} }
err = c.AdminLogs.Create("delete", wfid, "word_filter", user.LastIP, user.ID) err = c.AdminLogs.Create("delete", wfid, "word_filter", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -57,7 +57,7 @@ func PollVote(w http.ResponseWriter, r *http.Request, user c.User, sPollID strin
if err != nil { if err != nil {
return c.LocalError("Malformed input", w, r, user) return c.LocalError("Malformed input", w, r, user)
} }
err = poll.CastVote(optionIndex, user.ID, user.LastIP) err = poll.CastVote(optionIndex, user.ID, user.GetIP())
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -39,7 +39,7 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Use
return c.LocalError("You can't make a blank post", w, r, user) return c.LocalError("You can't make a blank post", w, r, user)
} }
// TODO: Fully parse the post and store it in the parsed column // TODO: Fully parse the post and store it in the parsed column
_, err = c.Prstore.Create(profileOwner.ID, content, user.ID, user.LastIP) _, err = c.Prstore.Create(profileOwner.ID, content, user.ID, user.GetIP())
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -66,7 +66,7 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
content := c.PreparseMessage(r.PostFormValue("content")) content := c.PreparseMessage(r.PostFormValue("content"))
// TODO: Fully parse the post and put that in the parsed column // TODO: Fully parse the post and put that in the parsed column
rid, err := c.Rstore.Create(topic, content, user.LastIP, user.ID) rid, err := c.Rstore.Create(topic, content, user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
@ -337,7 +337,7 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.ModLogs.Create("delete", reply.ParentID, "reply", user.LastIP, user.ID) err = c.ModLogs.Create("delete", reply.ParentID, "reply", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }

View File

@ -9,7 +9,6 @@ import (
"io" "io"
//"fmt" //"fmt"
"golang.org/x/image/tiff"
"image" "image"
"image/gif" "image/gif"
"image/jpeg" "image/jpeg"
@ -21,6 +20,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"golang.org/x/image/tiff"
c "github.com/Azareal/Gosora/common" c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/counters" "github.com/Azareal/Gosora/common/counters"
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
@ -332,7 +333,7 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.
} }
func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
fid, err := strconv.Atoi(r.PostFormValue("topic-board")) fid, err := strconv.Atoi(r.PostFormValue("board"))
if err != nil { if err != nil {
return c.LocalError(phrases.GetErrorPhrase("id_must_be_integer"), w, r, user) return c.LocalError(phrases.GetErrorPhrase("id_must_be_integer"), w, r, user)
} }
@ -345,10 +346,10 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
} }
tname := c.SanitiseSingleLine(r.PostFormValue("topic-name")) tname := c.SanitiseSingleLine(r.PostFormValue("name"))
content := c.PreparseMessage(r.PostFormValue("topic-content")) content := c.PreparseMessage(r.PostFormValue("content"))
// TODO: Fully parse the post and store it in the parsed column // TODO: Fully parse the post and store it in the parsed column
tid, err := c.Topics.Create(fid, tname, content, user.ID, user.LastIP) tid, err := c.Topics.Create(fid, tname, content, user.ID, user.GetIP())
if err != nil { if err != nil {
switch err { switch err {
case c.ErrNoRows: case c.ErrNoRows:
@ -632,7 +633,7 @@ func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
if err != nil { if err != nil {
return c.PreError("The provided TopicID is not a valid number.", w, r) return c.PreError("The provided TopicID is not a valid number.", w, r)
} }
tids = append(tids, tid) tids = []int{tid}
} }
if len(tids) == 0 { if len(tids) == 0 {
return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, js) return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, js)
@ -663,13 +664,13 @@ func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.ModLogs.Create("delete", tid, "topic", user.LastIP, user.ID) err = c.ModLogs.Create("delete", tid, "topic", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
// ? - We might need to add soft-delete before we can do an action reply for this // ? - We might need to add soft-delete before we can do an action reply for this
/*_, err = stmts.createActionReply.Exec(tid,"delete",ipaddress,user.ID) /*_, err = stmts.createActionReply.Exec(tid,"delete",ip,user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err,w,r,js) return c.InternalErrorJSQ(err,w,r,js)
}*/ }*/
@ -890,11 +891,11 @@ func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, sfid s
} }
func addTopicAction(action string, t *c.Topic, u c.User) error { func addTopicAction(action string, t *c.Topic, u c.User) error {
err := c.ModLogs.Create(action, t.ID, "topic", u.LastIP, u.ID) err := c.ModLogs.Create(action, t.ID, "topic", u.GetIP(), u.ID)
if err != nil { if err != nil {
return err return err
} }
return t.CreateActionReply(action, u.LastIP, u.ID) return t.CreateActionReply(action, u.GetIP(), u.ID)
} }
// TODO: Refactor this // TODO: Refactor this

View File

@ -73,7 +73,7 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid str
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.ModLogs.Create("ban", uid, "user", user.LastIP, user.ID) err = c.ModLogs.Create("ban", uid, "user", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -119,7 +119,7 @@ func UnbanUser(w http.ResponseWriter, r *http.Request, user c.User, suid string)
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.ModLogs.Create("unban", uid, "user", user.LastIP, user.ID) err = c.ModLogs.Create("unban", uid, "user", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -160,7 +160,7 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user c.User, suid stri
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.ModLogs.Create("activate", targetUser.ID, "user", user.LastIP, user.ID) err = c.ModLogs.Create("activate", targetUser.ID, "user", user.GetIP(), user.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -7,22 +7,22 @@
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form> <form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form>
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem formlabel"><a>{{lang "create_topic_board"}}</a></div> <div class="formitem formlabel"><a>{{lang "create_topic_board"}}</a></div>
<div class="formitem"><select form="quick_post_form" id="topic_board_input" name="topic-board"> <div class="formitem"><select form="quick_post_form" id="topic_board_input" name="board">
{{range .ItemList}}<option{{if eq .ID $.FID}} selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}} {{range .ItemList}}<option{{if eq .ID $.FID}} selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
</select></div> </select></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "create_topic_name"}}</a></div> <div class="formitem formlabel"><a>{{lang "create_topic_name"}}</a></div>
<div class="formitem"><input form="quick_post_form" name="topic-name" type="text" placeholder="{{lang "create_topic_name"}}" required /></div> <div class="formitem"><input form="quick_post_form" name="name" type="text" placeholder="{{lang "create_topic_name"}}" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "create_topic_content"}}</a></div> <div class="formitem formlabel"><a>{{lang "create_topic_content"}}</a></div>
<div class="formitem"><textarea form="quick_post_form" class="large" id="topic_content" name="topic-content" placeholder="{{lang "create_topic_placeholder"}}" required></textarea></div> <div class="formitem"><textarea form="quick_post_form" class="large" id="topic_content" name="content" placeholder="{{lang "create_topic_placeholder"}}" required></textarea></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<button form="quick_post_form" name="topic-button" class="formbutton">{{lang "create_topic_create_button"}}</button> <button form="quick_post_form" name="topic-button" class="formbutton">{{lang "create_topic_create_button"}}</button>
{{if .CurrentUser.Perms.UploadFiles}} {{if .CurrentUser.Perms.UploadFiles}}
<input name="quick_topic_upload_files" form="quick_post_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" /> <input name="quick_topic_upload_files" form="quick_post_form" id="quick_topic_upload_files" multiple type="file" style="display:none;" />
<label for="quick_topic_upload_files" class="formbutton add_file_button">{{lang "create_topic_add_file_button"}}</label>{{end}} <label for="quick_topic_upload_files" class="formbutton add_file_button">{{lang "create_topic_add_file_button"}}</label>{{end}}
<div id="upload_file_dock"></div> <div id="upload_file_dock"></div>
</div> </div>

View File

@ -31,12 +31,12 @@
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form auto_hide" aria-label="{{lang "quick_topic.aria"}}"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form auto_hide" aria-label="{{lang "quick_topic.aria"}}">
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form> <form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form>
<img class="little_row_avatar" src="{{.CurrentUser.MicroAvatar}}" height=64 alt="{{lang "quick_topic.avatar_alt"}}" title="{{lang "quick_topic.avatar_tooltip"}}" /> <img class="little_row_avatar" src="{{.CurrentUser.MicroAvatar}}" height=64 alt="{{lang "quick_topic.avatar_alt"}}" title="{{lang "quick_topic.avatar_tooltip"}}" />
<input form="quick_post_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden"> <input form="quick_post_form" id="topic_board_input" name="board" value="{{.Forum.ID}}" type="hidden">
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_name_row real_first_child"> <div class="formrow topic_name_row real_first_child">
<div class="formitem"> <div class="formitem">
<input form="quick_post_form" name="topic-name" placeholder="{{lang "quick_topic.whatsup"}}" required> <input form="quick_post_form" name="name" placeholder="{{lang "quick_topic.whatsup"}}" required>
</div> </div>
</div> </div>
</div> </div>

View File

@ -29,12 +29,12 @@
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form auto_hide" aria-label="{{lang "quick_topic.aria"}}"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form auto_hide" aria-label="{{lang "quick_topic.aria"}}">
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form> <form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form>
<img class="little_row_avatar" src="{{.CurrentUser.MicroAvatar}}" height=64 alt="{{lang "quick_topic.avatar_alt"}}" title="{{lang "quick_topic.avatar_tooltip"}}" /> <img class="little_row_avatar" src="{{.CurrentUser.MicroAvatar}}" height=64 alt="{{lang "quick_topic.avatar_alt"}}" title="{{lang "quick_topic.avatar_tooltip"}}" />
<input form="quick_post_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden"> <input form="quick_post_form" id="topic_board_input" name="board" value="{{.Forum.ID}}" type="hidden">
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_name_row real_first_child"> <div class="formrow topic_name_row real_first_child">
<div class="formitem"> <div class="formitem">
<input form="quick_post_form" name="topic-name" placeholder="{{lang "quick_topic.whatsup"}}" required> <input form="quick_post_form" name="name" placeholder="{{lang "quick_topic.whatsup"}}" required>
</div> </div>
</div> </div>
</div> </div>
@ -43,7 +43,7 @@
</div> </div>
{{end}} {{end}}
{{end}} {{end}}
<div id="forum_topic_list" class="rowblock micro_grid" aria-label="{{lang "forum_list_aria"}}" style="grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));"> <div id="forum_topic_list" class="rowblock micro_grid" aria-label="{{lang "forum_list_aria"}}" style="grid-template-columns:repeat(auto-fit,minmax(130px,1fr));">
{{range .ItemList}}<div class="rowitem" data-tid="{{.ID}}"> {{range .ItemList}}<div class="rowitem" data-tid="{{.ID}}">
<div> <div>
<a class="rowtopic" href="{{.Link}}" itemprop="itemListElement"><img src="{{.Content}}" style="width:100%;height:160px;" /></a> <a class="rowtopic" href="{{.Link}}" itemprop="itemListElement"><img src="{{.Content}}" style="width:100%;height:160px;" /></a>

View File

@ -14,8 +14,8 @@
<div class="formitem formlabel"><a>{{lang "panel_themes_widgets_enabled"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_widgets_enabled"}}</a></div>
<div class="formitem"> <div class="formitem">
<select name="wenabled"> <select name="wenabled">
<option{{if .Enabled}} selected{{end}} value="1">{{lang "option_yes"}}</option> <option{{if .Enabled}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .Enabled}} selected{{end}} value="0">{{lang "option_no"}}</option> <option{{if not .Enabled}} selected{{end}} value=0>{{lang "option_no"}}</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -59,13 +59,13 @@
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
<div class="formitem"><select form="quick_post_form" id="topic_board_input" name="topic-board"> <div class="formitem"><select form="quick_post_form" id="topic_board_input" name="board">
{{range .ForumList}}<option{{if eq .ID $.DefaultForum}} selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}} {{range .ForumList}}<option{{if eq .ID $.DefaultForum}} selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
</select></div> </select></div>
</div> </div>
<div class="formrow topic_name_row"> <div class="formrow topic_name_row">
<div class="formitem"> <div class="formitem">
<input form="quick_post_form" name="topic-name" placeholder="{{lang "quick_topic.whatsup"}}" required> <input form="quick_post_form" name="name" placeholder="{{lang "quick_topic.whatsup"}}" required>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<input form="quick_post_form" id="has_poll_input" name="has_poll" value=0 type="hidden" /> <input form="quick_post_form" id="has_poll_input" name="has_poll" value=0 type="hidden" />
<div class="formrow topic_content_row"> <div class="formrow topic_content_row">
<div class="formitem"> <div class="formitem">
<textarea form="quick_post_form" id="input_content" name="topic-content" placeholder="{{lang "quick_topic.content_placeholder"}}" required></textarea> <textarea form="quick_post_form" id="input_content" name="content" placeholder="{{lang "quick_topic.content_placeholder"}}" required></textarea>
</div> </div>
</div> </div>
<div class="formrow poll_content_row auto_hide"> <div class="formrow poll_content_row auto_hide">

View File

@ -141,22 +141,34 @@ func tickLoop(thumbChan chan bool) {
} }
} }
func dailies() { func asmMatches() {
// TODO: Find a more efficient way of doing this // TODO: Find a more efficient way of doing this
err := qgen.NewAcc().Select("activity_stream").Cols("asid").EachInt(func(asid int) error { acc := qgen.NewAcc()
count, err := qgen.NewAcc().Count("activity_stream_matches").Where("asid = " + strconv.Itoa(asid)).Total() countStmt := acc.Count("activity_stream_matches").Where("asid=?").Prepare()
if err := acc.FirstError(); err != nil {
c.LogError(err)
return
}
err := acc.Select("activity_stream").Cols("asid").EachInt(func(asid int) error {
var count int
err := countStmt.QueryRow(asid).Scan(&count)
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return err return err
} }
if count > 0 { if count > 0 {
return nil return nil
} }
_, err = qgen.NewAcc().Delete("activity_stream").Where("asid = ?").Run(asid) _, err = qgen.NewAcc().Delete("activity_stream").Where("asid=?").Run(asid)
return err return err
}) })
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
c.LogError(err) c.LogError(err)
} }
}
func dailies() {
asmMatches()
if c.Config.LogPruneCutoff > -1 { if c.Config.LogPruneCutoff > -1 {
f := func(tbl string) { f := func(tbl string) {
@ -172,7 +184,7 @@ func dailies() {
if c.Config.PostIPCutoff > -1 { if c.Config.PostIPCutoff > -1 {
// TODO: Use unixtime to remove this MySQLesque logic? // TODO: Use unixtime to remove this MySQLesque logic?
f := func(tbl string) { f := func(tbl string) {
_, err := qgen.NewAcc().Update(tbl).Set("ipaddress = '0'").DateOlderThan("createdAt",c.Config.PostIPCutoff,"day").Where("ipaddress != '0'").Exec() _, err := qgen.NewAcc().Update(tbl).Set("ipaddress='0'").DateOlderThan("createdAt",c.Config.PostIPCutoff,"day").Where("ipaddress!='0'").Exec()
if err != nil { if err != nil {
c.LogError(err) c.LogError(err)
} }
@ -182,16 +194,30 @@ func dailies() {
f("users_replies") f("users_replies")
// TODO: Find some way of purging the ip data in polls_votes without breaking any anti-cheat measures which might be running... maybe hash it instead? // TODO: Find some way of purging the ip data in polls_votes without breaking any anti-cheat measures which might be running... maybe hash it instead?
}
// TODO: lastActiveAt isn't currently set, so we can't rely on this to purge last_ips of users who haven't been on in a while // TODO: lastActiveAt isn't currently set, so we can't rely on this to purge last_ips of users who haven't been on in a while
/*_, err = qgen.NewAcc().Update("users").Set("last_ip = '0'").DateOlderThan("lastActiveAt",c.Config.PostIPCutoff,"day").Where("last_ip != '0'").Exec() /*if c.Config.LastIPCutoff == -1 {
_, err := qgen.NewAcc().Update("users").Set("last_ip=0").Where("last_ip!=0").Exec()
if err != nil {
c.LogError(err)
}
} else */if c.Config.LastIPCutoff > 0 {
/*_, err = qgen.NewAcc().Update("users").Set("last_ip='0'").DateOlderThan("lastActiveAt",c.Config.PostIPCutoff,"day").Where("last_ip!='0'").Exec()
if err != nil { if err != nil {
c.LogError(err) c.LogError(err)
}*/ }*/
mon := time.Now().Month()
_, err := qgen.NewAcc().Update("users").Set("last_ip=0").Where("last_ip!=0 AND last_ip NOT LIKE '"+strconv.Itoa(int(mon))+"-%'").Exec()
if err != nil {
c.LogError(err)
}
} }
err = c.Meta.Set("lastDaily", strconv.FormatInt(time.Now().Unix(), 10)) {
if err != nil { err := c.Meta.Set("lastDaily", strconv.FormatInt(time.Now().Unix(), 10))
c.LogError(err) if err != nil {
c.LogError(err)
}
} }
} }