Skip to content

Commit 64290a7

Browse files
authored
Escape generated SQL identifiers (#17)
This change escapes dialect-specific quote characters inside generated SQL identifiers. SQLite and PostgreSQL identifiers now double embedded `"` characters, and MySQL identifiers now double embedded backticks. Qualified table names also quote schema components before concatenating them with table names. The same quoting helpers are used for index DDL names. The regression coverage checks DML generated from table metadata and DDL generated from index, schema, and table metadata. Fixes #16.
1 parent c0288ad commit 64290a7

7 files changed

Lines changed: 300 additions & 11 deletions

db.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ func (m *DbMap) createIndexImpl(ctx context.Context, dialect reflect.Type,
101101
s.WriteString(" unique")
102102
}
103103
s.WriteString(" index")
104-
s.WriteString(fmt.Sprintf(" %s on %s", index.IndexName, table.TableName))
104+
s.WriteString(fmt.Sprintf(
105+
" %s on %s",
106+
m.Dialect.QuoteField(index.IndexName),
107+
m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName),
108+
))
105109
if dname := dialect.Name(); dname == "PostgresDialect" && index.IndexType != "" {
106110
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
107111
}
@@ -129,10 +133,14 @@ func (t *TableMap) DropIndex(ctx context.Context, name string) error {
129133
for _, idx := range t.indexes {
130134
if idx.IndexName == name {
131135
s := bytes.Buffer{}
132-
s.WriteString(fmt.Sprintf("DROP INDEX %s", idx.IndexName))
136+
s.WriteString(fmt.Sprintf("DROP INDEX %s", t.dbmap.Dialect.QuoteField(idx.IndexName)))
133137

134138
if dname := dialect.Name(); dname == "MySQLDialect" {
135-
s.WriteString(fmt.Sprintf(" %s %s", t.dbmap.Dialect.DropIndexSuffix(), t.TableName))
139+
s.WriteString(fmt.Sprintf(
140+
" %s %s",
141+
t.dbmap.Dialect.DropIndexSuffix(),
142+
t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName),
143+
))
136144
}
137145
s.WriteString(";")
138146
_, e := t.dbmap.ExecContext(ctx, s.String())

dialect_mysql.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,15 @@ func (d MySQLDialect) InsertAutoIncr(ctx context.Context, exec SqlExecutor, inse
146146
}
147147

148148
func (d MySQLDialect) QuoteField(f string) string {
149-
return "`" + f + "`"
149+
return "`" + strings.ReplaceAll(f, "`", "``") + "`"
150150
}
151151

152152
func (d MySQLDialect) QuotedTableForQuery(schema string, table string) string {
153153
if strings.TrimSpace(schema) == "" {
154154
return d.QuoteField(table)
155155
}
156156

157-
return schema + "." + d.QuoteField(table)
157+
return d.QuoteField(schema) + "." + d.QuoteField(table)
158158
}
159159

160160
func (d MySQLDialect) IfSchemaNotExists(command, schema string) string {

dialect_mysql_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ func TestMySQLDialect(t *testing.T) {
141141

142142
o.Spec("QuoteField", func(tcx testContext) {
143143
tcx.expect(tcx.dialect.QuoteField("foo")).To(matchers.Equal("`foo`"))
144+
tcx.expect(tcx.dialect.QuoteField("fo`o")).To(matchers.Equal("`fo``o`"))
144145
})
145146

146147
o.Group("QuotedTableForQuery", func() {
@@ -149,7 +150,8 @@ func TestMySQLDialect(t *testing.T) {
149150
})
150151

151152
o.Spec("with a supplied schema", func(tcx testContext) {
152-
tcx.expect(tcx.dialect.QuotedTableForQuery("foo", "bar")).To(matchers.Equal("foo.`bar`"))
153+
tcx.expect(tcx.dialect.QuotedTableForQuery("foo", "bar")).To(matchers.Equal("`foo`.`bar`"))
154+
tcx.expect(tcx.dialect.QuotedTableForQuery("fo`o", "ba`r")).To(matchers.Equal("`fo``o`.`ba``r`"))
153155
})
154156
})
155157

dialect_postgres.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,17 @@ func (d PostgresDialect) InsertAutoIncrToTarget(ctx context.Context, exec SqlExe
124124

125125
func (d PostgresDialect) QuoteField(f string) string {
126126
if d.LowercaseFields {
127-
return `"` + strings.ToLower(f) + `"`
127+
f = strings.ToLower(f)
128128
}
129-
return `"` + f + `"`
129+
return `"` + strings.ReplaceAll(f, `"`, `""`) + `"`
130130
}
131131

132132
func (d PostgresDialect) QuotedTableForQuery(schema string, table string) string {
133133
if strings.TrimSpace(schema) == "" {
134134
return d.QuoteField(table)
135135
}
136136

137-
return schema + "." + d.QuoteField(table)
137+
return d.QuoteField(schema) + "." + d.QuoteField(table)
138138
}
139139

140140
func (d PostgresDialect) IfSchemaNotExists(command, schema string) string {

dialect_postgres_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func TestPostgresDialect(t *testing.T) {
120120
o.Spec("By default, case is preserved", func(tcx postgresTestContext) {
121121
tcx.expect(tcx.dialect.QuoteField("Foo")).To(matchers.Equal(`"Foo"`))
122122
tcx.expect(tcx.dialect.QuoteField("bar")).To(matchers.Equal(`"bar"`))
123+
tcx.expect(tcx.dialect.QuoteField(`Fo"o`)).To(matchers.Equal(`"Fo""o"`))
123124
})
124125

125126
o.Group("With LowercaseFields set to true", func() {
@@ -130,6 +131,7 @@ func TestPostgresDialect(t *testing.T) {
130131

131132
o.Spec("fields are lowercased", func(tcx postgresTestContext) {
132133
tcx.expect(tcx.dialect.QuoteField("Foo")).To(matchers.Equal(`"foo"`))
134+
tcx.expect(tcx.dialect.QuoteField(`Fo"O`)).To(matchers.Equal(`"fo""o"`))
133135
})
134136
})
135137
})
@@ -140,7 +142,8 @@ func TestPostgresDialect(t *testing.T) {
140142
})
141143

142144
o.Spec("with a supplied schema", func(tcx postgresTestContext) {
143-
tcx.expect(tcx.dialect.QuotedTableForQuery("foo", "bar")).To(matchers.Equal(`foo."bar"`))
145+
tcx.expect(tcx.dialect.QuotedTableForQuery("foo", "bar")).To(matchers.Equal(`"foo"."bar"`))
146+
tcx.expect(tcx.dialect.QuotedTableForQuery(`fo"o`, `ba"r`)).To(matchers.Equal(`"fo""o"."ba""r"`))
144147
})
145148
})
146149

dialect_sqlite.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"fmt"
1010
"reflect"
11+
"strings"
1112
)
1213

1314
type SqliteDialect struct {
@@ -92,7 +93,7 @@ func (d SqliteDialect) InsertAutoIncr(ctx context.Context, exec SqlExecutor, ins
9293
}
9394

9495
func (d SqliteDialect) QuoteField(f string) string {
95-
return `"` + f + `"`
96+
return `"` + strings.ReplaceAll(f, `"`, `""`) + `"`
9697
}
9798

9899
// sqlite does not have schemas like PostgreSQL does, so just escape it like normal

0 commit comments

Comments
 (0)