Skip to content

Commit ceb88a0

Browse files
authored
Merge pull request #852 from jkatz/quote-literal
Add `QuoteLiteral` function.
2 parents bc6a3c0 + 5f09741 commit ceb88a0

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)