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

@ -23,7 +23,7 @@ type DefaultIPSearcher struct {
func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
acc := qgen.NewAcc()
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(),
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(),
@ -33,8 +33,7 @@ func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
func (s *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
var uid int
reqUserList := make(map[int]bool)
runQuery := func(stmt *sql.Stmt) error {
rows, err := stmt.Query(ip)
runQuery2 := func(rows *sql.Rows, err error) error {
if err != nil {
return err
}
@ -49,8 +48,11 @@ func (s *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
}
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 {
return uids, err
}

View File

@ -17,11 +17,24 @@ type DefaultPasswordResetter struct {
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) {
pr := "password_resets"
return &DefaultPasswordResetter{
getTokens: acc.Select("password_resets").Columns("token").Where("uid = ?").Prepare(),
create: acc.Insert("password_resets").Columns("email, uid, validated, token, createdAt").Fields("?,?,0,?,UTC_TIMESTAMP()").Prepare(),
delete: acc.Delete("password_resets").Where("uid =?").Prepare(),
getTokens: acc.Select(pr).Columns("token").Where("uid = ?").Prepare(),
create: acc.Insert(pr).Columns("email, uid, validated, token, createdAt").Fields("?,?,0,?,UTC_TIMESTAMP()").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()
}
@ -45,16 +58,14 @@ func (r *DefaultPasswordResetter) ValidateToken(uid int, token string) error {
success := false
for rows.Next() {
var rtoken string
err := rows.Scan(&rtoken)
if err != nil {
if err := rows.Scan(&rtoken); err != nil {
return err
}
if subtle.ConstantTimeCompare([]byte(token), []byte(rtoken)) == 1 {
success = true
}
}
err = rows.Err()
if err != nil {
if err = rows.Err(); err != nil {
return err
}

View File

@ -44,7 +44,7 @@ func (s *DefaultReportStore) Create(title string, content string, u *User, itemT
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 {
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.LastIP {
err = usercpy.UpdateIP(host)
if usercpy.Loggedin && host != usercpy.GetIP() {
mon := time.Now().Month()
err = usercpy.UpdateIP(strconv.Itoa(int(mon)) + "-" + host)
if err != nil {
InternalError(err, w, r)
return *usercpy, false
}
}
usercpy.LastIP = host
return *usercpy, true
}
@ -350,11 +350,11 @@ func UploadAvatar(w http.ResponseWriter, r *http.Request, user User, tuid int) (
if hdr.Filename == "" {
continue
}
infile, err := hdr.Open()
inFile, err := hdr.Open()
if err != nil {
return "", LocalError("Upload failed", w, r, user)
}
defer infile.Close()
defer inFile.Close()
if ext == "" {
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
outfile, err := os.Create("./uploads/avatar_" + strconv.Itoa(tuid) + "." + ext)
outFile, err := os.Create("./uploads/avatar_" + strconv.Itoa(tuid) + "." + ext)
if err != nil {
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 {
return "", LocalError("Upload failed [Copy Failed]", w, r, user)
}

View File

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

View File

@ -338,9 +338,14 @@ func (u *User) ChangeGroup(group int) (err error) {
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
func (u *User) UpdateIP(host string) error {
_, err := userStmts.updateLastIP.Exec(host, u.ID)
func (u *User) UpdateIP(ip string) error {
_, err := userStmts.updateLastIP.Exec(ip, u.ID)
if uc := Users.GetCache(); uc != nil {
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.
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

View File

@ -74,24 +74,25 @@ func BbcodeRegexParse(msg string) string {
// Only does the simple BBCode like [u], [b], [i] and [s]
func bbcodeSimpleParse(msg string) string {
var hasU, hasB, hasI, hasS bool
msgbytes := []byte(msg)
for i := 0; (i + 2) < len(msgbytes); i++ {
if msgbytes[i] == '[' && msgbytes[i+2] == ']' {
if msgbytes[i+1] == 'b' && !hasB {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
mbytes := []byte(msg)
for i := 0; (i + 2) < len(mbytes); i++ {
if mbytes[i] == '[' && mbytes[i+2] == ']' {
ch := mbytes[i+1]
if ch == 'b' && !hasB {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true
} else if msgbytes[i+1] == 'i' && !hasI {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 'i' && !hasI {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasI = true
} else if msgbytes[i+1] == 'u' && !hasU {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 'u' && !hasU {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasU = true
} else if msgbytes[i+1] == 's' && !hasS {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 's' && !hasS {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasS = true
}
i += 2
@ -105,46 +106,47 @@ func bbcodeSimpleParse(msg string) string {
closeBold := []byte("</b>")
closeStrike := []byte("</s>")
if hasI {
msgbytes = append(msgbytes, closeItalic...)
mbytes = append(mbytes, closeItalic...)
}
if hasU {
msgbytes = append(msgbytes, closeUnder...)
mbytes = append(mbytes, closeUnder...)
}
if hasB {
msgbytes = append(msgbytes, closeBold...)
mbytes = append(mbytes, closeBold...)
}
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
func BbcodeParseWithoutCode(msg string) string {
var hasU, hasB, hasI, hasS bool
var complexBbc bool
msgbytes := []byte(msg)
for i := 0; (i + 3) < len(msgbytes); i++ {
if msgbytes[i] == '[' {
if msgbytes[i+2] != ']' {
if msgbytes[i+1] == '/' {
if msgbytes[i+3] == ']' {
if msgbytes[i+2] == 'b' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
mbytes := []byte(msg)
for i := 0; (i + 3) < len(mbytes); i++ {
if mbytes[i] == '[' {
if mbytes[i+2] != ']' {
if mbytes[i+1] == '/' {
if mbytes[i+3] == ']' {
switch mbytes[i+2] {
case 'b':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasB = false
} else if msgbytes[i+2] == 'i' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
case 'i':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasI = false
} else if msgbytes[i+2] == 'u' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
case 'u':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasU = false
} else if msgbytes[i+2] == 's' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
case 's':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasS = false
}
i += 3
@ -155,21 +157,22 @@ func BbcodeParseWithoutCode(msg string) string {
complexBbc = true
}
} else {
if msgbytes[i+1] == 'b' && !hasB {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
ch := mbytes[i+1]
if ch == 'b' && !hasB {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true
} else if msgbytes[i+1] == 'i' && !hasI {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 'i' && !hasI {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasI = true
} else if msgbytes[i+1] == 'u' && !hasU {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 'u' && !hasU {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasU = true
} else if msgbytes[i+1] == 's' && !hasS {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 's' && !hasS {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasS = true
}
i += 2
@ -184,29 +187,29 @@ func BbcodeParseWithoutCode(msg string) string {
closeBold := []byte("</b>")
closeStrike := []byte("</s>")
if hasI {
msgbytes = append(bytes.TrimSpace(msgbytes), closeItalic...)
mbytes = append(bytes.TrimSpace(mbytes), closeItalic...)
}
if hasU {
msgbytes = append(bytes.TrimSpace(msgbytes), closeUnder...)
mbytes = append(bytes.TrimSpace(mbytes), closeUnder...)
}
if hasB {
msgbytes = append(bytes.TrimSpace(msgbytes), closeBold...)
mbytes = append(bytes.TrimSpace(mbytes), closeBold...)
}
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
if complexBbc {
msg = string(msgbytes)
msg = string(mbytes)
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 = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeQuotes.ReplaceAllString(msg, "<blockquote>$1</blockquote>")
return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
}
return string(msgbytes)
return string(mbytes)
}
// Does every type of BBCode
@ -214,63 +217,66 @@ func BbcodeFullParse(msg string) string {
var hasU, hasB, hasI, hasS, hasC bool
var complexBbc bool
msgbytes := []byte(msg)
msgbytes = append(msgbytes, c.SpaceGap...)
for i := 0; i < len(msgbytes); i++ {
if msgbytes[i] == '[' {
if msgbytes[i+2] != ']' {
if msgbytes[i+1] == '/' {
if msgbytes[i+3] == ']' {
mbytes := []byte(msg)
mbytes = append(mbytes, c.SpaceGap...)
for i := 0; i < len(mbytes); i++ {
if mbytes[i] == '[' {
if mbytes[i+2] != ']' {
if mbytes[i+1] == '/' {
if mbytes[i+3] == ']' {
if !hasC {
if msgbytes[i+2] == 'b' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
switch mbytes[i+2] {
case 'b':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasB = false
} else if msgbytes[i+2] == 'i' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
case 'i':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasI = false
} else if msgbytes[i+2] == 'u' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
case 'u':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasU = false
} else if msgbytes[i+2] == 's' {
msgbytes[i] = '<'
msgbytes[i+3] = '>'
case 's':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasS = false
}
i += 3
}
} 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
i += 7
}
complexBbc = true
}
} 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
i += 6
}
complexBbc = true
}
} else if !hasC {
if msgbytes[i+1] == 'b' && !hasB {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
ch := mbytes[i+1]
if ch == 'b' && !hasB {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true
} else if msgbytes[i+1] == 'i' && !hasI {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 'i' && !hasI {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasI = true
} else if msgbytes[i+1] == 'u' && !hasU {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 'u' && !hasU {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasU = true
} else if msgbytes[i+1] == 's' && !hasS {
msgbytes[i] = '<'
msgbytes[i+2] = '>'
} else if ch == 's' && !hasS {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasS = true
}
i += 2
@ -285,46 +291,46 @@ func BbcodeFullParse(msg string) string {
closeBold := []byte("</b>")
closeStrike := []byte("</s>")
if hasI {
msgbytes = append(bytes.TrimSpace(msgbytes), closeItalic...)
mbytes = append(bytes.TrimSpace(mbytes), closeItalic...)
}
if hasU {
msgbytes = append(bytes.TrimSpace(msgbytes), closeUnder...)
mbytes = append(bytes.TrimSpace(mbytes), closeUnder...)
}
if hasB {
msgbytes = append(bytes.TrimSpace(msgbytes), closeBold...)
mbytes = append(bytes.TrimSpace(mbytes), closeBold...)
}
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 {
i := 0
var start, lastTag int
var outbytes []byte
for ; i < len(msgbytes); i++ {
if msgbytes[i] == '[' {
if msgbytes[i+1] == 'u' {
if msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' {
i, start, lastTag, outbytes = bbcodeParseURL(i, start, lastTag, msgbytes, outbytes)
for ; i < len(mbytes); i++ {
if mbytes[i] == '[' {
if mbytes[i+1] == 'u' {
if mbytes[i+4] == ']' && mbytes[i+2] == 'r' && mbytes[i+3] == 'l' {
i, start, lastTag, outbytes = bbcodeParseURL(i, start, lastTag, mbytes, outbytes)
continue
}
} else if msgbytes[i+1] == 'r' {
if bytes.Equal(msgbytes[i+2:i+6], []byte("and]")) {
i, start, lastTag, outbytes = bbcodeParseRand(i, start, lastTag, msgbytes, outbytes)
} else if mbytes[i+1] == 'r' {
if bytes.Equal(mbytes[i+2:i+6], []byte("and]")) {
i, start, lastTag, outbytes = bbcodeParseRand(i, start, lastTag, mbytes, outbytes)
}
}
}
}
if lastTag != i {
outbytes = append(outbytes, msgbytes[lastTag:]...)
outbytes = append(outbytes, mbytes[lastTag:]...)
}
if len(outbytes) != 0 {
msg = string(outbytes[0 : len(outbytes)-10])
} else {
msg = string(msgbytes[0 : len(msgbytes)-10])
msg = string(mbytes[0 : len(mbytes)-10])
}
// TODO: Optimise these
@ -335,27 +341,27 @@ func BbcodeFullParse(msg string) string {
msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeH1.ReplaceAllString(msg, "<h2>$1</h2>")
} else {
msg = string(msgbytes[0 : len(msgbytes)-10])
msg = string(mbytes[0 : len(mbytes)-10])
}
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?
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
outbytes = append(outbytes, msgbytes[lastTag:i]...)
outbytes = append(outbytes, mbytes[lastTag:i]...)
i = start
i += c.PartialURLStringLen2(string(msgbytes[start:]))
if !bytes.Equal(msgbytes[i:i+6], []byte("[/url]")) {
i += c.PartialURLStringLen2(string(mbytes[start:]))
if !bytes.Equal(mbytes[i:i+6], []byte("[/url]")) {
outbytes = append(outbytes, c.InvalidURL...)
return i, start, lastTag, outbytes
}
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, msgbytes[start:i]...)
outbytes = append(outbytes, mbytes[start:i]...)
outbytes = append(outbytes, c.URLClose...)
i += 6
lastTag = i

View File

@ -167,7 +167,14 @@ func TestParser(t *testing.T) {
l.Add("// t", "// 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:")
//t l.Add("http:/", "http:/")
//t l.Add("http:/d", "http:/d")
l.Add("http:d", "http:d")
l.Add("https:", "https:")
l.Add("ftp:", "ftp:")
l.Add("git:", "git:")

View File

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

View File

@ -1,23 +1,23 @@
"use strict";
$(document).ready(() => {
let clickHandle = function(event){
let clickHandle = function(ev){
console.log("in clickHandle")
event.preventDefault();
let eparent = $(this).closest(".editable_parent");
eparent.find(".hide_on_block_edit").addClass("edit_opened");
eparent.find(".show_on_block_edit").addClass("edit_opened");
eparent.addClass("in_edit");
ev.preventDefault();
let ep = $(this).closest(".editable_parent");
ep.find(".hide_on_block_edit").addClass("edit_opened");
ep.find(".show_on_block_edit").addClass("edit_opened");
ep.addClass("in_edit");
eparent.find(".widget_save").click(() => {
eparent.find(".hide_on_block_edit").removeClass("edit_opened");
eparent.find(".show_on_block_edit").removeClass("edit_opened");
eparent.removeClass("in_edit");
ep.find(".widget_save").click(() => {
ep.find(".hide_on_block_edit").removeClass("edit_opened");
ep.find(".show_on_block_edit").removeClass("edit_opened");
ep.removeClass("in_edit");
});
eparent.find(".widget_delete").click(function(event) {
event.preventDefault();
eparent.remove();
ep.find(".widget_delete").click(function(ev) {
ev.preventDefault();
ep.remove();
let formData = new URLSearchParams();
formData.append("s",me.User.S);
let req = new XMLHttpRequest();
@ -29,15 +29,14 @@ $(document).ready(() => {
$(".widget_item a").click(clickHandle);
let changeHandle = function(event){
let changeHandle = function(ev){
let wtype = this.options[this.selectedIndex].value;
let typeBlock = this.closest(".widget_edit").querySelector(".wtypes");
typeBlock.className = "wtypes wtype_"+wtype;
};
$(".wtype_sel").change(changeHandle);
$(".widget_new a").click(function(event){
$(".widget_new a").click(function(ev){
console.log("clicked widget_new a")
let widgetList = this.closest(".panel_widgets");
let widgetNew = this.closest(".widget_new");
@ -51,10 +50,10 @@ $(document).ready(() => {
$(".wtype_sel").change(changeHandle);
});
$(".widget_save").click(function(event){
$(".widget_save").click(function(ev){
console.log("in .widget_save")
event.preventDefault();
event.stopPropagation();
ev.preventDefault();
ev.stopPropagation();
let pform = this.closest("form");
let data = new URLSearchParams();
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())
}
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))
}
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))
}
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))
}
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))
}
@ -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))
}
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))
}
@ -135,15 +135,16 @@ func (build *Accumulator) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) *sql.S
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)))
}
func (build *Accumulator) SimpleUpdateSelect(table string, set string, where string) *sql.Stmt {
return build.prepare(build.adapter.SimpleUpdateSelect(qUpdate(table, set, where)))
func (build *Accumulator) SimpleUpdateSelect(table, set, table2, cols, where, orderby, limit string) *sql.Stmt {
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))
}

View File

@ -44,7 +44,7 @@ func (a *MssqlAdapter) DbVersion() string {
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 == "" {
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: 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 == "" {
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")
}
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 == "" {
return "", errors.New("You need a name for this table")
}
q := "INSERT INTO [" + table + "] ("
if columns == "" {
if cols == "" {
q += ") VALUES ()"
a.pushStatement(name, "insert", q)
return q, nil
}
// Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(columns) {
if column.Type == "function" {
q += column.Left + ","
for _, col := range processColumns(cols) {
if col.Type == TokenFunc {
q += col.Left + ","
} else {
q += "[" + column.Left + "],"
q += "[" + col.Left + "],"
}
}
// Remove the trailing comma
q = q[0 : len(q)-1]
q += ") VALUES ("
@ -314,17 +313,17 @@ func (a *MssqlAdapter) SimpleUpsert(name string, table string, columns string, f
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "substitute":
case TokenSub:
q += " ?"
case "function", "operator", "number", "or":
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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
// 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) + ","
if column.Type == "function" {
matched += column.Left + " = f" + strconv.Itoa(columnID) + ","
notMatched += column.Left + ","
if col.Type == TokenFunc {
matched += col.Left + " = f" + strconv.Itoa(columnID) + ","
notMatched += col.Left + ","
} else {
matched += "[" + column.Left + "] = f" + strconv.Itoa(columnID) + ","
notMatched += "[" + column.Left + "],"
matched += "[" + col.Left + "] = f" + strconv.Itoa(columnID) + ","
notMatched += "[" + col.Left + "],"
}
}
@ -376,17 +375,17 @@ func (a *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
q += "[" + item.Column + "]="
for _, token := range item.Expr {
switch token.Type {
case "substitute":
case TokenSub:
q += " ?"
case "function", "operator", "number", "or":
case TokenFunc, TokenOp, TokenNumber, TokenOr:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
@ -394,7 +393,6 @@ func (a *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
}
q += ","
}
// Remove the trailing comma
q = q[0 : len(q)-1]
// 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 _, token := range loc.Expr {
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
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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 _, token := range loc.Expr {
switch token.Type {
case "substitute":
case TokenSub:
q += " ?"
case "function", "operator", "number", "or":
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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 != "" {
return "", errors.New("Orderby needs to be set to use limit on Mssql")
}
substituteCount := 0
subCount := 0
q := ""
// 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 _, token := range loc.Expr {
switch token.Type {
case "substitute":
substituteCount++
q += " ?" + strconv.Itoa(substituteCount)
case "function", "operator", "number", "or":
case TokenSub:
subCount++
q += " ?" + strconv.Itoa(subCount)
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// 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?
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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)
if limiter.Offset != "" {
if limiter.Offset == "?" {
substituteCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS"
subCount++
q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else {
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?
if limiter.MaxCount != "" {
if limiter.MaxCount == "?" {
substituteCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount)
subCount++
limiter.MaxCount = "?" + strconv.Itoa(subCount)
}
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 != "" {
return "", errors.New("Orderby needs to be set to use limit on Mssql")
}
substituteCount := 0
subCount := 0
q := ""
for _, column := range processColumns(columns) {
for _, col := range processColumns(columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "[" + column.Table + "].[" + column.Left + "]"
} else if column.Type == "function" {
source = column.Left
if col.Table != "" {
source = "[" + col.Table + "].[" + col.Left + "]"
} else if col.Type == TokenFunc {
source = col.Left
} else {
source = "[" + column.Left + "]"
source = "[" + col.Left + "]"
}
if column.Alias != "" {
alias = " AS '" + column.Alias + "'"
if col.Alias != "" {
alias = " AS '" + col.Alias + "'"
}
q += source + alias + ","
}
@ -629,23 +627,23 @@ func (a *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string,
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "substitute":
substituteCount++
q += " ?" + strconv.Itoa(substituteCount)
case "function", "operator", "number", "or":
case TokenSub:
subCount++
q += " ?" + strconv.Itoa(subCount)
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
halves := strings.Split(token.Contents, ".")
if len(halves) == 2 {
q += " [" + halves[0] + "].[" + halves[1] + "]"
} else {
q += " [" + token.Contents + "]"
}
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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)
if limiter.Offset != "" {
if limiter.Offset == "?" {
substituteCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS"
subCount++
q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else {
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?
if limiter.MaxCount != "" {
if limiter.MaxCount == "?" {
substituteCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount)
subCount++
limiter.MaxCount = "?" + strconv.Itoa(subCount)
}
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 != "" {
return "", errors.New("Orderby needs to be set to use limit on Mssql")
}
substituteCount := 0
subCount := 0
q := ""
for _, column := range processColumns(columns) {
for _, col := range processColumns(columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "[" + column.Table + "].[" + column.Left + "]"
} else if column.Type == "function" {
source = column.Left
if col.Table != "" {
source = "[" + col.Table + "].[" + col.Left + "]"
} else if col.Type == TokenFunc {
source = col.Left
} else {
source = "[" + column.Left + "]"
source = "[" + col.Left + "]"
}
if column.Alias != "" {
alias = " AS '" + column.Alias + "'"
if col.Alias != "" {
alias = " AS '" + col.Alias + "'"
}
q += source + alias + ","
}
@ -754,23 +752,23 @@ func (a *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "substitute":
substituteCount++
q += " ?" + strconv.Itoa(substituteCount)
case "function", "operator", "number", "or":
case TokenSub:
subCount++
q += " ?" + strconv.Itoa(subCount)
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
halves := strings.Split(token.Contents, ".")
if len(halves) == 2 {
q += " [" + halves[0] + "].[" + halves[1] + "]"
} else {
q += " [" + token.Contents + "]"
}
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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)
if limiter.Offset != "" {
if limiter.Offset == "?" {
substituteCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS"
subCount++
q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else {
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?
if limiter.MaxCount != "" {
if limiter.MaxCount == "?" {
substituteCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount)
subCount++
limiter.MaxCount = "?" + strconv.Itoa(subCount)
}
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 + "] ("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(ins.Columns) {
if column.Type == "function" {
q += column.Left + ","
for _, col := range processColumns(ins.Columns) {
if col.Type == TokenFunc {
q += col.Left + ","
} else {
q += "[" + column.Left + "],"
q += "[" + col.Left + "],"
}
}
q = q[0:len(q)-1] + ") SELECT "
/* Select */
substituteCount := 0
subCount := 0
for _, column := range processColumns(sel.Columns) {
for _, col := range processColumns(sel.Columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Type == "function" || column.Type == "substitute" {
source = column.Left
if col.Type == TokenFunc || col.Type == TokenSub {
source = col.Left
} else {
source = "[" + column.Left + "]"
source = "[" + col.Left + "]"
}
if column.Alias != "" {
alias = " AS [" + column.Alias + "]"
if col.Alias != "" {
alias = " AS [" + col.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 _, token := range loc.Expr {
switch token.Type {
case "substitute":
substituteCount++
q += " ?" + strconv.Itoa(substituteCount)
case "function", "operator", "number", "or":
case TokenSub:
subCount++
q += " ?" + strconv.Itoa(subCount)
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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)
if limiter.Offset != "" {
if limiter.Offset == "?" {
substituteCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS"
subCount++
q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else {
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?
if limiter.MaxCount != "" {
if limiter.MaxCount == "?" {
substituteCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount)
subCount++
limiter.MaxCount = "?" + strconv.Itoa(subCount)
}
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 + "] ("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(ins.Columns) {
if column.Type == "function" {
q += column.Left + ","
for _, col := range processColumns(ins.Columns) {
if col.Type == TokenFunc {
q += col.Left + ","
} else {
q += "[" + column.Left + "],"
q += "[" + col.Left + "],"
}
}
q = q[0:len(q)-1] + ") SELECT "
/* Select */
substituteCount := 0
subCount := 0
for _, column := range processColumns(sel.Columns) {
for _, col := range processColumns(sel.Columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "[" + column.Table + "].[" + column.Left + "]"
} else if column.Type == "function" {
source = column.Left
if col.Table != "" {
source = "[" + col.Table + "].[" + col.Left + "]"
} else if col.Type == TokenFunc {
source = col.Left
} else {
source = "[" + column.Left + "]"
source = "[" + col.Left + "]"
}
if column.Alias != "" {
alias = " AS '" + column.Alias + "'"
if col.Alias != "" {
alias = " AS '" + col.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 _, token := range loc.Expr {
switch token.Type {
case "substitute":
substituteCount++
q += " ?" + strconv.Itoa(substituteCount)
case "function", "operator", "number", "or":
case TokenSub:
subCount++
q += " ?" + strconv.Itoa(subCount)
case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike:
// TODO: Split the function case off to speed things up
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
halves := strings.Split(token.Contents, ".")
if len(halves) == 2 {
q += " [" + halves[0] + "].[" + halves[1] + "]"
} else {
q += " [" + token.Contents + "]"
}
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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)
if limiter.Offset != "" {
if limiter.Offset == "?" {
substituteCount++
q += " OFFSET ?" + strconv.Itoa(substituteCount) + " ROWS"
subCount++
q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS"
} else {
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?
if limiter.MaxCount != "" {
if limiter.MaxCount == "?" {
substituteCount++
limiter.MaxCount = "?" + strconv.Itoa(substituteCount)
subCount++
limiter.MaxCount = "?" + strconv.Itoa(subCount)
}
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")
}
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 == "" {
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 _, token := range loc.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "GETUTCDATE()"
}
q += " " + token.Contents
case "column":
case TokenColumn:
q += " [" + token.Contents + "]"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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
func (adapter *MysqlAdapter) GetName() string {
return adapter.Name
func (a *MysqlAdapter) GetName() string {
return a.Name
}
func (adapter *MysqlAdapter) GetStmt(name string) DBStmt {
return adapter.Buffer[name]
func (a *MysqlAdapter) GetStmt(name string) DBStmt {
return a.Buffer[name]
}
func (adapter *MysqlAdapter) GetStmts() map[string]DBStmt {
return adapter.Buffer
func (a *MysqlAdapter) GetStmts() map[string]DBStmt {
return a.Buffer
}
// 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"]
if !ok {
return nil, ErrNoCollation
@ -78,21 +78,21 @@ func (adapter *MysqlAdapter) BuildConn(config map[string]string) (*sql.DB, error
return db, db.Ping()
}
func (adapter *MysqlAdapter) DbVersion() string {
func (a *MysqlAdapter) DbVersion() string {
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 == "" {
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
adapter.pushStatement(name, "drop-table", querystr)
return querystr, nil
a.pushStatement(name, "drop-table", q)
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 == "" {
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")
}
var querystr = "CREATE TABLE `" + table + "` ("
q := "CREATE TABLE `" + table + "` ("
for _, column := range columns {
column, size, end := adapter.parseColumn(column)
querystr += "\n\t`" + column.Name + "` " + column.Type + size + end + ","
column, size, end := a.parseColumn(column)
q += "\n\t`" + column.Name + "` " + column.Type + size + end + ","
}
if len(keys) > 0 {
for _, key := range keys {
querystr += "\n\t" + key.Type
q += "\n\t" + key.Type
if key.Type != "unique" {
querystr += " key"
q += " key"
}
if key.Type == "foreign" {
cols := strings.Split(key.Columns, ",")
querystr += "(`" + cols[0] + "`) REFERENCES `" + key.FTable + "`(`" + cols[1] + "`)"
q += "(`" + cols[0] + "`) REFERENCES `" + key.FTable + "`(`" + cols[1] + "`)"
if key.Cascade {
querystr += " ON DELETE CASCADE"
q += " ON DELETE CASCADE"
}
querystr += ","
q += ","
} else {
querystr += "("
q += "("
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 != "" {
querystr += " CHARSET=" + charset
q += " CHARSET=" + charset
}
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
adapter.pushStatement(name, "create-table", querystr+";")
return querystr + ";", nil
q += ";"
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
if column.Type == "createdAt" {
column.Type = "datetime"
@ -162,7 +163,7 @@ func (adapter *MysqlAdapter) parseColumn(column DBTableColumn) (col DBTableColum
end = " DEFAULT "
/*if column.Type == "datetime" && column.Default[len(column.Default)-1] == ')' {
end += column.Default
} else */if adapter.stringyType(column.Type) && column.Default != "''" {
} else */if a.stringyType(column.Type) && column.Default != "''" {
end += "'" + column.Default + "'"
} else {
end += column.Default
@ -188,20 +189,20 @@ func (a *MysqlAdapter) AddColumn(name string, table string, column DBTableColumn
}
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 {
querystr += " " + key.Type
q += " " + key.Type
if key.Type != "unique" {
querystr += " key"
q += " key"
} 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
a.pushStatement(name, "add-column", querystr)
return querystr, nil
a.pushStatement(name, "add-column", q)
return q, nil
}
// 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")
}
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
a.pushStatement(name, "add-index", querystr)
return querystr, nil
a.pushStatement(name, "add-index", q)
return q, nil
}
// TODO: Test to make sure everything works here
@ -228,20 +229,20 @@ func (a *MysqlAdapter) AddKey(name string, table string, column string, key DBTa
if table == "" {
return "", errors.New("You need a name for this table")
}
var querystr string
var q string
if key.Type == "fulltext" {
querystr = "ALTER TABLE `" + table + "` ADD FULLTEXT(`" + column + "`)"
q = "ALTER TABLE `" + table + "` ADD FULLTEXT(`" + column + "`)"
} else {
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
a.pushStatement(name, "add-key", querystr)
return querystr, nil
a.pushStatement(name, "add-key", q)
return q, nil
}
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 {
return
}
@ -255,18 +256,19 @@ func (a *MysqlAdapter) AddForeignKey(name string, table string, column string, f
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 {
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
a.pushStatement(name, "add-foreign-key", querystr)
return querystr, nil
a.pushStatement(name, "add-foreign-key", q)
return q, nil
}
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 == "" {
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("`(")
if columns != "" {
sb.WriteString(adapter.buildColumns(columns))
sb.WriteString(a.buildColumns(columns))
sb.WriteString(") VALUES (")
fs := processFields(fields)
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
q := sb.String()
adapter.pushStatement(name, "insert", q)
a.pushStatement(name, "insert", q)
return q, nil
}
func (adapter *MysqlAdapter) buildColumns(columns string) (querystr string) {
func (a *MysqlAdapter) buildColumns(columns string) (q string) {
if columns == "" {
return ""
}
// Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(columns) {
if column.Type == "function" {
querystr += column.Left + ","
for _, col := range processColumns(columns) {
if col.Type == TokenFunc {
q += col.Left + ","
} else {
querystr += "`" + column.Left + "`,"
q += "`" + col.Left + "`,"
}
}
return querystr[0 : len(querystr)-1]
return q[0 : len(q)-1]
}
// ! 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 == "" {
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")
}
var querystr = "REPLACE INTO `" + table + "`(" + adapter.buildColumns(columns) + ") VALUES ("
q := "REPLACE INTO `" + table + "`(" + a.buildColumns(columns) + ") VALUES ("
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
adapter.pushStatement(name, "replace", querystr+")")
return querystr + ")", nil
a.pushStatement(name, "replace", q+")")
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 == "" {
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")
}
var querystr = "INSERT INTO `" + table + "`("
var parsedFields = processFields(fields)
q := "INSERT INTO `" + table + "`("
parsedFields := processFields(fields)
var insertColumns 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]
if column.Type == "function" {
insertColumns += column.Left + ","
if col.Type == TokenFunc {
insertColumns += col.Left + ","
insertValues += field.Name + ","
setBit += column.Left + " = " + field.Name + " AND "
setBit += col.Left + " = " + field.Name + " AND "
} else {
insertColumns += "`" + column.Left + "`,"
insertColumns += "`" + col.Left + "`,"
insertValues += field.Name + ","
setBit += "`" + column.Left + "` = " + field.Name + " AND "
setBit += "`" + col.Left + "` = " + field.Name + " AND "
}
}
insertColumns = insertColumns[0 : len(insertColumns)-1]
@ -381,15 +383,16 @@ func (adapter *MysqlAdapter) SimpleUpsert(name string, table string, columns str
insertColumns += ") VALUES (" + insertValues
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
adapter.pushStatement(name, "upsert", querystr)
return querystr, nil
a.pushStatement(name, "upsert", q)
return q, nil
}
var sulen1 = len("UPDATE `` SET ")
func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
func (a *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
if up.table == "" {
return "", errors.New("You need a name for this table")
}
@ -414,14 +417,14 @@ func (adapter *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error)
sb.WriteString("`=")
for _, token := range item.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr:
sb.WriteString(" ")
sb.WriteString(token.Contents)
case "column":
case TokenColumn:
sb.WriteString(" `")
sb.WriteString(token.Contents)
sb.WriteString("`")
case "string":
case TokenString:
sb.WriteString(" '")
sb.WriteString(token.Contents)
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)
if err != nil {
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
q := sb.String()
adapter.pushStatement(up.name, "update", q)
a.pushStatement(up.name, "update", q)
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 == "" {
return "", errors.New("You need a name for this table")
}
if where == "" {
return "", errors.New("You need to specify what data you want to delete")
}
var q = "DELETE FROM `" + table + "` WHERE"
q := "DELETE FROM `" + table + "` WHERE"
// Add support for BETWEEN x.x
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot:
q += " " + token.Contents
case "column":
case TokenColumn:
q += " `" + token.Contents + "`"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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])
// 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
}
func (adapter *MysqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) {
func (a *MysqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) {
if b.table == "" {
return "", errors.New("You need a name for this table")
}
if b.where == "" && b.dateCutoff == nil {
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 {
return q, err
}
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
adapter.pushStatement(b.name, "delete", q)
a.pushStatement(b.name, "delete", q)
return q, nil
}
// 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 == "" {
return "", errors.New("You need a name for this table")
}
q := "DELETE FROM `" + table + "`"
adapter.pushStatement(name, "purge", q)
a.pushStatement(name, "purge", q)
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 {
return "", nil
}
@ -512,11 +514,11 @@ func (adapter *MysqlAdapter) buildWhere(where string) (q string, err error) {
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents
case "column":
case TokenColumn:
q += " `" + token.Contents + "`"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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
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 {
return "", nil
}
q = " WHERE"
if dateCutoff != nil {
if dateCutoff.Type == 0 {
@ -546,11 +547,11 @@ func (adapter *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutof
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents
case "column":
case TokenColumn:
q += " `" + token.Contents + "`"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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
}
func (adapter *MysqlAdapter) buildOrderby(orderby string) (q string) {
func (a *MysqlAdapter) buildOrderby(orderby string) (q string) {
if len(orderby) != 0 {
q = " ORDER BY "
for _, column := range processOrderby(orderby) {
@ -575,14 +576,14 @@ func (adapter *MysqlAdapter) buildOrderby(orderby string) (q string) {
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 == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleSelect")
}
var q = "SELECT "
q := "SELECT "
// Slice up the user friendly strings into something easier to process
for _, column := range strings.Split(strings.TrimSpace(columns), ",") {
@ -590,14 +591,14 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str
}
q = q[0 : len(q)-1]
whereStr, err := adapter.buildWhere(where)
whereStr, err := a.buildWhere(where)
if err != nil {
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)
adapter.pushStatement(name, "select", q)
a.pushStatement(name, "select", q)
return q, nil
}
@ -611,6 +612,7 @@ func (a *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string,
var cslen1 = len("SELECT FROM ``")
var cslen2 = len(" WHERE `` IN(")
func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.Builder) error {
if preBuilder.table == "" {
return errors.New("You need a name for this table")
@ -728,9 +730,9 @@ func (a *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string
return q, nil
}
func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
func (a *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
sel := up.whereSubQuery
whereStr, err := adapter.buildWhere(sel.where)
whereStr, err := a.buildWhere(sel.where)
if err != nil {
return "", err
}
@ -740,11 +742,11 @@ func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, e
setter += "`" + item.Column + "`="
for _, token := range item.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
setter += " " + token.Contents
case "column":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr:
setter += token.Contents
case TokenColumn:
setter += "`" + token.Contents + "`"
case "string":
case TokenString:
setter += "'" + token.Contents + "'"
}
}
@ -752,65 +754,62 @@ func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, e
}
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) + ")"
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 := "UPDATE `" + up.table + "` SET " + setter + " WHERE (SELECT" + a.buildJoinColumns(sel.columns) + " FROM `" + sel.table + "`" + whereStr + a.buildOrderby(sel.orderby) + a.buildLimit(sel.limit) + ")"
q = strings.TrimSpace(q)
adapter.pushStatement(name, "insert", q)
a.pushStatement(up.name, "update", q)
return q, nil
}
func (adapter *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
whereStr, err := adapter.buildJoinWhere(sel.Where)
func (a *MysqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) {
whereStr, err := a.buildWhere(sel.Where)
if err != nil {
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)
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
}
// TODO: Make this more consistent with the other build* methods?
func (adapter *MysqlAdapter) buildJoiners(joiners string) (q string) {
for _, joiner := range processJoiner(joiners) {
q += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
func (a *MysqlAdapter) buildJoiners(joiners string) (q string) {
for _, j := range processJoiner(joiners) {
q += "`" + j.LeftTable + "`.`" + j.LeftColumn + "` " + j.Operator + " `" + j.RightTable + "`.`" + j.RightColumn + "` AND "
}
// Remove the trailing AND
return q[0 : len(q)-4]
}
// 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 {
q = " WHERE"
for _, loc := range processWhere(where) {
for _, token := range loc.Expr {
switch token.Type {
case "function", "operator", "number", "substitute", "or":
case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents
case "column":
case TokenColumn:
halves := strings.Split(token.Contents, ".")
if len(halves) == 2 {
q += " `" + halves[0] + "`.`" + halves[1] + "`"
} else {
q += " `" + token.Contents + "`"
}
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
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
}
func (adapter *MysqlAdapter) buildLimit(limit string) (q string) {
func (a *MysqlAdapter) buildLimit(limit string) (q string) {
if limit != "" {
q = " LIMIT " + limit
}
return q
}
func (a *MysqlAdapter) buildJoinColumns(columns string) (q string) {
for _, column := range processColumns(columns) {
func (a *MysqlAdapter) buildJoinColumns(cols string) (q string) {
for _, col := range processColumns(cols) {
// TODO: Move the stirng and number logic to processColumns?
// TODO: Error if [0] doesn't exist
firstChar := column.Left[0]
firstChar := col.Left[0]
if firstChar == '\'' {
column.Type = "string"
col.Type = TokenString
} else {
_, err := strconv.Atoi(string(firstChar))
if err == nil {
column.Type = "number"
col.Type = TokenNumber
}
}
// Escape the column names, just in case we've used a reserved keyword
var source = column.Left
if column.Table != "" {
source = "`" + column.Table + "`.`" + source + "`"
} else if column.Type != "function" && column.Type != "number" && column.Type != "substitute" && column.Type != "string" {
source := col.Left
if col.Table != "" {
source = "`" + col.Table + "`.`" + source + "`"
} else if col.Type != TokenFunc && col.Type != TokenNumber && col.Type != TokenSub && col.Type != TokenString {
source = "`" + source + "`"
}
var alias string
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
if col.Alias != "" {
alias = " AS `" + col.Alias + "`"
}
q += " " + source + alias + ","
}
return q[0 : len(q)-1]
}
func (adapter *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
whereStr, err := adapter.buildJoinWhere(sel.Where)
func (a *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
whereStr, err := a.buildJoinWhere(sel.Where)
if err != nil {
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)
adapter.pushStatement(name, "insert", q)
a.pushStatement(name, "insert", q)
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 == "" {
return "", errors.New("You need a name for this table")
}
whereStr, err := adapter.buildWhere(where)
whereStr, err := a.buildWhere(where)
if err != nil {
return "", err
}
q = "SELECT COUNT(*) FROM `" + table + "`" + whereStr + adapter.buildLimit(limit)
q = "SELECT COUNT(*) FROM `" + table + "`" + whereStr + a.buildLimit(limit)
q = strings.TrimSpace(q)
adapter.pushStatement(name, "select", q)
a.pushStatement(name, "select", q)
return q, nil
}
func (adapter *MysqlAdapter) Builder() *prebuilder {
return &prebuilder{adapter}
func (a *MysqlAdapter) Builder() *prebuilder {
return &prebuilder{a}
}
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)
}
func (adapter *MysqlAdapter) stringyType(ctype string) bool {
func (a *MysqlAdapter) stringyType(ctype string) bool {
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"
}

View File

@ -195,23 +195,23 @@ func (a *PgsqlAdapter) SimpleInsert(name string, table string, columns string, f
return q, nil
}
func (a *PgsqlAdapter) buildColumns(columns string) (q string) {
if columns == "" {
func (a *PgsqlAdapter) buildColumns(cols string) (q string) {
if cols == "" {
return ""
}
// Escape the column names, just in case we've used a reserved keyword
for _, column := range processColumns(columns) {
if column.Type == "function" {
q += column.Left + ","
for _, col := range processColumns(cols) {
if col.Type == TokenFunc {
q += col.Left + ","
} else {
q += "\"" + column.Left + "\","
q += "\"" + col.Left + "\","
}
}
return q[0 : len(q)-1]
}
// 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 == "" {
return "", errors.New("You need a name for this table")
}
@ -252,23 +252,22 @@ func (a *PgsqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
q += "`" + item.Column + "`="
for _, token := range item.Expr {
switch token.Type {
case "function":
case TokenFunc:
// TODO: Write a more sophisticated function parser on the utils side.
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()"
}
q += " " + token.Contents
case "operator", "number", "substitute", "or":
case TokenOp, TokenNumber, TokenSub, TokenOr:
q += " " + token.Contents
case "column":
case TokenColumn:
q += " `" + token.Contents + "`"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
}
}
q += ","
}
// Remove the trailing comma
q = q[0 : len(q)-1]
// 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 _, token := range loc.Expr {
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?
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()"
}
q += " " + token.Contents
case "operator", "number", "substitute", "or":
case TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
q += " " + token.Contents
case "column":
case TokenColumn:
q += " `" + token.Contents + "`"
case "string":
case TokenString:
q += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")

View File

@ -57,7 +57,8 @@ type DBColumn struct {
Table string
Left string // Could be a function or a column, so I'm naming this Left
Alias string // aka AS Blah, if it's present
Type string // function or column
//Type string // function or column
Type int
}
type DBField struct {
@ -82,9 +83,22 @@ type DBOrder struct {
Order string
}
const (
TokenFunc = iota
TokenOp
TokenColumn
TokenNumber
TokenString
TokenSub
TokenOr
TokenNot
TokenLike
)
type DBToken struct {
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 {

View File

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

View File

@ -25,7 +25,7 @@ import (
// A blank list to fill out that parameter in Page for routes which don't use it
var tList []interface{}
var successJSONBytes = []byte(`{"success":"1"}`)
var successJSONBytes = []byte(`{"success":1}`)
// TODO: Refactor this
// 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()
for rows.Next() {
var alert c.Alert
err = rows.Scan(&alert.ASID, &alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID, &createdAt)
var al c.Alert
err = rows.Scan(&al.ASID, &al.ActorID, &al.TargetUserID, &al.Event, &al.ElementType, &al.ElementID, &createdAt)
if err != nil {
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()
//log.Print("uCreatedAt", uCreatedAt)
//if rCreatedAt == 0 || rCreatedAt < uCreatedAt {
alerts = append(alerts, alert)
actors = append(actors, alert.ActorID)
alerts = append(alerts, al)
actors = append(actors, al.ActorID)
//}
if uCreatedAt > topCreatedAt {
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 {
h := sha256.New()
h.Write([]byte(c.JSTokenBox.Load().(string)))
h.Write([]byte(user.LastIP))
h.Write([]byte(user.GetIP()))
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+`;
hihi = hihi.replace('ld','Id');
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)
}
username := c.SanitiseSingleLine(r.PostFormValue("username"))
uid, err, requiresExtraAuth := c.Auth.Authenticate(username, r.PostFormValue("password"))
name := c.SanitiseSingleLine(r.PostFormValue("username"))
uid, err, requiresExtraAuth := c.Auth.Authenticate(name, r.PostFormValue("password"))
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?
logItem := &c.LoginLogItem{UID: uid, Success: false, IP: user.LastIP}
logItem := &c.LoginLogItem{UID: uid, Success: false, IP: user.GetIP()}
_, err := logItem.Create()
if err != nil {
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
logItem := &c.LoginLogItem{UID: uid, Success: true, IP: user.LastIP}
logItem := &c.LoginLogItem{UID: uid, Success: true, IP: user.GetIP()}
_, err = logItem.Create()
if err != nil {
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 {
// Is this error check redundant? We already check for the error in PreRoute for the same IP
// 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)
return nil
@ -220,7 +220,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user c.User)
if !c.Config.DisableJSAntispam {
h := sha256.New()
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)) {
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()
if err != nil {
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
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 {
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 {
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 {
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 {
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 {
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)
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 {
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 {
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 {
return c.InternalError(err, w, r)
}
@ -300,7 +300,7 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil {
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 {
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 {
return c.InternalError(err, w, r)
}
if forum.Preset == "" {
forum.Preset = "custom"
}
@ -438,7 +437,7 @@ func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil {
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 {
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 {
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 {
return c.InternalError(err, w, r)
}
@ -276,7 +276,7 @@ func GroupsPromotionsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c
if err != nil {
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 {
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 {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
}
group, err := c.Groups.Get(gid)
if err == sql.ErrNoRows {
//log.Print("aaaaa monsters")
@ -386,24 +385,25 @@ func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid
return ferr
}
gname := r.FormValue("name")
if gname == "" {
name := r.FormValue("name")
if name == "" {
return c.LocalError(p.GetErrorPhrase("panel_groups_need_name"), w, r, user)
}
gtag := r.FormValue("tag")
tag := r.FormValue("tag")
rank := r.FormValue("type")
var originalRank string
// TODO: Use a switch for this
if group.IsAdmin {
switch {
case group.IsAdmin:
originalRank = "Admin"
} else if group.IsMod {
case group.IsMod:
originalRank = "Mod"
} else if group.IsBanned {
case group.IsBanned:
originalRank = "Banned"
} else if group.ID == 6 {
case group.ID == 6:
originalRank = "Guest"
} else {
default:
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 {
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 {
return c.InternalError(err, w, r)
}
@ -489,7 +489,7 @@ func GroupsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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
}
// 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 {
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 {
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 {
return c.InternalError(err, w, r)
}
@ -233,7 +233,7 @@ func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user c.Use
if err != nil {
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 {
return c.InternalError(err, w, r)
}
@ -266,7 +266,7 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil {
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 {
return c.InternalError(err, w, r)
}
@ -300,7 +300,7 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil {
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 {
return c.InternalError(err, w, r)
}
@ -342,7 +342,7 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user c.Us
}
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 {
return c.InternalError(err, w, r)
}
@ -446,7 +446,7 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil {
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 {
return c.InternalError(err, w, r)
}
@ -474,7 +474,7 @@ func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Us
if err != nil {
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 {
return c.InternalError(err, w, r)
}
@ -507,7 +507,7 @@ func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.Us
if err != nil {
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 {
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()
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 {
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)
}
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 {
return c.InternalError(err, w, r)
}
@ -249,7 +249,7 @@ func UsersAvatarRemoveSubmit(w http.ResponseWriter, r *http.Request, user c.User
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 {
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 {
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 {
return c.InternalError(err, w, r)
}
@ -111,7 +111,7 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil {
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 {
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 {
return ferr
}
js := (r.PostFormValue("js") == "1")
js := r.PostFormValue("js") == "1"
if !user.Perms.EditSettings {
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 {
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 {
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 {
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 {
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)
}
// 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 {
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"))
// 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 {
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)
}
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 {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -9,7 +9,6 @@ import (
"io"
//"fmt"
"golang.org/x/image/tiff"
"image"
"image/gif"
"image/jpeg"
@ -21,6 +20,8 @@ import (
"strconv"
"strings"
"golang.org/x/image/tiff"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/counters"
"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 {
fid, err := strconv.Atoi(r.PostFormValue("topic-board"))
fid, err := strconv.Atoi(r.PostFormValue("board"))
if err != nil {
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)
}
tname := c.SanitiseSingleLine(r.PostFormValue("topic-name"))
content := c.PreparseMessage(r.PostFormValue("topic-content"))
tname := c.SanitiseSingleLine(r.PostFormValue("name"))
content := c.PreparseMessage(r.PostFormValue("content"))
// 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 {
switch err {
case c.ErrNoRows:
@ -632,7 +633,7 @@ func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
if err != nil {
return c.PreError("The provided TopicID is not a valid number.", w, r)
}
tids = append(tids, tid)
tids = []int{tid}
}
if len(tids) == 0 {
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)
}
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 {
return c.InternalErrorJSQ(err, w, r, js)
}
// ? - 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 {
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 {
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 {
return err
}
return t.CreateActionReply(action, u.LastIP, u.ID)
return t.CreateActionReply(action, u.GetIP(), u.ID)
}
// 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)
}
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 {
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)
}
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 {
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)
}
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 {
return c.InternalError(err, w, r)
}

View File

@ -7,17 +7,17 @@
<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="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}}
</select></div>
</div>
<div class="formrow">
<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 class="formrow">
<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 class="formrow">
<button form="quick_post_form" name="topic-button" class="formbutton">{{lang "create_topic_create_button"}}</button>

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"}}">
<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"}}" />
<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="topic_meta">
<div class="formrow topic_name_row real_first_child">
<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>

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"}}">
<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"}}" />
<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="topic_meta">
<div class="formrow topic_name_row real_first_child">
<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>

View File

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

View File

@ -59,13 +59,13 @@
<div class="main_form">
<div class="topic_meta">
<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}}
</select></div>
</div>
<div class="formrow topic_name_row">
<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>

View File

@ -1,7 +1,7 @@
<input form="quick_post_form" id="has_poll_input" name="has_poll" value=0 type="hidden" />
<div class="formrow topic_content_row">
<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 class="formrow poll_content_row auto_hide">

View File

@ -141,10 +141,18 @@ func tickLoop(thumbChan chan bool) {
}
}
func dailies() {
func asmMatches() {
// TODO: Find a more efficient way of doing this
err := qgen.NewAcc().Select("activity_stream").Cols("asid").EachInt(func(asid int) error {
count, err := qgen.NewAcc().Count("activity_stream_matches").Where("asid = " + strconv.Itoa(asid)).Total()
acc := qgen.NewAcc()
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 {
return err
}
@ -157,6 +165,10 @@ func dailies() {
if err != nil && err != sql.ErrNoRows {
c.LogError(err)
}
}
func dailies() {
asmMatches()
if c.Config.LogPruneCutoff > -1 {
f := func(tbl string) {
@ -182,16 +194,30 @@ func dailies() {
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: 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
/*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 {
c.LogError(err)
}*/
}
err = c.Meta.Set("lastDaily", strconv.FormatInt(time.Now().Unix(), 10))
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 {
c.LogError(err)
}
}
}