Add support for blob expressions.

This commit is contained in:
go-jet 2025-02-28 18:23:15 +01:00
parent 26e478dc7e
commit c94216ab0e
37 changed files with 1296 additions and 81 deletions

View file

@ -0,0 +1,103 @@
package jet
// BlobExpression interface
type BlobExpression interface {
Expression
isStringOrBlob()
EQ(rhs BlobExpression) BoolExpression
NOT_EQ(rhs BlobExpression) BoolExpression
IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression
LT(rhs BlobExpression) BoolExpression
LT_EQ(rhs BlobExpression) BoolExpression
GT(rhs BlobExpression) BoolExpression
GT_EQ(rhs BlobExpression) BoolExpression
BETWEEN(min, max BlobExpression) BoolExpression
NOT_BETWEEN(min, max BlobExpression) BoolExpression
CONCAT(rhs BlobExpression) BlobExpression
LIKE(pattern BlobExpression) BoolExpression
NOT_LIKE(pattern BlobExpression) BoolExpression
}
type blobInterfaceImpl struct {
parent BlobExpression
}
func (s *blobInterfaceImpl) isStringOrBlob() {}
func (s *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
return Eq(s.parent, rhs)
}
func (s *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
return NotEq(s.parent, rhs)
}
func (s *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
return IsDistinctFrom(s.parent, rhs)
}
func (s *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
return IsNotDistinctFrom(s.parent, rhs)
}
func (s *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
return Gt(s.parent, rhs)
}
func (s *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
return GtEq(s.parent, rhs)
}
func (s *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
return Lt(s.parent, rhs)
}
func (s *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
return LtEq(s.parent, rhs)
}
func (s *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
return NewBetweenOperatorExpression(s.parent, min, max, false)
}
func (s *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
return NewBetweenOperatorExpression(s.parent, min, max, true)
}
func (s *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
return BlobExp(newBinaryStringOperatorExpression(s.parent, rhs, StringConcatOperator))
}
func (s *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, "LIKE")
}
func (s *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, "NOT LIKE")
}
//---------------------------------------------------//
type blobExpressionWrapper struct {
blobInterfaceImpl
Expression
}
func newBlobExpressionWrap(expression Expression) BlobExpression {
blobExpressionWrap := blobExpressionWrapper{Expression: expression}
blobExpressionWrap.blobInterfaceImpl.parent = &blobExpressionWrap
return &blobExpressionWrap
}
// BlobExp is blob expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as blob expression.
// Does not add sql cast to generated sql builder output.
func BlobExp(expression Expression) BlobExpression {
return newBlobExpressionWrap(expression)
}

View file

