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:
2025-11-06 18:37:32 +00:00
9 changed files with 52 additions and 17 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
View 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
}