Improve quote policy #1567
|
@ -41,6 +41,7 @@ type Dialect interface {
|
||||||
|
|
||||||
IsReserved(string) bool
|
IsReserved(string) bool
|
||||||
Quoter() schemas.Quoter
|
Quoter() schemas.Quoter
|
||||||
|
SetQuotePolicy(quotePolicy QuotePolicy)
|
||||||
|
|
||||||
AutoIncrStr() string
|
AutoIncrStr() string
|
||||||
|
|
||||||
|
@ -79,6 +80,11 @@ type Base struct {
|
||||||
db *core.DB
|
db *core.DB
|
||||||
dialect Dialect
|
dialect Dialect
|
||||||
uri *URI
|
uri *URI
|
||||||
|
quoter schemas.Quoter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) Quoter() schemas.Quoter {
|
||||||
|
return b.quoter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) DB() *core.DB {
|
func (b *Base) DB() *core.DB {
|
||||||
|
@ -210,7 +216,7 @@ func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string {
|
||||||
idxName = index.XName(tableName)
|
idxName = index.XName(tableName)
|
||||||
return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique,
|
return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique,
|
||||||
quoter.Quote(idxName), quoter.Quote(tableName),
|
quoter.Quote(idxName), quoter.Quote(tableName),
|
||||||
quoter.Quote(strings.Join(index.Cols, quoter.ReverseQuote(","))))
|
quoter.Join(index.Cols, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string {
|
func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string {
|
||||||
|
@ -258,7 +264,7 @@ func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, char
|
||||||
|
|
||||||
if len(pkList) > 1 {
|
if len(pkList) > 1 {
|
||||||
sql += "PRIMARY KEY ( "
|
sql += "PRIMARY KEY ( "
|
||||||
sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(",")))
|
sql += quoter.Join(pkList, ",")
|
||||||
sql += " ), "
|
sql += " ), "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,13 @@ func (s *QuoteFilter) Do(sql string) string {
|
||||||
return sql
|
return sql
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix, suffix := s.quoter[0][0], s.quoter[1][0]
|
|
||||||
raw := []byte(sql)
|
raw := []byte(sql)
|
||||||
for i, cnt := 0, 0; i < len(raw); i = i + 1 {
|
for i, cnt := 0, 0; i < len(raw); i = i + 1 {
|
||||||
if raw[i] == '`' {
|
if raw[i] == '`' {
|
||||||
if cnt%2 == 0 {
|
if cnt%2 == 0 {
|
||||||
raw[i] = prefix
|
raw[i] = s.quoter.Prefix
|
||||||
} else {
|
} else {
|
||||||
raw[i] = suffix
|
raw[i] = s.quoter.Suffix
|
||||||
}
|
}
|
||||||
cnt++
|
cnt++
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,13 +204,17 @@ var (
|
||||||
"EXIT": true,
|
"EXIT": true,
|
||||||
"PROC": true,
|
"PROC": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mssqlQuoter = schemas.Quoter{'[', ']', schemas.AlwaysReverse}
|
||||||
)
|
)
|
||||||
|
|
||||||
type mssql struct {
|
type mssql struct {
|
||||||
Base
|
Base
|
||||||
|
quoter schemas.Quoter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mssql) Init(d *core.DB, uri *URI) error {
|
func (db *mssql) Init(d *core.DB, uri *URI) error {
|
||||||
|
db.quoter = mssqlQuoter
|
||||||
return db.Base.Init(d, db, uri)
|
return db.Base.Init(d, db, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,12 +287,25 @@ func (db *mssql) SupportInsertMany() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mssql) IsReserved(name string) bool {
|
func (db *mssql) IsReserved(name string) bool {
|
||||||
_, ok := mssqlReservedWords[name]
|
_, ok := mssqlReservedWords[strings.ToUpper(name)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mssql) Quoter() schemas.Quoter {
|
func (db *mssql) SetQuotePolicy(quotePolicy QuotePolicy) {
|
||||||
return schemas.Quoter{"[", "]"}
|
switch quotePolicy {
|
||||||
|
case QuotePolicyNone:
|
||||||
|
var q = mssqlQuoter
|
||||||
|
q.IsReverse = schemas.AlwaysNoReverse
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyReserved:
|
||||||
|
var q = mssqlQuoter
|
||||||
|
q.IsReverse = db.IsReserved
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyAlways:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
db.quoter = mssqlQuoter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mssql) SupportEngine() bool {
|
func (db *mssql) SupportEngine() bool {
|
||||||
|
|
|
@ -161,6 +161,8 @@ var (
|
||||||
"YEAR_MONTH": true,
|
"YEAR_MONTH": true,
|
||||||
"ZEROFILL": true,
|
"ZEROFILL": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mysqlQuoter = schemas.Quoter{'`', '`', schemas.AlwaysReverse}
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysql struct {
|
type mysql struct {
|
||||||
|
@ -178,6 +180,7 @@ type mysql struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mysql) Init(d *core.DB, uri *URI) error {
|
func (db *mysql) Init(d *core.DB, uri *URI) error {
|
||||||
|
db.quoter = mysqlQuoter
|
||||||
return db.Base.Init(d, db, uri)
|
return db.Base.Init(d, db, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,14 +275,10 @@ func (db *mysql) SupportInsertMany() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mysql) IsReserved(name string) bool {
|
func (db *mysql) IsReserved(name string) bool {
|
||||||
_, ok := mysqlReservedWords[name]
|
_, ok := mysqlReservedWords[strings.ToUpper(name)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *mysql) Quoter() schemas.Quoter {
|
|
||||||
return schemas.Quoter{"`", "`"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *mysql) SupportEngine() bool {
|
func (db *mysql) SupportEngine() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -458,6 +457,23 @@ func (db *mysql) GetTables(ctx context.Context) ([]*schemas.Table, error) {
|
||||||
return tables, nil
|
return tables, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) {
|
||||||
|
switch quotePolicy {
|
||||||
|
case QuotePolicyNone:
|
||||||
|
var q = mysqlQuoter
|
||||||
|
q.IsReverse = schemas.AlwaysNoReverse
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyReserved:
|
||||||
|
var q = mysqlQuoter
|
||||||
|
q.IsReverse = db.IsReserved
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyAlways:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
db.quoter = mysqlQuoter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (db *mysql) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) {
|
func (db *mysql) GetIndexes(ctx context.Context, tableName string) (map[string]*schemas.Index, error) {
|
||||||
args := []interface{}{db.uri.DBName, tableName}
|
args := []interface{}{db.uri.DBName, tableName}
|
||||||
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
|
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
|
||||||
|
@ -538,7 +554,7 @@ func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, ch
|
||||||
|
|
||||||
if len(pkList) > 1 {
|
if len(pkList) > 1 {
|
||||||
sql += "PRIMARY KEY ( "
|
sql += "PRIMARY KEY ( "
|
||||||
sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(",")))
|
sql += quoter.Join(pkList, ",")
|
||||||
sql += " ), "
|
sql += " ), "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -498,6 +498,8 @@ var (
|
||||||
"YEAR": true,
|
"YEAR": true,
|
||||||
"ZONE": true,
|
"ZONE": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oracleQuoter = schemas.Quoter{'[', ']', schemas.AlwaysReverse}
|
||||||
)
|
)
|
||||||
|
|
||||||
type oracle struct {
|
type oracle struct {
|
||||||
|
@ -505,6 +507,7 @@ type oracle struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *oracle) Init(d *core.DB, uri *URI) error {
|
func (db *oracle) Init(d *core.DB, uri *URI) error {
|
||||||
|
db.quoter = oracleQuoter
|
||||||
return db.Base.Init(d, db, uri)
|
return db.Base.Init(d, db, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,14 +552,10 @@ func (db *oracle) SupportInsertMany() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *oracle) IsReserved(name string) bool {
|
func (db *oracle) IsReserved(name string) bool {
|
||||||
_, ok := oracleReservedWords[name]
|
_, ok := oracleReservedWords[strings.ToUpper(name)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *oracle) Quoter() schemas.Quoter {
|
|
||||||
return schemas.Quoter{"\"", "\""}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *oracle) SupportEngine() bool {
|
func (db *oracle) SupportEngine() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -601,7 +600,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c
|
||||||
|
|
||||||
if len(pkList) > 0 {
|
if len(pkList) > 0 {
|
||||||
sql += "PRIMARY KEY ( "
|
sql += "PRIMARY KEY ( "
|
||||||
sql += quoter.Quote(strings.Join(pkList, quoter.ReverseQuote(",")))
|
sql += quoter.Join(pkList, ",")
|
||||||
sql += " ), "
|
sql += " ), "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,6 +619,23 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, c
|
||||||
return sql
|
return sql
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) {
|
||||||
|
switch quotePolicy {
|
||||||
|
case QuotePolicyNone:
|
||||||
|
var q = oracleQuoter
|
||||||
|
q.IsReverse = schemas.AlwaysNoReverse
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyReserved:
|
||||||
|
var q = oracleQuoter
|
||||||
|
q.IsReverse = db.IsReserved
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyAlways:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
db.quoter = oracleQuoter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
|
func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
|
||||||
args := []interface{}{tableName, idxName}
|
args := []interface{}{tableName, idxName}
|
||||||
return `SELECT INDEX_NAME FROM USER_INDEXES ` +
|
return `SELECT INDEX_NAME FROM USER_INDEXES ` +
|
||||||
|
|
|
@ -766,6 +766,8 @@ var (
|
||||||
"YES": true,
|
"YES": true,
|
||||||
"ZONE": true,
|
"ZONE": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postgresQuoter = schemas.Quoter{'"', '"', schemas.AlwaysReverse}
|
||||||
)
|
)
|
||||||
|
|
||||||
const postgresPublicSchema = "public"
|
const postgresPublicSchema = "public"
|
||||||
|
@ -775,6 +777,7 @@ type postgres struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *postgres) Init(d *core.DB, uri *URI) error {
|
func (db *postgres) Init(d *core.DB, uri *URI) error {
|
||||||
|
db.quoter = postgresQuoter
|
||||||
err := db.Base.Init(d, db, uri)
|
err := db.Base.Init(d, db, uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -785,6 +788,23 @@ func (db *postgres) Init(d *core.DB, uri *URI) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) {
|
||||||
|
switch quotePolicy {
|
||||||
|
case QuotePolicyNone:
|
||||||
|
var q = postgresQuoter
|
||||||
|
q.IsReverse = schemas.AlwaysNoReverse
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyReserved:
|
||||||
|
var q = postgresQuoter
|
||||||
|
q.IsReverse = db.IsReserved
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyAlways:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
db.quoter = postgresQuoter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (db *postgres) DefaultSchema() string {
|
func (db *postgres) DefaultSchema() string {
|
||||||
return postgresPublicSchema
|
return postgresPublicSchema
|
||||||
}
|
}
|
||||||
|
@ -857,14 +877,10 @@ func (db *postgres) SupportInsertMany() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *postgres) IsReserved(name string) bool {
|
func (db *postgres) IsReserved(name string) bool {
|
||||||
_, ok := postgresReservedWords[name]
|
_, ok := postgresReservedWords[strings.ToUpper(name)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *postgres) Quoter() schemas.Quoter {
|
|
||||||
return schemas.Quoter{`"`, `"`}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *postgres) AutoIncrStr() string {
|
func (db *postgres) AutoIncrStr() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
15
dialects/quote.go
Normal file
15
dialects/quote.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2020 The Xorm Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package dialects
|
||||||
|
|
||||||
|
// QuotePolicy describes quote handle policy
|
||||||
|
type QuotePolicy int
|
||||||
|
|
||||||
|
// All QuotePolicies
|
||||||
|
const (
|
||||||
|
QuotePolicyAlways QuotePolicy = iota
|
||||||
|
QuotePolicyNone
|
||||||
|
QuotePolicyReserved
|
||||||
|
)
|
|
@ -143,6 +143,8 @@ var (
|
||||||
"WITH": true,
|
"WITH": true,
|
||||||
"WITHOUT": true,
|
"WITHOUT": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlite3Quoter = schemas.Quoter{'`', '`', schemas.AlwaysReverse}
|
||||||
)
|
)
|
||||||
|
|
||||||
type sqlite3 struct {
|
type sqlite3 struct {
|
||||||
|
@ -150,9 +152,27 @@ type sqlite3 struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *sqlite3) Init(d *core.DB, uri *URI) error {
|
func (db *sqlite3) Init(d *core.DB, uri *URI) error {
|
||||||
|
db.quoter = sqlite3Quoter
|
||||||
return db.Base.Init(d, db, uri)
|
return db.Base.Init(d, db, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *sqlite3) SetQuotePolicy(quotePolicy QuotePolicy) {
|
||||||
|
switch quotePolicy {
|
||||||
|
case QuotePolicyNone:
|
||||||
|
var q = sqlite3Quoter
|
||||||
|
q.IsReverse = schemas.AlwaysNoReverse
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyReserved:
|
||||||
|
var q = sqlite3Quoter
|
||||||
|
q.IsReverse = db.IsReserved
|
||||||
|
db.quoter = q
|
||||||
|
case QuotePolicyAlways:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
db.quoter = sqlite3Quoter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (db *sqlite3) SQLType(c *schemas.Column) string {
|
func (db *sqlite3) SQLType(c *schemas.Column) string {
|
||||||
switch t := c.SQLType.Name; t {
|
switch t := c.SQLType.Name; t {
|
||||||
case schemas.Bool:
|
case schemas.Bool:
|
||||||
|
@ -196,14 +216,10 @@ func (db *sqlite3) SupportInsertMany() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *sqlite3) IsReserved(name string) bool {
|
func (db *sqlite3) IsReserved(name string) bool {
|
||||||
_, ok := sqlite3ReservedWords[name]
|
_, ok := sqlite3ReservedWords[strings.ToUpper(name)]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *sqlite3) Quoter() schemas.Quoter {
|
|
||||||
return schemas.Quoter{"`", "`"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *sqlite3) AutoIncrStr() string {
|
func (db *sqlite3) AutoIncrStr() string {
|
||||||
return "AUTOINCREMENT"
|
return "AUTOINCREMENT"
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,10 @@ func (engine *Engine) GetCacher(tableName string) caches.Cacher {
|
||||||
return engine.cacherMgr.GetCacher(tableName)
|
return engine.cacherMgr.GetCacher(tableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) SetQuotePolicy(quotePolicy dialects.QuotePolicy) {
|
||||||
|
engine.dialect.SetQuotePolicy(quotePolicy)
|
||||||
|
}
|
||||||
|
|
||||||
// BufferSize sets buffer size for iterate
|
// BufferSize sets buffer size for iterate
|
||||||
func (engine *Engine) BufferSize(size int) *Session {
|
func (engine *Engine) BufferSize(size int) *Session {
|
||||||
session := engine.NewSession()
|
session := engine.NewSession()
|
||||||
|
|
269
schemas/quote.go
269
schemas/quote.go
|
@ -8,14 +8,29 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Quoter represents two quote characters
|
// Quoter represents a quoter to the SQL table name and column name
|
||||||
type Quoter [2]string
|
type Quoter struct {
|
||||||
|
Prefix byte
|
||||||
|
Suffix byte
|
||||||
|
IsReverse func(string) bool
|
||||||
|
}
|
||||||
|
|
||||||
// CommonQuoter represetns a common quoter
|
var (
|
||||||
var CommonQuoter = Quoter{"`", "`"}
|
// AlwaysFalseReverse always think it's not a reverse word
|
||||||
|
AlwaysNoReverse = func(string) bool { return false }
|
||||||
|
|
||||||
|
// AlwaysReverse always reverse the word
|
||||||
|
AlwaysReverse = func(string) bool { return true }
|
||||||
|
|
||||||
|
// CommanQuoteMark represnets the common quote mark
|
||||||
|
CommanQuoteMark byte = '`'
|
||||||
|
|
||||||
|
// CommonQuoter represetns a common quoter
|
||||||
|
CommonQuoter = Quoter{CommanQuoteMark, CommanQuoteMark, AlwaysReverse}
|
||||||
|
)
|
||||||
|
|
||||||
func (q Quoter) IsEmpty() bool {
|
func (q Quoter) IsEmpty() bool {
|
||||||
return q[0] == "" && q[1] == ""
|
return q.Prefix == 0 && q.Suffix == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q Quoter) Quote(s string) string {
|
func (q Quoter) Quote(s string) string {
|
||||||
|
@ -24,42 +39,6 @@ func (q Quoter) Quote(s string) string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q Quoter) Replace(sql string, newQuoter Quoter) string {
|
|
||||||
if q.IsEmpty() {
|
|
||||||
return sql
|
|
||||||
}
|
|
||||||
|
|
||||||
if newQuoter.IsEmpty() {
|
|
||||||
var buf strings.Builder
|
|
||||||
for i := 0; i < len(sql); i = i + 1 {
|
|
||||||
if sql[i] != q[0][0] && sql[i] != q[1][0] {
|
|
||||||
_ = buf.WriteByte(sql[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix, suffix := newQuoter[0][0], newQuoter[1][0]
|
|
||||||
var buf strings.Builder
|
|
||||||
for i, cnt := 0, 0; i < len(sql); i = i + 1 {
|
|
||||||
if cnt == 0 && sql[i] == q[0][0] {
|
|
||||||
_ = buf.WriteByte(prefix)
|
|
||||||
cnt = 1
|
|
||||||
} else if cnt == 1 && sql[i] == q[1][0] {
|
|
||||||
_ = buf.WriteByte(suffix)
|
|
||||||
cnt = 0
|
|
||||||
} else {
|
|
||||||
_ = buf.WriteByte(sql[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q Quoter) ReverseQuote(s string) string {
|
|
||||||
reverseQuoter := Quoter{q[1], q[0]}
|
|
||||||
return reverseQuoter.Quote(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim removes quotes from s
|
// Trim removes quotes from s
|
||||||
func (q Quoter) Trim(s string) string {
|
func (q Quoter) Trim(s string) string {
|
||||||
if len(s) < 2 {
|
if len(s) < 2 {
|
||||||
|
@ -69,10 +48,10 @@ func (q Quoter) Trim(s string) string {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
switch {
|
switch {
|
||||||
case i == 0 && s[i:i+1] == q[0]:
|
case i == 0 && s[i] == q.Prefix:
|
||||||
case i == len(s)-1 && s[i:i+1] == q[1]:
|
case i == len(s)-1 && s[i] == q.Suffix:
|
||||||
case s[i:i+1] == q[1] && s[i+1] == '.':
|
case s[i] == q.Suffix && s[i+1] == '.':
|
||||||
case s[i:i+1] == q[0] && s[i-1] == '.':
|
case s[i] == q.Prefix && s[i-1] == '.':
|
||||||
default:
|
default:
|
||||||
buf.WriteByte(s[i])
|
buf.WriteByte(s[i])
|
||||||
}
|
}
|
||||||
|
@ -81,31 +60,8 @@ func (q Quoter) Trim(s string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q Quoter) Join(a []string, sep string) string {
|
func (q Quoter) Join(a []string, sep string) string {
|
||||||
switch len(a) {
|
|
||||||
case 0:
|
|
||||||
return ""
|
|
||||||
case 1:
|
|
||||||
return a[0]
|
|
||||||
}
|
|
||||||
n := len(sep) * (len(a) - 1)
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
n += len(a[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.Grow(n)
|
q.JoinWrite(&b, a, sep)
|
||||||
for i, s := range a {
|
|
||||||
if i > 0 {
|
|
||||||
b.WriteString(sep)
|
|
||||||
}
|
|
||||||
if q[0] != "" && s != "*" {
|
|
||||||
b.WriteString(q[0])
|
|
||||||
}
|
|
||||||
b.WriteString(strings.TrimSpace(s))
|
|
||||||
if q[1] != "" && s != "*" {
|
|
||||||
b.WriteString(q[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,23 +82,113 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if q[0] != "" && s != "*" && s[0] != '`' {
|
if s != "*" {
|
||||||
if _, err := b.WriteString(q[0]); err != nil {
|
q.QuoteTo(b, strings.TrimSpace(s))
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := b.WriteString(strings.TrimSpace(s)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if q[1] != "" && s != "*" && s[0] != '`' {
|
|
||||||
if _, err := b.WriteString(q[1]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findWord(v string, start int) int {
|
||||||
|
for j := start; j < len(v); j++ {
|
||||||
|
switch v[j] {
|
||||||
|
case '.', ' ':
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStart(value string, start int) int {
|
||||||
|
if value[start] == '.' {
|
||||||
|
return start + 1
|
||||||
|
}
|
||||||
|
if value[start] != ' ' {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
var k int
|
||||||
|
for j := start; j < len(value); j++ {
|
||||||
|
if value[j] != ' ' {
|
||||||
|
k = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if k-1 == len(value) {
|
||||||
|
return len(value)
|
||||||
|
}
|
||||||
|
if (value[k] == 'A' || value[k] == 'a') && (value[k+1] == 'S' || value[k+1] == 's') {
|
||||||
|
k = k + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := k; j < len(value); j++ {
|
||||||
|
if value[j] != ' ' {
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
|
||||||
|
var realWord = word
|
||||||
|
if (word[0] == CommanQuoteMark && word[len(word)-1] == CommanQuoteMark) ||
|
||||||
|
(word[0] == q.Prefix && word[len(word)-1] == q.Suffix) {
|
||||||
|
realWord = word[1 : len(word)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if q.IsEmpty() {
|
||||||
|
_, err := buf.WriteString(realWord)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isReverse := q.IsReverse(realWord)
|
||||||
|
if isReverse {
|
||||||
|
if err := buf.WriteByte(q.Prefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := buf.WriteString(realWord); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isReverse {
|
||||||
|
return buf.WriteByte(q.Suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoteTo quotes the table or column names. i.e. if the quotes are [ and ]
|
||||||
|
// name -> [name]
|
||||||
|
// `name` -> [name]
|
||||||
|
// [name] -> [name]
|
||||||
|
// schema.name -> [schema].[name]
|
||||||
|
// `schema`.`name` -> [schema].[name]
|
||||||
|
// `schema`.name -> [schema].[name]
|
||||||
|
// schema.`name` -> [schema].[name]
|
||||||
|
// [schema].name -> [schema].[name]
|
||||||
|
// schema.[name] -> [schema].[name]
|
||||||
|
// name AS a -> [name] AS a
|
||||||
|
// schema.name AS a -> [schema].[name] AS a
|
||||||
|
func (q Quoter) QuoteTo(buf *strings.Builder, value string) error {
|
||||||
|
var i int
|
||||||
|
for i < len(value) {
|
||||||
|
start := findStart(value, i)
|
||||||
|
if start > i {
|
||||||
|
if _, err := buf.WriteString(value[i:start]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nextEnd = findWord(value, start)
|
||||||
|
|
||||||
|
if err := q.quoteWordTo(buf, value[start:nextEnd]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i = nextEnd
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings quotes a slice of string
|
||||||
func (q Quoter) Strings(s []string) []string {
|
func (q Quoter) Strings(s []string) []string {
|
||||||
var res = make([]string, 0, len(s))
|
var res = make([]string, 0, len(s))
|
||||||
for _, a := range s {
|
for _, a := range s {
|
||||||
|
@ -150,64 +196,3 @@ func (q Quoter) Strings(s []string) []string {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q Quoter) QuoteTo(buf *strings.Builder, value string) {
|
|
||||||
if q.IsEmpty() {
|
|
||||||
buf.WriteString(value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix, suffix := q[0][0], q[1][0]
|
|
||||||
lastCh := 0 // 0 prefix, 1 char, 2 suffix
|
|
||||||
i := 0
|
|
||||||
for i < len(value) {
|
|
||||||
// start of a token; might be already quoted
|
|
||||||
if value[i] == '.' {
|
|
||||||
_ = buf.WriteByte('.')
|
|
||||||
lastCh = 1
|
|
||||||
i++
|
|
||||||
} else if value[i] == prefix || value[i] == '`' {
|
|
||||||
// Has quotes; skip/normalize `name` to prefix+name+sufix
|
|
||||||
var ch byte
|
|
||||||
if value[i] == prefix {
|
|
||||||
ch = suffix
|
|
||||||
} else {
|
|
||||||
ch = '`'
|
|
||||||
}
|
|
||||||
_ = buf.WriteByte(prefix)
|
|
||||||
i++
|
|
||||||
lastCh = 0
|
|
||||||
for ; i < len(value) && value[i] != ch && value[i] != ' '; i++ {
|
|
||||||
_ = buf.WriteByte(value[i])
|
|
||||||
lastCh = 1
|
|
||||||
}
|
|
||||||
_ = buf.WriteByte(suffix)
|
|
||||||
lastCh = 2
|
|
||||||
i++
|
|
||||||
} else if value[i] == ' ' {
|
|
||||||
if lastCh != 2 {
|
|
||||||
_ = buf.WriteByte(suffix)
|
|
||||||
lastCh = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// a AS b or a b
|
|
||||||
for ; i < len(value); i++ {
|
|
||||||
if value[i] != ' ' && value[i-1] == ' ' && (len(value) > i+1 && !strings.EqualFold(value[i:i+2], "AS")) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = buf.WriteByte(value[i])
|
|
||||||
lastCh = 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Requires quotes
|
|
||||||
_ = buf.WriteByte(prefix)
|
|
||||||
for ; i < len(value) && value[i] != '.' && value[i] != ' '; i++ {
|
|
||||||
_ = buf.WriteByte(value[i])
|
|
||||||
lastCh = 1
|
|
||||||
}
|
|
||||||
_ = buf.WriteByte(suffix)
|
|
||||||
lastCh = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,54 +11,125 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestQuoteTo(t *testing.T) {
|
func TestAlwaysQuoteTo(t *testing.T) {
|
||||||
var quoter = Quoter{"[", "]"}
|
var (
|
||||||
|
quoter = Quoter{'[', ']', AlwaysReverse}
|
||||||
|
kases = []struct {
|
||||||
|
expected string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"[mytable]", "mytable"},
|
||||||
|
{"[mytable]", "`mytable`"},
|
||||||
|
{"[mytable]", `[mytable]`},
|
||||||
|
{`["mytable"]`, `"mytable"`},
|
||||||
|
{"[myschema].[mytable]", "myschema.mytable"},
|
||||||
|
{"[myschema].[mytable]", "`myschema`.mytable"},
|
||||||
|
{"[myschema].[mytable]", "myschema.`mytable`"},
|
||||||
|
{"[myschema].[mytable]", "`myschema`.`mytable`"},
|
||||||
|
{"[myschema].[mytable]", `[myschema].mytable`},
|
||||||
|
{"[myschema].[mytable]", `myschema.[mytable]`},
|
||||||
|
{"[myschema].[mytable]", `[myschema].[mytable]`},
|
||||||
|
{`["myschema].[mytable"]`, `"myschema.mytable"`},
|
||||||
|
{"[message_user] AS [sender]", "`message_user` AS `sender`"},
|
||||||
|
{"[myschema].[mytable] AS [table]", "myschema.mytable AS table"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test := func(t *testing.T, expected string, value string) {
|
for _, v := range kases {
|
||||||
buf := &strings.Builder{}
|
t.Run(v.value, func(t *testing.T) {
|
||||||
quoter.QuoteTo(buf, value)
|
buf := &strings.Builder{}
|
||||||
assert.EqualValues(t, expected, buf.String())
|
quoter.QuoteTo(buf, v.value)
|
||||||
|
assert.EqualValues(t, v.expected, buf.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test(t, "[mytable]", "mytable")
|
func TestReversedQuoteTo(t *testing.T) {
|
||||||
test(t, "[mytable]", "`mytable`")
|
var (
|
||||||
test(t, "[mytable]", `[mytable]`)
|
quoter = Quoter{'[', ']', func(s string) bool {
|
||||||
|
if s == "mytable" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}}
|
||||||
|
kases = []struct {
|
||||||
|
expected string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"[mytable]", "mytable"},
|
||||||
|
{"[mytable]", "`mytable`"},
|
||||||
|
{"[mytable]", `[mytable]`},
|
||||||
|
{`"mytable"`, `"mytable"`},
|
||||||
|
{"myschema.[mytable]", "myschema.mytable"},
|
||||||
|
{"myschema.[mytable]", "`myschema`.mytable"},
|
||||||
|
{"myschema.[mytable]", "myschema.`mytable`"},
|
||||||
|
{"myschema.[mytable]", "`myschema`.`mytable`"},
|
||||||
|
{"myschema.[mytable]", `[myschema].mytable`},
|
||||||
|
{"myschema.[mytable]", `myschema.[mytable]`},
|
||||||
|
{"myschema.[mytable]", `[myschema].[mytable]`},
|
||||||
|
{`"myschema.mytable"`, `"myschema.mytable"`},
|
||||||
|
{"message_user AS sender", "`message_user` AS `sender`"},
|
||||||
|
{"myschema.[mytable] AS table", "myschema.mytable AS table"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(t, `["mytable"]`, `"mytable"`)
|
for _, v := range kases {
|
||||||
|
t.Run(v.value, func(t *testing.T) {
|
||||||
|
buf := &strings.Builder{}
|
||||||
|
quoter.QuoteTo(buf, v.value)
|
||||||
|
assert.EqualValues(t, v.expected, buf.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test(t, "[myschema].[mytable]", "myschema.mytable")
|
func TestNoQuoteTo(t *testing.T) {
|
||||||
test(t, "[myschema].[mytable]", "`myschema`.mytable")
|
var (
|
||||||
test(t, "[myschema].[mytable]", "myschema.`mytable`")
|
quoter = Quoter{'[', ']', AlwaysNoReverse}
|
||||||
test(t, "[myschema].[mytable]", "`myschema`.`mytable`")
|
kases = []struct {
|
||||||
test(t, "[myschema].[mytable]", `[myschema].mytable`)
|
expected string
|
||||||
test(t, "[myschema].[mytable]", `myschema.[mytable]`)
|
value string
|
||||||
test(t, "[myschema].[mytable]", `[myschema].[mytable]`)
|
}{
|
||||||
|
{"mytable", "mytable"},
|
||||||
|
{"mytable", "`mytable`"},
|
||||||
|
{"mytable", `[mytable]`},
|
||||||
|
{`"mytable"`, `"mytable"`},
|
||||||
|
{"myschema.mytable", "myschema.mytable"},
|
||||||
|
{"myschema.mytable", "`myschema`.mytable"},
|
||||||
|
{"myschema.mytable", "myschema.`mytable`"},
|
||||||
|
{"myschema.mytable", "`myschema`.`mytable`"},
|
||||||
|
{"myschema.mytable", `[myschema].mytable`},
|
||||||
|
{"myschema.mytable", `myschema.[mytable]`},
|
||||||
|
{"myschema.mytable", `[myschema].[mytable]`},
|
||||||
|
{`"myschema.mytable"`, `"myschema.mytable"`},
|
||||||
|
{"message_user AS sender", "`message_user` AS `sender`"},
|
||||||
|
{"myschema.mytable AS table", "myschema.mytable AS table"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(t, `["myschema].[mytable"]`, `"myschema.mytable"`)
|
for _, v := range kases {
|
||||||
|
t.Run(v.value, func(t *testing.T) {
|
||||||
test(t, "[message_user] AS [sender]", "`message_user` AS `sender`")
|
buf := &strings.Builder{}
|
||||||
|
quoter.QuoteTo(buf, v.value)
|
||||||
assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ","))
|
assert.EqualValues(t, v.expected, buf.String())
|
||||||
|
})
|
||||||
buf := &strings.Builder{}
|
}
|
||||||
quoter = Quoter{"", ""}
|
|
||||||
quoter.QuoteTo(buf, "noquote")
|
|
||||||
assert.EqualValues(t, "noquote", buf.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJoin(t *testing.T) {
|
func TestJoin(t *testing.T) {
|
||||||
cols := []string{"f1", "f2", "f3"}
|
cols := []string{"f1", "f2", "f3"}
|
||||||
quoter := Quoter{"[", "]"}
|
quoter := Quoter{'[', ']', AlwaysReverse}
|
||||||
|
|
||||||
|
assert.EqualValues(t, "[a],[b]", quoter.Join([]string{"a", " b"}, ","))
|
||||||
|
|
||||||
assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", "))
|
assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", "))
|
||||||
|
|
||||||
quoter = Quoter{"", ""}
|
quoter.IsReverse = AlwaysNoReverse
|
||||||
assert.EqualValues(t, "f1, f2, f3", quoter.Join(cols, ", "))
|
assert.EqualValues(t, "f1, f2, f3", quoter.Join(cols, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrings(t *testing.T) {
|
func TestStrings(t *testing.T) {
|
||||||
cols := []string{"f1", "f2", "t3.f3"}
|
cols := []string{"f1", "f2", "t3.f3"}
|
||||||
quoter := Quoter{"[", "]"}
|
quoter := Quoter{'[', ']', AlwaysReverse}
|
||||||
|
|
||||||
quotedCols := quoter.Strings(cols)
|
quotedCols := quoter.Strings(cols)
|
||||||
assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols)
|
assert.EqualValues(t, []string{"[f1]", "[f2]", "[t3].[f3]"}, quotedCols)
|
||||||
|
@ -72,6 +143,6 @@ func TestTrim(t *testing.T) {
|
||||||
|
|
||||||
for src, dst := range kases {
|
for src, dst := range kases {
|
||||||
assert.EqualValues(t, src, CommonQuoter.Trim(src))
|
assert.EqualValues(t, src, CommonQuoter.Trim(src))
|
||||||
assert.EqualValues(t, dst, Quoter{"[", "]"}.Trim(src))
|
assert.EqualValues(t, dst, Quoter{'[', ']', AlwaysReverse}.Trim(src))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user