func writeUint64(buf common.BufferWriter, n uint64) { if n < maxLookup { buf.WriteString(itoaTab[int(n)]) } else { buf.WriteString(strconv.FormatUint(n, 10)) } }
// Invariant: for scope conditions only func writeScopeCondition(buf common.BufferWriter, f *whereFragment, args *[]interface{}, pos *int64) { buf.WriteRune(' ') if len(f.Values) > 0 { // map relative $1, $2 placeholders to absolute replaced := remapPlaceholders(buf, f.Condition, *pos) *pos += replaced *args = append(*args, f.Values...) } else { buf.WriteString(f.Condition) } }
// Invariant: only called when len(fragments) > 0 func writeWhereFragmentsToSql(buf common.BufferWriter, fragments []*whereFragment, args *[]interface{}, pos *int64) { hasConditions := false for _, f := range fragments { if f.Condition != "" { if hasConditions { buf.WriteString(" AND (") } else { buf.WriteRune('(') hasConditions = true } if len(f.Values) > 0 { // map relative $1, $2 placeholders to absolute replaced := remapPlaceholders(buf, f.Condition, *pos) *pos += replaced *args = append(*args, f.Values...) } else { buf.WriteString(f.Condition) } buf.WriteRune(')') } else if f.EqualityMap != nil { hasConditions = writeEqualityMapToSql(buf, f.EqualityMap, args, hasConditions, pos) } else { panic("invalid equality map") } } }
// WriteIdentifier writes escaped identifier. func (pd *Postgres) WriteIdentifier(buf common.BufferWriter, ident string) { if ident == "" { panic("Identifier is empty string") } if ident == "*" { buf.WriteString(ident) return } buf.WriteRune('"') buf.WriteString(ident) buf.WriteRune('"') }
func writeWhereCondition(buf common.BufferWriter, k string, pred string, anyConditions bool) bool { if anyConditions { buf.WriteString(" AND (") } else { buf.WriteRune('(') anyConditions = true } Dialect.WriteIdentifier(buf, k) buf.WriteString(pred) buf.WriteRune(')') return anyConditions }
func remapPlaceholders(buf common.BufferWriter, statement string, start int64) int64 { if !strings.Contains(statement, "$") { buf.WriteString(statement) return 0 } highest := 0 pos := int(start) - 1 // 0-based statement = rePlaceholder.ReplaceAllStringFunc(statement, func(s string) string { i, _ := strconv.Atoi(s[1:]) if i > highest { highest = i } sum := strconv.Itoa(pos + i) return "$" + sum }) buf.WriteString(statement) return int64(highest) }
func writeEqualityMapToSql(buf common.BufferWriter, eq map[string]interface{}, args *[]interface{}, anyConditions bool, pos *int64) bool { for k, v := range eq { if v == nil { anyConditions = writeWhereCondition(buf, k, " IS NULL", anyConditions) } else { vVal := reflect.ValueOf(v) if vVal.Kind() == reflect.Array || vVal.Kind() == reflect.Slice { vValLen := vVal.Len() if vValLen == 0 { if vVal.IsNil() { anyConditions = writeWhereCondition(buf, k, " IS NULL", anyConditions) } else { if anyConditions { buf.WriteString(" AND (1=0)") } else { buf.WriteString("(1=0)") } } } else if vValLen == 1 { anyConditions = writeWhereCondition(buf, k, equalsPlaceholderTab[*pos], anyConditions) *args = append(*args, vVal.Index(0).Interface()) *pos++ } else { // " IN $n" anyConditions = writeWhereCondition(buf, k, inPlaceholderTab[*pos], anyConditions) *args = append(*args, v) *pos++ } } else { anyConditions = writeWhereCondition(buf, k, equalsPlaceholderTab[*pos], anyConditions) *args = append(*args, v) *pos++ } } } return anyConditions }
func writePlaceholder64(buf common.BufferWriter, pos int64) { if pos < maxLookup { buf.WriteString(placeholderTab[pos]) } else { buf.WriteRune('$') buf.WriteString(strconv.FormatInt(pos, 10)) } }
func writePlaceholder(buf common.BufferWriter, pos int) { if pos < maxLookup { buf.WriteString(placeholderTab[pos]) } else { buf.WriteRune('$') buf.WriteString(strconv.Itoa(pos)) } }
// writeKVWhere writes "col1" = $1 AND "col2" = $2, ... "coln" = $n func writeKVWhere(buf common.BufferWriter, columns []string, values []interface{}, args *[]interface{}, anyConditions bool, pos *int64) bool { if len(columns) != len(values) { panic("Mismatch of column and values") } for i, k := range columns { v := values[i] if v == nil { anyConditions = writeWhereCondition(buf, k, " IS NULL", anyConditions) } else if e, ok := v.(*Expression); ok { start := pos buf.WriteString(" = ") // map relative $1, $2 placeholders to absolute remapPlaceholders(buf, e.Sql, *start) *args = append(*args, e.Args...) *pos += int64(len(e.Args)) } else { vVal := reflect.ValueOf(v) if vVal.Kind() == reflect.Array || vVal.Kind() == reflect.Slice { vValLen := vVal.Len() if vValLen == 0 { if vVal.IsNil() { anyConditions = writeWhereCondition(buf, k, " IS NULL", anyConditions) } else { if anyConditions { buf.WriteString(" AND (1=0)") } else { buf.WriteString("(1=0)") } } } else if vValLen == 1 { anyConditions = writeWhereCondition(buf, k, equalsPlaceholderTab[*pos], anyConditions) *args = append(*args, vVal.Index(0).Interface()) *pos++ } else { // " IN $n" anyConditions = writeWhereCondition(buf, k, inPlaceholderTab[*pos], anyConditions) *args = append(*args, v) *pos++ } } else { anyConditions = writeWhereCondition(buf, k, equalsPlaceholderTab[*pos], anyConditions) *args = append(*args, v) *pos++ } } } return anyConditions }
// WriteStringLiteral writes an escaped string. No escape characters // are allowed. // // Postgres 9.1+ does not allow any escape // sequences by default. See http://www.postgresql.org/docs/9.3/interactive/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE // In short, all backslashes are treated literally not as escape sequences. func (pd *Postgres) WriteStringLiteral(buf common.BufferWriter, val string) { if val == "" { buf.WriteString("''") return } hasTag := true // don't use double dollar quote strings unless the string is long enough if len(val) > 64 { // if pgDollarTag unique tag is in string, try to create a new one (only once though) hasTag = strings.Contains(val, pgDollarTag) if hasTag { randomizePgDollarTag() hasTag = strings.Contains(val, pgDollarTag) } } if hasTag { buf.WriteRune('\'') if strings.Contains(val, "'") { for _, char := range val { // apos if char == '\'' { buf.WriteString(`''`) } else if char == 0 { panic("postgres doesn't support NULL char in text, see http://stackoverflow.com/questions/1347646/postgres-error-on-insert-error-invalid-byte-sequence-for-encoding-utf8-0x0") } else { buf.WriteRune(char) } } } else { buf.WriteString(val) } buf.WriteRune('\'') } else { buf.WriteString(pgDollarTag) buf.WriteString(val) buf.WriteString(pgDollarTag) } }
// WriteFormattedTime formats t into a format postgres understands. // Taken with gratitude from pq: https://github.com/lib/pq/blob/b269bd035a727d6c1081f76e7a239a1b00674c40/encode.go#L403 func (pd *Postgres) WriteFormattedTime(buf common.BufferWriter, t time.Time) { buf.WriteRune('\'') defer buf.WriteRune('\'') // XXX: This doesn't currently deal with infinity values // Need to send dates before 0001 A.D. with " BC" suffix, instead of the // minus sign preferred by Go. // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on bc := false if t.Year() <= 0 { // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11" t = t.AddDate((-t.Year())*2+1, 0, 0) bc = true } buf.WriteString(t.Format(time.RFC3339Nano)) _, offset := t.Zone() offset = offset % 60 if offset != 0 { // RFC3339Nano already printed the minus sign if offset < 0 { offset = -offset } buf.WriteRune(':') if offset < 10 { buf.WriteRune('0') } buf.WriteString(strconv.FormatInt(int64(offset), 10)) } if bc { buf.WriteString(" BC") } }