Merge pull request 'Add common whitespace characters, ASCII, HTML, backslashes to literal escaping' (#1) from advanced-literal-escape into main
Reviewed-on: #1
This commit is contained in:
@@ -69,6 +69,7 @@ func AlterTable(s *schema.Table) (*string, error) {
|
||||
}
|
||||
|
||||
ddl := fmt.Sprintf(sql.DDL_ALTER_TABLE, sql.QuoteIdent(s.Name), strings.Join(ddlParts, " "))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -91,6 +92,7 @@ func DropColumn(s *schema.Table, f *schema.Field) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_DROP_COLUMN, sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -129,6 +131,7 @@ func AddForeignKey(s *schema.Table, f *schema.Field) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_ADD_FOREIGN_KEY, sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -140,6 +143,7 @@ func DropForeignKey(s *schema.Table, f *schema.Field) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_DROP_FOREIGN_KEY, sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -151,6 +155,7 @@ func AddConstraint(s *schema.Table, f *schema.Field) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_ADD_CONSTRAINT, sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -162,5 +167,6 @@ func DropConstraint(s *schema.Table, f *schema.Field) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_DROP_CONSTRAINT, sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func CreateTable(s *schema.Table) (*string, error) {
|
||||
}
|
||||
|
||||
if f.Comment != nil {
|
||||
comments = append(comments, fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name), *f.Comment))
|
||||
comments = append(comments, fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s';", sql.QuoteIdent(s.Name), sql.QuoteIdent(f.Name), *f.Comment))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,5 @@ func CreateTable(s *schema.Table) (*string, error) {
|
||||
ddl += "\n" + strings.Join(comments, "\n")
|
||||
}
|
||||
|
||||
log.Printf("DDL: %s", ddl)
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.secnex.io/secnex/pgson/schema"
|
||||
"git.secnex.io/secnex/pgson/sql"
|
||||
@@ -23,6 +22,6 @@ func Delete(s *schema.Table, where map[string]any) (*string, error) {
|
||||
}
|
||||
|
||||
ddl := fmt.Sprintf(sql.DML_DELETE, sql.QuoteIdent(s.Name), whereClause)
|
||||
log.Printf("DDL: %s", ddl)
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ func DropTable(s *schema.Table) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_DROP_TABLE, sql.QuoteIdent(s.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@ func Insert(s *schema.Table, data map[string]any) (*string, error) {
|
||||
values = append(values, sql.QuoteValue(data[k]))
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DML_INSERT_INTO, sql.QuoteIdent(s.Name), strings.Join(cols, ", "), strings.Join(values, ", "))
|
||||
log.Printf("DDL: %s", ddl)
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ func TruncateTable(s *schema.Table) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
ddl := fmt.Sprintf(sql.DDL_TRUNCATE_TABLE, sql.QuoteIdent(s.Name))
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func Update(s *schema.Table, data map[string]any, where map[string]any) (*string
|
||||
}
|
||||
|
||||
ddl := fmt.Sprintf(sql.DML_UPDATE, sql.QuoteIdent(s.Name), strings.Join(setParts, ", "), whereClause)
|
||||
log.Printf("DDL: %s", ddl)
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -59,6 +59,6 @@ func UpdateDeletedAt(s *schema.Table, where map[string]any) (*string, error) {
|
||||
}
|
||||
|
||||
ddl := fmt.Sprintf(sql.DML_UPDATE, sql.QuoteIdent(s.Name), "deleted_at = CURRENT_TIMESTAMP", whereClause)
|
||||
log.Printf("DDL: %s", ddl)
|
||||
|
||||
return &ddl, nil
|
||||
}
|
||||
|
||||
@@ -120,14 +120,36 @@ func QuoteIdent(name string) string {
|
||||
return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
|
||||
}
|
||||
|
||||
// QuoteLiteral escapes a string literal for use in SQL queries
|
||||
// It doubles single quotes to prevent SQL injection
|
||||
func QuoteLiteral(s string) string {
|
||||
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
|
||||
s = strings.ReplaceAll(s, "\x00", "")
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(len(s) + 2)
|
||||
b.WriteByte('\'')
|
||||
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\'':
|
||||
b.WriteString("''")
|
||||
case '\\':
|
||||
b.WriteString("\\\\")
|
||||
case '\x00':
|
||||
continue
|
||||
case '\t', '\n', '\r':
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
if r < 0x20 {
|
||||
b.WriteString(fmt.Sprintf("\\%03o", r))
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\'')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// QuoteValue formats a value according to its type for use in SQL queries
|
||||
// It handles strings, numbers, booleans, null, and UUIDs safely
|
||||
func QuoteValue(v any) string {
|
||||
if v == nil {
|
||||
return "NULL"
|
||||
@@ -135,7 +157,6 @@ func QuoteValue(v any) string {
|
||||
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
// Check if it's a valid UUID format
|
||||
if matched, _ := regexp.MatchString(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, strings.ToLower(val)); matched {
|
||||
return QuoteLiteral(val)
|
||||
}
|
||||
@@ -150,14 +171,10 @@ func QuoteValue(v any) string {
|
||||
}
|
||||
return "FALSE"
|
||||
default:
|
||||
// For unknown types, convert to string and escape
|
||||
return QuoteLiteral(fmt.Sprintf("%v", val))
|
||||
}
|
||||
}
|
||||
|
||||
// BuildWhereClause safely builds a WHERE clause from conditions
|
||||
// conditions is a map of column names to values (uses = operator)
|
||||
// Returns empty string if no conditions provided
|
||||
func BuildWhereClause(conditions map[string]any) (string, error) {
|
||||
if len(conditions) == 0 {
|
||||
return "", nil
|
||||
|
||||
12
utils/string.go
Normal file
12
utils/string.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
func String(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
func Pointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
Reference in New Issue
Block a user