Add support for SELECT_JSON statements.
This commit is contained in:
parent
7047de44a9
commit
7b16e432ff
46 changed files with 2732 additions and 307 deletions
13
internal/3rdparty/snaker/snaker.go
vendored
13
internal/3rdparty/snaker/snaker.go
vendored
|
|
@ -40,14 +40,23 @@ func snakeToCamel(s string, upperCase bool) string {
|
|||
|
||||
if upperCase || i > 0 {
|
||||
result += camelizeWord(word, len(words) > 1)
|
||||
} else {
|
||||
result += word
|
||||
} else { // lowerCase and i == 0
|
||||
result += toLowerFirstLetter(word)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func toLowerFirstLetter(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
runes := []rune(s)
|
||||
runes[0] = unicode.ToLower(runes[0])
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func camelizeWord(word string, force bool) string {
|
||||
runes := []rune(word)
|
||||
|
||||
|
|
|
|||
2
internal/3rdparty/snaker/snaker_test.go
vendored
2
internal/3rdparty/snaker/snaker_test.go
vendored
|
|
@ -8,6 +8,8 @@ import (
|
|||
func TestSnakeToCamel(t *testing.T) {
|
||||
require.Equal(t, SnakeToCamel(""), "")
|
||||
require.Equal(t, SnakeToCamel("potato_"), "Potato")
|
||||
require.Equal(t, SnakeToCamel("potato_", false), "potato")
|
||||
require.Equal(t, SnakeToCamel("Potato_", false), "potato")
|
||||
require.Equal(t, SnakeToCamel("this_has_to_be_uppercased"), "ThisHasToBeUppercased")
|
||||
require.Equal(t, SnakeToCamel("this_is_an_id"), "ThisIsAnID")
|
||||
require.Equal(t, SnakeToCamel("this_is_an_identifier"), "ThisIsAnIdentifier")
|
||||
|
|
|
|||
|
|
@ -30,3 +30,8 @@ func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder)
|
|||
out.WriteString("AS")
|
||||
out.WriteAlias(a.alias)
|
||||
}
|
||||
|
||||
func (a *alias) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
out.WriteJsonObjKey(a.alias)
|
||||
a.expression.serialize(statement, out)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ func (s *ClauseSelect) Projections() ProjectionList {
|
|||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(s.ProjectionList) == 0 {
|
||||
panic("jet: SELECT clause has to have at least one projection")
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("SELECT")
|
||||
s.OptimizerHints.Serialize(statementType, out, options...)
|
||||
|
|
@ -66,10 +70,6 @@ func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, o
|
|||
out.WriteByte(')')
|
||||
}
|
||||
|
||||
if len(s.ProjectionList) == 0 {
|
||||
panic("jet: SELECT clause has to have at least one projection")
|
||||
}
|
||||
|
||||
out.WriteProjections(statementType, s.ProjectionList)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
package jet
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/internal/3rdparty/snaker"
|
||||
)
|
||||
|
||||
// Column is common column interface for all types of columns.
|
||||
type Column interface {
|
||||
Name() string
|
||||
|
|
@ -97,7 +101,17 @@ func (c ColumnExpressionImpl) serializeForProjection(statement StatementType, ou
|
|||
c.serialize(statement, out)
|
||||
|
||||
out.WriteString("AS")
|
||||
out.WriteAlias(c.defaultAlias())
|
||||
|
||||
if statement.IsSelectJSON() {
|
||||
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
|
||||
} else {
|
||||
out.WriteAlias(c.defaultAlias())
|
||||
}
|
||||
}
|
||||
|
||||
func (c ColumnExpressionImpl) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
|
||||
c.serialize(statement, out)
|
||||
}
|
||||
|
||||
func (c ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ func (cl ColumnList) serializeForProjection(statement StatementType, out *SQLBui
|
|||
SerializeProjectionList(statement, projections, out)
|
||||
}
|
||||
|
||||
func (cl ColumnList) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
projections := ColumnListToProjectionList(cl)
|
||||
|
||||
SerializeProjectionListJsonObj(statement, projections, out)
|
||||
}
|
||||
|
||||
// dummy column interface implementation
|
||||
|
||||
// Name is placeholder for ColumnList to implement Column interface
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package jet
|
|||
|
||||
import "fmt"
|
||||
|
||||
// Expression is common interface for all expressions.
|
||||
// Expression is a common interface for all expressions.
|
||||
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
|
||||
type Expression interface {
|
||||
Serializer
|
||||
|
|
@ -89,9 +89,16 @@ func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, o
|
|||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
if statement.IsSelectJSON() {
|
||||
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
|
||||
}
|
||||
e.Parent.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||
e.Parent.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package jet
|
|||
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
|
||||
type Projection interface {
|
||||
serializeForProjection(statement StatementType, out *SQLBuilder)
|
||||
serializeForJsonObj(statement StatementType, out *SQLBuilder)
|
||||
fromImpl(subQuery SelectTable) Projection
|
||||
}
|
||||
|
||||
|
|
@ -28,6 +29,10 @@ func (pl ProjectionList) serializeForProjection(statement StatementType, out *SQ
|
|||
SerializeProjectionList(statement, pl, out)
|
||||
}
|
||||
|
||||
func (pl ProjectionList) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
SerializeProjectionListJsonObj(statement, pl, out)
|
||||
}
|
||||
|
||||
// As will create new projection list where each column is wrapped with a new table alias.
|
||||
// tableAlias should be in the form 'name' or 'name.*', or it can be an empty string, which will remove existing table alias.
|
||||
// For instance: If projection list has a column 'Artist.Name', and tableAlias is 'Musician.*', returned projection list will
|
||||
|
|
@ -79,3 +84,10 @@ func (pl ProjectionList) Except(toExclude ...Column) ProjectionList {
|
|||
|
||||
return ret
|
||||
}
|
||||
|
||||
// JsonProjectionList redefines []Projection so projections can be serialized as json object key/values
|
||||
type JsonProjectionList []Projection
|
||||
|
||||
func (j JsonProjectionList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
SerializeProjectionListJsonObj(statement, j, out)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package jet
|
||||
|
||||
type rawStatementImpl struct {
|
||||
serializerStatementInterfaceImpl
|
||||
statementInterfaceImpl
|
||||
|
||||
RawQuery string
|
||||
NamedArguments map[string]interface{}
|
||||
|
|
@ -10,7 +10,7 @@ type rawStatementImpl struct {
|
|||
// RawStatement creates new sql statements from raw query and optional map of named arguments
|
||||
func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) SerializerStatement {
|
||||
newRawStatement := rawStatementImpl{
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
statementInterfaceImpl: statementInterfaceImpl{
|
||||
dialect: dialect,
|
||||
statementType: "",
|
||||
parent: nil,
|
||||
|
|
|
|||
|
|
@ -22,16 +22,22 @@ func (s SerializeOption) WithFallTrough(options []SerializeOption) []SerializeOp
|
|||
// StatementType is type of the SQL statement
|
||||
type StatementType string
|
||||
|
||||
func (s StatementType) IsSelectJSON() bool {
|
||||
return s == SelectJsonObjStatementType || s == SelectJsonArrStatementType
|
||||
}
|
||||
|
||||
// Statement types
|
||||
const (
|
||||
SelectStatementType StatementType = "SELECT"
|
||||
InsertStatementType StatementType = "INSERT"
|
||||
UpdateStatementType StatementType = "UPDATE"
|
||||
DeleteStatementType StatementType = "DELETE"
|
||||
SetStatementType StatementType = "SET"
|
||||
LockStatementType StatementType = "LOCK"
|
||||
UnLockStatementType StatementType = "UNLOCK"
|
||||
WithStatementType StatementType = "WITH"
|
||||
SelectStatementType StatementType = "SELECT"
|
||||
SelectJsonObjStatementType StatementType = "SELECT_JSON_OBJ"
|
||||
SelectJsonArrStatementType StatementType = "SELECT_JSON_ARR"
|
||||
InsertStatementType StatementType = "INSERT"
|
||||
UpdateStatementType StatementType = "UPDATE"
|
||||
DeleteStatementType StatementType = "DELETE"
|
||||
SetStatementType StatementType = "SET"
|
||||
LockStatementType StatementType = "LOCK"
|
||||
UnLockStatementType StatementType = "UNLOCK"
|
||||
WithStatementType StatementType = "WITH"
|
||||
)
|
||||
|
||||
// Serializer interface
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@ func (s *SQLBuilder) WriteString(str string) {
|
|||
s.write([]byte(str))
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) WriteJsonObjKey(key string) {
|
||||
s.WriteString(fmt.Sprintf(`'%s', `, key))
|
||||
}
|
||||
|
||||
// WriteIdentifier adds identifier to output SQL
|
||||
func (s *SQLBuilder) WriteIdentifier(name string, alwaysQuote ...bool) {
|
||||
if s.shouldQuote(name, alwaysQuote...) {
|
||||
|
|
|
|||
|
|
@ -7,25 +7,49 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
|
||||
// Statement is a common interface for all SQL statements, including SELECT, SELECT_JSON_ARR, SELECT_JSON_OBJ, INSERT,
|
||||
// UPDATE, DELETE, and LOCK.
|
||||
type Statement interface {
|
||||
// Sql returns parametrized sql query with list of arguments.
|
||||
// Sql returns a parameterized SQL query along with its list of arguments.
|
||||
Sql() (query string, args []interface{})
|
||||
// DebugSql returns debug query where every parametrized placeholder is replaced with its argument string representation.
|
||||
// Do not use it in production. Use it only for debug purposes.
|
||||
|
||||
// DebugSql returns a debug-friendly SQL query where all parameterized placeholders
|
||||
// are replaced with their respective argument string representations.
|
||||
//
|
||||
// Warning: This method should only be used for debugging purposes.
|
||||
// Do not use it in production, as it may lead to security risks such as SQL injection.
|
||||
DebugSql() (query string)
|
||||
// Query executes statement over database connection/transaction db and stores row results in destination.
|
||||
// Destination can be either pointer to struct or pointer to a slice.
|
||||
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
|
||||
|
||||
// Query executes statement on the provided database connection or transaction (db),
|
||||
// storing the retrieved row results in the given destination.
|
||||
// Destination must be a pointer to either a struct or a slice.
|
||||
// If the destination is a pointer to a struct and the query returns no rows, Query returns qrm.ErrNoRows.
|
||||
Query(db qrm.Queryable, destination interface{}) error
|
||||
// QueryContext executes statement with a context over database connection/transaction db and stores row result in destination.
|
||||
// Destination can be either pointer to struct or pointer to a slice.
|
||||
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
|
||||
|
||||
// QueryContext executes statement with a context over database connection/transaction db,
|
||||
// storing the retrieved row results in the given destination.
|
||||
// Destination must be a pointer to either a struct or a slice.
|
||||
// If the destination is a pointer to a struct and the query returns no rows, Query returns qrm.ErrNoRows.
|
||||
QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error
|
||||
|
||||
// QueryJSON executes the given statement within the provided context on the database connection/transaction (db)
|
||||
// and unmarshals the JSON result into the destination.
|
||||
// If the statement is created as SELECT_JSON_ARR, the destination must be a pointer to a slice of structs or a
|
||||
// pointer to []map[string]any.
|
||||
// If the statement is created as SELECT_JSON_OBJ, the destination must be a pointer to a struct or a pointer to
|
||||
// map[string]any.
|
||||
// QueryJSON can also be used by other SQL statements that generate JSON on the server. The only requirement is
|
||||
// that the query must return exactly one row with a single column; otherwise, an error is returned.
|
||||
// If the destination is a pointer to a struct (or []map[string]any) and the query result set is empty, the method
|
||||
// returns qrm.ErrNoRows.
|
||||
QueryJSON(ctx context.Context, db qrm.Queryable, destination interface{}) error
|
||||
|
||||
// Exec executes statement over db connection/transaction without returning any rows.
|
||||
Exec(db qrm.Executable) (sql.Result, error)
|
||||
|
||||
// ExecContext executes statement with context over db connection/transaction without returning any rows.
|
||||
ExecContext(ctx context.Context, db qrm.Executable) (sql.Result, error)
|
||||
|
||||
// Rows executes statements over db connection/transaction and returns rows
|
||||
Rows(ctx context.Context, db qrm.Queryable) (*Rows, error)
|
||||
}
|
||||
|
|
@ -60,14 +84,14 @@ type SerializerHasProjections interface {
|
|||
HasProjections
|
||||
}
|
||||
|
||||
// serializerStatementInterfaceImpl struct
|
||||
type serializerStatementInterfaceImpl struct {
|
||||
// statementInterfaceImpl struct
|
||||
type statementInterfaceImpl struct {
|
||||
dialect Dialect
|
||||
statementType StatementType
|
||||
parent SerializerStatement
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Sql() (query string, args []interface{}) {
|
||||
func (s *statementInterfaceImpl) Sql() (query string, args []interface{}) {
|
||||
|
||||
queryData := &SQLBuilder{Dialect: s.dialect}
|
||||
|
||||
|
|
@ -77,7 +101,7 @@ func (s *serializerStatementInterfaceImpl) Sql() (query string, args []interface
|
|||
return
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) DebugSql() (query string) {
|
||||
func (s *statementInterfaceImpl) DebugSql() (query string) {
|
||||
sqlBuilder := &SQLBuilder{Dialect: s.dialect, Debug: true}
|
||||
|
||||
s.parent.serialize(s.statementType, sqlBuilder, NoWrap)
|
||||
|
|
@ -86,11 +110,29 @@ func (s *serializerStatementInterfaceImpl) DebugSql() (query string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Query(db qrm.Queryable, destination interface{}) error {
|
||||
func (s *statementInterfaceImpl) Query(db qrm.Queryable, destination interface{}) error {
|
||||
return s.QueryContext(context.Background(), db, destination)
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error {
|
||||
func (s *statementInterfaceImpl) QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error {
|
||||
return s.query(ctx, func(query string, args []interface{}) (int64, error) {
|
||||
return qrm.Query(ctx, db, query, args, destination)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *statementInterfaceImpl) QueryJSON(ctx context.Context, db qrm.Queryable, destination interface{}) error {
|
||||
return s.query(ctx, func(query string, args []interface{}) (int64, error) {
|
||||
if s.statementType == SelectJsonObjStatementType {
|
||||
return qrm.QueryJsonObj(ctx, db, query, args, destination)
|
||||
}
|
||||
return qrm.QueryJsonArr(ctx, db, query, args, destination)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *statementInterfaceImpl) query(
|
||||
ctx context.Context,
|
||||
queryFunc func(query string, args []interface{}) (int64, error),
|
||||
) error {
|
||||
query, args := s.Sql()
|
||||
|
||||
callLogger(ctx, s)
|
||||
|
|
@ -99,7 +141,7 @@ func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db
|
|||
var err error
|
||||
|
||||
duration := duration(func() {
|
||||
rowsProcessed, err = qrm.Query(ctx, db, query, args, destination)
|
||||
rowsProcessed, err = queryFunc(query, args)
|
||||
})
|
||||
|
||||
callQueryLoggerFunc(ctx, QueryInfo{
|
||||
|
|
@ -112,11 +154,11 @@ func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Exec(db qrm.Executable) (res sql.Result, err error) {
|
||||
func (s *statementInterfaceImpl) Exec(db qrm.Executable) (res sql.Result, err error) {
|
||||
return s.ExecContext(context.Background(), db)
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db qrm.Executable) (res sql.Result, err error) {
|
||||
func (s *statementInterfaceImpl) ExecContext(ctx context.Context, db qrm.Executable) (res sql.Result, err error) {
|
||||
query, args := s.Sql()
|
||||
|
||||
callLogger(ctx, s)
|
||||
|
|
@ -141,7 +183,7 @@ func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db q
|
|||
return res, err
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.Queryable) (*Rows, error) {
|
||||
func (s *statementInterfaceImpl) Rows(ctx context.Context, db qrm.Queryable) (*Rows, error) {
|
||||
query, args := s.Sql()
|
||||
|
||||
callLogger(ctx, s)
|
||||
|
|
@ -191,11 +233,15 @@ type ExpressionStatement interface {
|
|||
}
|
||||
|
||||
// NewExpressionStatementImpl creates new expression statement
|
||||
func NewExpressionStatementImpl(Dialect Dialect, statementType StatementType, parent ExpressionStatement, clauses ...Clause) ExpressionStatement {
|
||||
func NewExpressionStatementImpl(Dialect Dialect,
|
||||
statementType StatementType,
|
||||
parent ExpressionStatement,
|
||||
clauses ...Clause) ExpressionStatement {
|
||||
|
||||
return &expressionStatementImpl{
|
||||
ExpressionInterfaceImpl{Parent: parent},
|
||||
statementImpl{
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
statementInterfaceImpl: statementInterfaceImpl{
|
||||
parent: parent,
|
||||
dialect: Dialect,
|
||||
statementType: statementType,
|
||||
|
|
@ -211,13 +257,16 @@ type expressionStatementImpl struct {
|
|||
}
|
||||
|
||||
func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
if statement.IsSelectJSON() {
|
||||
panic("jet: SELECT JSON statements need to be aliased when used as a projection.")
|
||||
}
|
||||
s.serialize(statement, out)
|
||||
}
|
||||
|
||||
// NewStatementImpl creates new statementImpl
|
||||
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
|
||||
return &statementImpl{
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
statementInterfaceImpl: statementInterfaceImpl{
|
||||
parent: parent,
|
||||
dialect: Dialect,
|
||||
statementType: statementType,
|
||||
|
|
@ -227,7 +276,7 @@ func NewStatementImpl(Dialect Dialect, statementType StatementType, parent Seria
|
|||
}
|
||||
|
||||
type statementImpl struct {
|
||||
serializerStatementInterfaceImpl
|
||||
statementInterfaceImpl
|
||||
|
||||
Clauses []Clause
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,23 @@ func SerializeProjectionList(statement StatementType, projections []Projection,
|
|||
}
|
||||
}
|
||||
|
||||
// SerializeProjectionListJsonObj serializes a list of projections for JSON object
|
||||
func SerializeProjectionListJsonObj(statement StatementType, projections []Projection, out *SQLBuilder) {
|
||||
|
||||
for i, p := range projections {
|
||||
if i > 0 {
|
||||
out.WriteString(",")
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
panic("jet: Projection is nil")
|
||||
}
|
||||
|
||||
p.serializeForJsonObj(statement, out)
|
||||
}
|
||||
}
|
||||
|
||||
// SerializeColumnNames func
|
||||
func SerializeColumnNames(columns []Column, out *SQLBuilder) {
|
||||
for i, col := range columns {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ func WITH(dialect Dialect, recursive bool, cte ...*CommonTableExpression) func(s
|
|||
newWithImpl := &withImpl{
|
||||
recursive: recursive,
|
||||
ctes: cte,
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
statementInterfaceImpl: statementInterfaceImpl{
|
||||
dialect: dialect,
|
||||
statementType: WithStatementType,
|
||||
},
|
||||
|
|
@ -25,7 +25,7 @@ func WITH(dialect Dialect, recursive bool, cte ...*CommonTableExpression) func(s
|
|||
}
|
||||
|
||||
type withImpl struct {
|
||||
serializerStatementInterfaceImpl
|
||||
statementInterfaceImpl
|
||||
recursive bool
|
||||
ctes []*CommonTableExpression
|
||||
primaryStatement SerializerStatement
|
||||
|
|
|
|||
|
|
@ -115,6 +115,16 @@ func AssertJSON(t *testing.T, data interface{}, expectedJSON string) {
|
|||
require.Equal(t, dataJson, expectedJSON)
|
||||
}
|
||||
|
||||
// AssertJsonEqual checks if actual and expected json representation are the same
|
||||
func AssertJsonEqual(t require.TestingT, actual, expected interface{}, option ...cmp.Option) {
|
||||
actualJsonData, err := json.MarshalIndent(actual, "", "\t")
|
||||
require.NoError(t, err)
|
||||
expectedJsonData, err := json.MarshalIndent(expected, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, actualJsonData, expectedJsonData)
|
||||
}
|
||||
|
||||
// SaveJSONFile saves v as json at testRelativePath
|
||||
// nolint:unused
|
||||
func SaveJSONFile(v interface{}, testRelativePath string) {
|
||||
|
|
@ -127,7 +137,10 @@ func SaveJSONFile(v interface{}, testRelativePath string) {
|
|||
}
|
||||
|
||||
// AssertJSONFile check if data json representation is the same as json at testRelativePath
|
||||
func AssertJSONFile(t *testing.T, data interface{}, testRelativePath string) {
|
||||
func AssertJSONFile(t require.TestingT, data interface{}, testRelativePath string) {
|
||||
if _, ok := t.(*testing.B); ok {
|
||||
return // skip assert for benchmarks
|
||||
}
|
||||
|
||||
filePath := getFullPath(testRelativePath)
|
||||
fileJSONData, err := os.ReadFile(filePath) // #nosec G304
|
||||
|
|
@ -145,7 +158,11 @@ func AssertJSONFile(t *testing.T, data interface{}, testRelativePath string) {
|
|||
}
|
||||
|
||||
// AssertStatementSql check if statement Sql() is the same as expectedQuery and expectedArgs
|
||||
func AssertStatementSql(t *testing.T, query jet.PrintableStatement, expectedQuery string, expectedArgs ...interface{}) {
|
||||
func AssertStatementSql(t require.TestingT, query jet.PrintableStatement, expectedQuery string, expectedArgs ...interface{}) {
|
||||
if _, ok := t.(*testing.B); ok {
|
||||
return // skip assert for benchmarks
|
||||
}
|
||||
|
||||
queryStr, args := query.Sql()
|
||||
assertQueryString(t, queryStr, expectedQuery)
|
||||
|
||||
|
|
@ -255,6 +272,16 @@ func AssertQueryPanicErr(t *testing.T, stmt jet.Statement, db qrm.DB, dest inter
|
|||
_ = stmt.Query(db, dest)
|
||||
}
|
||||
|
||||
// AssertQueryJsonPanicErr check if statement QueryJSON execution panics with error errString
|
||||
func AssertQueryJsonPanicErr(t *testing.T, stmt jet.Statement, db qrm.DB, dest interface{}, errString string) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
require.Equal(t, r, errString)
|
||||
}()
|
||||
|
||||
_ = stmt.QueryJSON(context.Background(), db, dest)
|
||||
}
|
||||
|
||||
// AssertFileContent check if file content at filePath contains expectedContent text.
|
||||
func AssertFileContent(t *testing.T, filePath string, expectedContent string) {
|
||||
enumFileData, err := os.ReadFile(filePath) // #nosec G304
|
||||
|
|
@ -283,14 +310,14 @@ func AssertFileNamesEqual(t *testing.T, dirPath string, fileNames ...string) {
|
|||
}
|
||||
|
||||
// AssertDeepEqual checks if actual and expected objects are deeply equal.
|
||||
func AssertDeepEqual(t *testing.T, actual, expected interface{}, option ...cmp.Option) {
|
||||
func AssertDeepEqual(t require.TestingT, actual, expected interface{}, option ...cmp.Option) {
|
||||
if !assert.True(t, cmp.Equal(actual, expected, option...)) {
|
||||
printDiff(actual, expected, option...)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func assertQueryString(t *testing.T, actual, expected string) {
|
||||
func assertQueryString(t require.TestingT, actual, expected string) {
|
||||
if !assert.Equal(t, actual, expected) {
|
||||
printDiff(actual, expected)
|
||||
t.FailNow()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package datetime
|
||||
|
||||
import "time"
|
||||
import (
|
||||
//"github.com/go-jet/jet/v2/internal/utils/min"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExtractTimeComponents extracts number of days, hours, minutes, seconds, microseconds from duration
|
||||
func ExtractTimeComponents(duration time.Duration) (days, hours, minutes, seconds, microseconds int64) {
|
||||
|
|
@ -20,3 +23,36 @@ func ExtractTimeComponents(duration time.Duration) (days, hours, minutes, second
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// TryParseAsTime attempts to parse the provided value as a time using one of the given formats.
|
||||
//
|
||||
// The function iterates over the provided formats and tries to parse the value into a time.Time object.
|
||||
// It returns the parsed time and a boolean indicating whether the parsing was successful.
|
||||
func TryParseAsTime(value interface{}, formats []string) (time.Time, bool) {
|
||||
|
||||
var timeStr string
|
||||
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
timeStr = v
|
||||
case []byte:
|
||||
timeStr = string(v)
|
||||
case int64:
|
||||
return time.Unix(v, 0), true // sqlite
|
||||
default:
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
for _, format := range formats {
|
||||
formatLen := min(len(format), len(timeStr))
|
||||
t, err := time.Parse(format[:formatLen], timeStr)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return t, true
|
||||
}
|
||||
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package min
|
||||
|
||||
// Int returns minimum of two int values
|
||||
func Int(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue