Skip to content

Commit 5f09741

Browse files
committed
Add QuoteLiteral function.
There already exists a `QuoteIdentifier` function that helps to escape identifiers that you need to dynamically generate. `QuoteLiteral` does the same, but for literals, which is helpful for DDL statements that utilize literals, as you cannot pass parameters into said statements. This is modeled after the way libpq implements `PQescapeStringInternal` and can be referenced here: https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/interfaces/libpq/fe-exec.c;hb=HEAD#l3336
1 parent bc6a3c0 commit 5f09741

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

conn.go

+33
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,39 @@ func QuoteIdentifier(name string) string {
15001500
return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
15011501
}
15021502

1503+
// QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal
1504+
// to DDL and other statements that do not accept parameters) to be used as part
1505+
// of an SQL statement. For example:
1506+
//
1507+
// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z")
1508+
// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date))
1509+
//
1510+
// Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be
1511+
// replaced by two backslashes (i.e. "\\") and the C-style escape identifier
1512+
// that PostgreSQL provides ('E') will be prepended to the string.
1513+
func QuoteLiteral(literal string) string {
1514+
// This follows the PostgreSQL internal algorithm for handling quoted literals
1515+
// from libpq, which can be found in the "PQEscapeStringInternal" function,
1516+
// which is found in the libpq/fe-exec.c source file:
1517+
// https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/interfaces/libpq/fe-exec.c
1518+
//
1519+
// substitute any single-quotes (') with two single-quotes ('')
1520+
literal = strings.Replace(literal, `'`, `''`, -1)
1521+
// determine if the string has any backslashes (\) in it.
1522+
// if it does, replace any backslashes (\) with two backslashes (\\)
1523+
// then, we need to wrap the entire string with a PostgreSQL
1524+
// C-style escape. Per how "PQEscapeStringInternal" handles this case, we
1525+
// also add a space before the "E"
1526+
if strings.Contains(literal, `\`) {
1527+
literal = strings.Replace(literal, `\`, `\\`, -1)
1528+
literal = ` E'` + literal + `'`
1529+
} else {
1530+
// otherwise, we can just wrap the literal with a pair of single quotes
1531+
literal = `'` + literal + `'`
1532+
}
1533+
return literal
1534+
}
1535+
15031536
func md5s(s string) string {
15041537
h := md5.New()
15051538
h.Write([]byte(s))

conn_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,38 @@ func TestQuoteIdentifier(t *testing.T) {
15541554
}
15551555
}
15561556

1557+
func TestQuoteLiteral(t *testing.T) {
1558+
var cases = []struct {
1559+
input string
1560+
want string
1561+
}{
1562+
{`foo`, `'foo'`},
1563+
{`foo bar baz`, `'foo bar baz'`},
1564+
{`foo'bar`, `'foo''bar'`},
1565+
{`foo\bar`, ` E'foo\\bar'`},
1566+
{`foo\ba'r`, ` E'foo\\ba''r'`},
1567+
{`foo"bar`, `'foo"bar'`},
1568+
{`foo\x00bar`, ` E'foo\\x00bar'`},
1569+
{`\x00foo`, ` E'\\x00foo'`},
1570+
{`'`, `''''`},
1571+
{`''`, `''''''`},
1572+
{`\`, ` E'\\'`},
1573+
{`'abc'; DROP TABLE users;`, `'''abc''; DROP TABLE users;'`},
1574+
{`\'`, ` E'\\'''`},
1575+
{`E'\''`, ` E'E''\\'''''`},
1576+
{`e'\''`, ` E'e''\\'''''`},
1577+
{`E'\'abc\'; DROP TABLE users;'`, ` E'E''\\''abc\\''; DROP TABLE users;'''`},
1578+
{`e'\'abc\'; DROP TABLE users;'`, ` E'e''\\''abc\\''; DROP TABLE users;'''`},
1579+
}
1580+
1581+
for _, test := range cases {
1582+
got := QuoteLiteral(test.input)
1583+
if got != test.want {
1584+
t.Errorf("QuoteLiteral(%q) = %v want %v", test.input, got, test.want)
1585+
}
1586+
}
1587+
}
1588+
15571589
func TestRowsResultTag(t *testing.T) {
15581590
type ResultTag interface {
15591591
Result() driver.Result

0 commit comments

Comments
 (0)