1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
|
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package dbx
import (
"fmt"
"sort"
"strings"
)
// Expression represents a DB expression that can be embedded in a SQL statement.
type Expression interface {
// Build converts an expression into a SQL fragment.
// If the expression contains binding parameters, they will be added to the given Params.
Build(*DB, Params) string
}
// HashExp represents a hash expression.
//
// A hash expression is a map whose keys are DB column names which need to be filtered according
// to the corresponding values. For example, HashExp{"level": 2, "dept": 10} will generate
// the SQL: "level"=2 AND "dept"=10.
//
// HashExp also handles nil values and slice values. For example, HashExp{"level": []interface{}{1, 2}, "dept": nil}
// will generate: "level" IN (1, 2) AND "dept" IS NULL.
type HashExp map[string]interface{}
// NewExp generates an expression with the specified SQL fragment and the optional binding parameters.
func NewExp(e string, params ...Params) Expression {
if len(params) > 0 {
return &Exp{e, params[0]}
}
return &Exp{e, nil}
}
// Not generates a NOT expression which prefixes "NOT" to the specified expression.
func Not(e Expression) Expression {
return &NotExp{e}
}
// And generates an AND expression which concatenates the given expressions with "AND".
func And(exps ...Expression) Expression {
return &AndOrExp{exps, "AND"}
}
// Or generates an OR expression which concatenates the given expressions with "OR".
func Or(exps ...Expression) Expression {
return &AndOrExp{exps, "OR"}
}
// In generates an IN expression for the specified column and the list of allowed values.
// If values is empty, a SQL "0=1" will be generated which represents a false expression.
func In(col string, values ...interface{}) Expression {
return &InExp{col, values, false}
}
// NotIn generates an NOT IN expression for the specified column and the list of disallowed values.
// If values is empty, an empty string will be returned indicating a true expression.
func NotIn(col string, values ...interface{}) Expression {
return &InExp{col, values, true}
}
// DefaultLikeEscape specifies the default special character escaping for LIKE expressions
// The strings at 2i positions are the special characters to be escaped while those at 2i+1 positions
// are the corresponding escaped versions.
var DefaultLikeEscape = []string{"\\", "\\\\", "%", "\\%", "_", "\\_"}
// Like generates a LIKE expression for the specified column and the possible strings that the column should be like.
// If multiple values are present, the column should be like *all* of them. For example, Like("name", "key", "word")
// will generate a SQL expression: "name" LIKE "%key%" AND "name" LIKE "%word%".
//
// By default, each value will be surrounded by "%" to enable partial matching. If a value contains special characters
// such as "%", "\", "_", they will also be properly escaped.
//
// You may call Escape() and/or Match() to change the default behavior. For example, Like("name", "key").Match(false, true)
// generates "name" LIKE "key%".
func Like(col string, values ...string) *LikeExp {
return &LikeExp{
left: true,
right: true,
col: col,
values: values,
escape: DefaultLikeEscape,
Like: "LIKE",
}
}
// NotLike generates a NOT LIKE expression.
// For example, NotLike("name", "key", "word") will generate a SQL expression:
// "name" NOT LIKE "%key%" AND "name" NOT LIKE "%word%". Please see Like() for more details.
func NotLike(col string, values ...string) *LikeExp {
return &LikeExp{
left: true,
right: true,
col: col,
values: values,
escape: DefaultLikeEscape,
Like: "NOT LIKE",
}
}
// OrLike generates an OR LIKE expression.
// This is similar to Like() except that the column should be like one of the possible values.
// For example, OrLike("name", "key", "word") will generate a SQL expression:
// "name" LIKE "%key%" OR "name" LIKE "%word%". Please see Like() for more details.
func OrLike(col string, values ...string) *LikeExp {
return &LikeExp{
or: true,
left: true,
right: true,
col: col,
values: values,
escape: DefaultLikeEscape,
Like: "LIKE",
}
}
// OrNotLike generates an OR NOT LIKE expression.
// For example, OrNotLike("name", "key", "word") will generate a SQL expression:
// "name" NOT LIKE "%key%" OR "name" NOT LIKE "%word%". Please see Like() for more details.
func OrNotLike(col string, values ...string) *LikeExp {
return &LikeExp{
or: true,
left: true,
right: true,
col: col,
values: values,
escape: DefaultLikeEscape,
Like: "NOT LIKE",
}
}
// Exists generates an EXISTS expression by prefixing "EXISTS" to the given expression.
func Exists(exp Expression) Expression {
return &ExistsExp{exp, false}
}
// NotExists generates an EXISTS expression by prefixing "NOT EXISTS" to the given expression.
func NotExists(exp Expression) Expression {
return &ExistsExp{exp, true}
}
// Between generates a BETWEEN expression.
// For example, Between("age", 10, 30) generates: "age" BETWEEN 10 AND 30
func Between(col string, from, to interface{}) Expression {
return &BetweenExp{col, from, to, false}
}
// NotBetween generates a NOT BETWEEN expression.
// For example, NotBetween("age", 10, 30) generates: "age" NOT BETWEEN 10 AND 30
func NotBetween(col string, from, to interface{}) Expression {
return &BetweenExp{col, from, to, true}
}
// Exp represents an expression with a SQL fragment and a list of optional binding parameters.
type Exp struct {
e string
params Params
}
// Build converts an expression into a SQL fragment.
func (e *Exp) Build(db *DB, params Params) string {
if len(e.params) == 0 {
return e.e
}
for k, v := range e.params {
params[k] = v
}
return e.e
}
// Build converts an expression into a SQL fragment.
func (e HashExp) Build(db *DB, params Params) string {
if len(e) == 0 {
return ""
}
// ensure the hash exp generates the same SQL for different runs
names := []string{}
for name := range e {
names = append(names, name)
}
sort.Strings(names)
var parts []string
for _, name := range names {
value := e[name]
switch value.(type) {
case nil:
name = db.QuoteColumnName(name)
parts = append(parts, name+" IS NULL")
case Expression:
if sql := value.(Expression).Build(db, params); sql != "" {
parts = append(parts, "("+sql+")")
}
case []interface{}:
in := In(name, value.([]interface{})...)
if sql := in.Build(db, params); sql != "" {
parts = append(parts, sql)
}
default:
pn := fmt.Sprintf("p%v", len(params))
name = db.QuoteColumnName(name)
parts = append(parts, name+"={:"+pn+"}")
params[pn] = value
}
}
if len(parts) == 1 {
return parts[0]
}
return strings.Join(parts, " AND ")
}
// NotExp represents an expression that should prefix "NOT" to a specified expression.
type NotExp struct {
e Expression
}
// Build converts an expression into a SQL fragment.
func (e *NotExp) Build(db *DB, params Params) string {
if sql := e.e.Build(db, params); sql != "" {
return "NOT (" + sql + ")"
}
return ""
}
// AndOrExp represents an expression that concatenates multiple expressions using either "AND" or "OR".
type AndOrExp struct {
exps []Expression
op string
}
// Build converts an expression into a SQL fragment.
func (e *AndOrExp) Build(db *DB, params Params) string {
if len(e.exps) == 0 {
return ""
}
var parts []string
for _, a := range e.exps {
if a == nil {
continue
}
if sql := a.Build(db, params); sql != "" {
parts = append(parts, sql)
}
}
if len(parts) == 1 {
return parts[0]
}
return "(" + strings.Join(parts, ") "+e.op+" (") + ")"
}
// InExp represents an "IN" or "NOT IN" expression.
type InExp struct {
col string
values []interface{}
not bool
}
// Build converts an expression into a SQL fragment.
func (e *InExp) Build(db *DB, params Params) string {
if len(e.values) == 0 {
if e.not {
return ""
}
return "0=1"
}
var values []string
for _, value := range e.values {
switch value.(type) {
case nil:
values = append(values, "NULL")
case Expression:
sql := value.(Expression).Build(db, params)
values = append(values, sql)
default:
name := fmt.Sprintf("p%v", len(params))
params[name] = value
values = append(values, "{:"+name+"}")
}
}
col := db.QuoteColumnName(e.col)
if len(values) == 1 {
if e.not {
return col + "<>" + values[0]
}
return col + "=" + values[0]
}
in := "IN"
if e.not {
in = "NOT IN"
}
return fmt.Sprintf("%v %v (%v)", col, in, strings.Join(values, ", "))
}
// LikeExp represents a variant of LIKE expressions.
type LikeExp struct {
or bool
left, right bool
col string
values []string
escape []string
// Like stores the LIKE operator. It can be "LIKE", "NOT LIKE".
// It may also be customized as something like "ILIKE".
Like string
}
// Escape specifies how a LIKE expression should be escaped.
// Each string at position 2i represents a special character and the string at position 2i+1 is
// the corresponding escaped version.
func (e *LikeExp) Escape(chars ...string) *LikeExp {
e.escape = chars
return e
}
// Match specifies whether to do wildcard matching on the left and/or right of given strings.
func (e *LikeExp) Match(left, right bool) *LikeExp {
e.left, e.right = left, right
return e
}
// Build converts an expression into a SQL fragment.
func (e *LikeExp) Build(db *DB, params Params) string {
if len(e.values) == 0 {
return ""
}
if len(e.escape)%2 != 0 {
panic("LikeExp.Escape must be a slice of even number of strings")
}
var parts []string
col := db.QuoteColumnName(e.col)
for _, value := range e.values {
name := fmt.Sprintf("p%v", len(params))
for i := 0; i < len(e.escape); i += 2 {
value = strings.Replace(value, e.escape[i], e.escape[i+1], -1)
}
if e.left {
value = "%" + value
}
if e.right {
value += "%"
}
params[name] = value
parts = append(parts, fmt.Sprintf("%v %v {:%v}", col, e.Like, name))
}
if e.or {
return strings.Join(parts, " OR ")
}
return strings.Join(parts, " AND ")
}
// ExistsExp represents an EXISTS or NOT EXISTS expression.
type ExistsExp struct {
exp Expression
not bool
}
// Build converts an expression into a SQL fragment.
func (e *ExistsExp) Build(db *DB, params Params) string {
sql := e.exp.Build(db, params)
if sql == "" {
if e.not {
return ""
}
return "0=1"
}
if e.not {
return "NOT EXISTS (" + sql + ")"
}
return "EXISTS (" + sql + ")"
}
// BetweenExp represents a BETWEEN or a NOT BETWEEN expression.
type BetweenExp struct {
col string
from, to interface{}
not bool
}
// Build converts an expression into a SQL fragment.
func (e *BetweenExp) Build(db *DB, params Params) string {
between := "BETWEEN"
if e.not {
between = "NOT BETWEEN"
}
name1 := fmt.Sprintf("p%v", len(params))
name2 := fmt.Sprintf("p%v", len(params)+1)
params[name1] = e.from
params[name2] = e.to
col := db.QuoteColumnName(e.col)
return fmt.Sprintf("%v %v {:%v} AND {:%v}", col, between, name1, name2)
}
// Enclose surrounds the provided nonempty expression with parenthesis "()".
func Enclose(exp Expression) Expression {
return &EncloseExp{exp}
}
// EncloseExp represents a parenthesis enclosed expression.
type EncloseExp struct {
exp Expression
}
// Build converts an expression into a SQL fragment.
func (e *EncloseExp) Build(db *DB, params Params) string {
str := e.exp.Build(db, params)
if str == "" {
return ""
}
return "(" + str + ")"
}
|