@ -122,7 +122,7 @@ func IntegerColumn(name string) ColumnInteger {
//------------------------------------------------------//
// ColumnString is interface for SQL text, character, character varying
// bytea, uuid columns and enums types.
// uuid columns and enums types.
type ColumnString interface {
StringExpression
Column
@ -163,6 +163,47 @@ func StringColumn(name string) ColumnString {
//------------------------------------------------------//
// ColumnBlob is interface for binary data types (bytea, binary, blob, etc...)
type ColumnBlob interface {
BlobExpression
Column
From(subQuery SelectTable) ColumnBlob
SET(blob BlobExpression) ColumnAssigment
}
type blobColumnImpl struct {
blobInterfaceImpl
ColumnExpressionImpl
}
func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob {
newBlobColumn := BlobColumn(i.name)
newBlobColumn.setTableName(i.tableName)
newBlobColumn.setSubQuery(subQuery)
return newBlobColumn
}
func (i *blobColumnImpl) SET(blobExp BlobExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: blobExp,
}
}
// BlobColumn creates named blob column.
func BlobColumn(name string) ColumnBlob {
blobColumn := &blobColumnImpl{}
blobColumn.blobInterfaceImpl.parent = blobColumn
blobColumn.ColumnExpressionImpl = NewColumnImpl(name, "", blobColumn)
return blobColumn
}
//------------------------------------------------------//
// ColumnTime is interface for SQL time column.
type ColumnTime interface {
TimeExpression

View file

@ -1,6 +1,8 @@
package jet
import "strings"
import (
"strings"
)
// Dialect interface
type Dialect interface {
@ -11,6 +13,7 @@ type Dialect interface {
AliasQuoteChar() byte
IdentifierQuoteChar() byte
ArgumentPlaceholder() QueryPlaceholderFunc
ArgumentToString(value any) (string, bool)
IsReservedWord(name string) bool
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName(index int) string
@ -34,6 +37,7 @@ type DialectParams struct {
AliasQuoteChar byte
IdentifierQuoteChar byte
ArgumentPlaceholder QueryPlaceholderFunc
ArgumentToString func(value any) (string, bool)
ReservedWords []string
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName func(index int) string
@ -49,6 +53,7 @@ func NewDialect(params DialectParams) Dialect {
aliasQuoteChar: params.AliasQuoteChar,
identifierQuoteChar: params.IdentifierQuoteChar,
argumentPlaceholder: params.ArgumentPlaceholder,
argumentToString: params.ArgumentToString,
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
serializeOrderBy: params.SerializeOrderBy,
valuesDefaultColumnName: params.ValuesDefaultColumnName,
@ -63,6 +68,7 @@ type dialectImpl struct {
aliasQuoteChar byte
identifierQuoteChar byte
argumentPlaceholder QueryPlaceholderFunc
argumentToString func(value any) (string, bool)
reservedWords map[string]bool
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
valuesDefaultColumnName func(index int) string
@ -102,6 +108,10 @@ func (d *dialectImpl) ArgumentPlaceholder() QueryPlaceholderFunc {
return d.argumentPlaceholder
}
func (d *dialectImpl) ArgumentToString(value any) (string, bool) {
return d.argumentToString(value)
}
func (d *dialectImpl) IsReservedWord(name string) bool {
_, isReservedWord := d.reservedWords[strings.ToLower(name)]
return isReservedWord

View file

@ -159,7 +159,7 @@ func newExpressionListOperator(operator string, expressions ...Expression) *expr
}
func newBoolExpressionListOperator(operator string, expressions ...BoolExpression) BoolExpression {
return BoolExp(newExpressionListOperator(operator, BoolExpressionListToExpressionList(expressions)...))
return BoolExp(newExpressionListOperator(operator, ToExpressionList(expressions)...))
}
func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {

View file

@ -255,18 +255,30 @@ func leadLagImpl(name string, expr Expression, offsetAndDefault ...interface{})
//------------ String functions ------------------//
// HEX function takes an input and returns its equivalent hexadecimal representation
func HEX(expression Expression) StringExpression {
return StringExp(Func("HEX", expression))
}
// UNHEX for a string argument str, UNHEX(str) interprets each pair of characters in the argument
// as a hexadecimal number and converts it to the byte represented by the number.
// The return value is a binary string.
func UNHEX(expression StringExpression) BlobExpression {
return BlobExp(Func("UNHEX", expression))
}
// BIT_LENGTH returns number of bits in string expression
func BIT_LENGTH(stringExpression StringExpression) IntegerExpression {
func BIT_LENGTH(stringExpression StringOrBlobExpression) IntegerExpression {
return newIntegerFunc("BIT_LENGTH", stringExpression)
}
// CHAR_LENGTH returns number of characters in string expression
func CHAR_LENGTH(stringExpression StringExpression) IntegerExpression {
func CHAR_LENGTH(stringExpression StringOrBlobExpression) IntegerExpression {
return newIntegerFunc("CHAR_LENGTH", stringExpression)
}
// OCTET_LENGTH returns number of bytes in string expression
func OCTET_LENGTH(stringExpression StringExpression) IntegerExpression {
func OCTET_LENGTH(stringExpression StringOrBlobExpression) IntegerExpression {
return newIntegerFunc("OCTET_LENGTH", stringExpression)
}
@ -282,7 +294,7 @@ func UPPER(stringExpression StringExpression) StringExpression {
// BTRIM removes the longest string consisting only of characters
// in characters (a space by default) from the start and end of string
func BTRIM(stringExpression StringExpression, trimChars ...StringExpression) StringExpression {
func BTRIM(stringExpression StringOrBlobExpression, trimChars ...StringOrBlobExpression) StringExpression {
if len(trimChars) > 0 {
return NewStringFunc("BTRIM", stringExpression, trimChars[0])
}
@ -291,7 +303,7 @@ func BTRIM(stringExpression StringExpression, trimChars ...StringExpression) Str
// LTRIM removes the longest string containing only characters
// from characters (a space by default) from the start of string
func LTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
func LTRIM(str StringOrBlobExpression, trimChars ...StringOrBlobExpression) StringExpression {
if len(trimChars) > 0 {
return NewStringFunc("LTRIM", str, trimChars[0])
}
@ -300,7 +312,7 @@ func LTRIM(str StringExpression, trimChars ...StringExpression) StringExpression
// RTRIM removes the longest string containing only characters
// from characters (a space by default) from the end of string
func RTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
func RTRIM(str StringOrBlobExpression, trimChars ...StringOrBlobExpression) StringExpression {
if len(trimChars) > 0 {
return NewStringFunc("RTRIM", str, trimChars[0])
}
@ -324,32 +336,32 @@ func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression
// CONVERT converts string to dest_encoding. The original encoding is
// specified by src_encoding. The string must be valid in this encoding.
func CONVERT(str StringExpression, srcEncoding StringExpression, destEncoding StringExpression) StringExpression {
return NewStringFunc("CONVERT", str, srcEncoding, destEncoding)
func CONVERT(str BlobExpression, srcEncoding StringExpression, destEncoding StringExpression) BlobExpression {
return BlobExp(Func("CONVERT", str, srcEncoding, destEncoding))
}
// CONVERT_FROM converts string to the database encoding. The original
// encoding is specified by src_encoding. The string must be valid in this encoding.
func CONVERT_FROM(str StringExpression, srcEncoding StringExpression) StringExpression {
func CONVERT_FROM(str BlobExpression, srcEncoding StringExpression) StringExpression {
return NewStringFunc("CONVERT_FROM", str, srcEncoding)
}
// CONVERT_TO converts string to dest_encoding.
func CONVERT_TO(str StringExpression, toEncoding StringExpression) StringExpression {
return NewStringFunc("CONVERT_TO", str, toEncoding)
func CONVERT_TO(str StringExpression, toEncoding StringExpression) BlobExpression {
return BlobExp(Func("CONVERT_TO", str, toEncoding))
}
// ENCODE encodes binary data into a textual representation.
// Supported formats are: base64, hex, escape. escape converts zero bytes and
// high-bit-set bytes to octal sequences (\nnn) and doubles backslashes.
func ENCODE(data StringExpression, format StringExpression) StringExpression {
return NewStringFunc("ENCODE", data, format)
func ENCODE(data BlobExpression, format StringExpression) StringExpression {
return StringExp(Func("ENCODE", data, format))
}
// DECODE decodes binary data from textual representation in string.
// Options for format are same as in encode.
func DECODE(data StringExpression, format StringExpression) StringExpression {
return NewStringFunc("DECODE", data, format)
func DECODE(data StringExpression, format StringExpression) BlobExpression {
return BlobExp(Func("DECODE", data, format))
}
// FORMAT formats a number to a format like "#,###,###.##", rounded to a specified number of decimal places, then it returns the result as a string.
@ -379,11 +391,11 @@ func RIGHT(str StringExpression, n IntegerExpression) StringExpression {
}
// LENGTH returns number of characters in string with a given encoding
func LENGTH(str StringExpression, encoding ...StringExpression) StringExpression {
func LENGTH(str StringOrBlobExpression, encoding ...StringExpression) IntegerExpression {
if len(encoding) > 0 {
return NewStringFunc("LENGTH", str, encoding[0])
return IntExp(Func("LENGTH", str, encoding[0]))
}
return NewStringFunc("LENGTH", str)
return IntExp(Func("LENGTH", str))
}
// LPAD fills up the string to length length by prepending the characters
@ -407,8 +419,13 @@ func RPAD(str StringExpression, length IntegerExpression, text ...StringExpressi
return NewStringFunc("RPAD", str, length)
}
// BIT_COUNT returns the number of bits set in the binary string (also known as “popcount”).
func BIT_COUNT(bytes BlobExpression) IntegerExpression {
return IntExp(Func("BIT_COUNT", bytes))
}
// MD5 calculates the MD5 hash of string, returning the result in hexadecimal
func MD5(stringExpression StringExpression) StringExpression {
func MD5(stringExpression StringOrBlobExpression) StringExpression {
return NewStringFunc("MD5", stringExpression)
}
@ -434,7 +451,7 @@ func STRPOS(str, substring StringExpression) IntegerExpression {
}
// SUBSTR extracts substring
func SUBSTR(str StringExpression, from IntegerExpression, count ...IntegerExpression) StringExpression {
func SUBSTR(str StringOrBlobExpression, from IntegerExpression, count ...IntegerExpression) StringExpression {
if len(count) > 0 {
return NewStringFunc("SUBSTR", str, from, count[0])
}

View file

@ -468,6 +468,11 @@ func RawDate(raw string, namedArgs ...map[string]interface{}) DateExpression {
return DateExp(Raw(raw, namedArgs...))
}
// RawBlob is raw query helper that for blob expressions
func RawBlob(raw string, namedArgs ...map[string]interface{}) BlobExpression {
return BlobExp(Raw(raw, namedArgs...))
}
// RawRange helper that for range expressions
func RawRange[T Expression](raw string, namedArgs ...map[string]interface{}) Range[T] {
return RangeExp[T](Raw(raw, namedArgs...))

View file

@ -127,7 +127,7 @@ func (s *SQLBuilder) finalize() (string, []interface{}) {
}
func (s *SQLBuilder) insertConstantArgument(arg interface{}) {
s.WriteString(argToString(arg))
s.WriteString(s.argToString(arg))
}
func (s *SQLBuilder) insertParametrizedArgument(arg interface{}) {
@ -200,7 +200,7 @@ func (s *SQLBuilder) insertRawQuery(raw string, namedArg map[string]interface{})
}
if s.Debug {
placeholder = argToString(namedArgumentPos.Value)
placeholder = s.argToString(namedArgumentPos.Value)
}
raw = strings.Replace(raw, namedArgumentPos.Name, placeholder, toReplace)
@ -209,11 +209,17 @@ func (s *SQLBuilder) insertRawQuery(raw string, namedArg map[string]interface{})
s.WriteString(raw)
}
func argToString(value interface{}) string {
func (s *SQLBuilder) argToString(value interface{}) string {
if is.Nil(value) {
return "NULL"
}
strVal, ok := s.Dialect.ArgumentToString(value)
if ok {
return strVal
}
switch bindVal := value.(type) {
case bool:
if bindVal {
@ -250,7 +256,7 @@ func argToString(value interface{}) string {
return err.Error()
}
return argToString(val)
return s.argToString(val)
}
panic(fmt.Sprintf("jet: %s type can not be used as SQL query parameter", reflect.TypeOf(value).String()))

View file

@ -3,6 +3,7 @@ package jet
// StringExpression interface
type StringExpression interface {
Expression
isStringOrBlob()
EQ(rhs StringExpression) BoolExpression
NOT_EQ(rhs StringExpression) BoolExpression
@ -29,6 +30,8 @@ type stringInterfaceImpl struct {
parent StringExpression
}
func (s *stringInterfaceImpl) isStringOrBlob() {}
func (s *stringInterfaceImpl) EQ(rhs StringExpression) BoolExpression {
return Eq(s.parent, rhs)
}

View file

@ -0,0 +1,8 @@
package jet
// StringOrBlobExpression is common interface for all string and blob expressions
type StringOrBlobExpression interface {
Expression
isStringOrBlob()
}

View file

@ -132,8 +132,8 @@ func ExpressionListToSerializerList(expressions []Expression) []Serializer {
return ret
}
// BoolExpressionListToExpressionList converts list of bool expressions to list of expressions
func BoolExpressionListToExpressionList(expressions []BoolExpression) []Expression {
// ToExpressionList converts list of any expressions to list of expressions
func ToExpressionList[T Expression](expressions []T) []Expression {
var ret []Expression
for _, expression := range expressions {

View file

@ -1,6 +1,8 @@
package is
import "reflect"
import (
"reflect"
)
// Nil check if v is nil
func Nil(v interface{}) bool {