Skip to content
Open
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
.idea
*.iml

# Mac
**.DS_Store

# Test files
gen
.gentestdata
Expand All @@ -23,4 +26,5 @@ gen
.docker
.env
.tempTestDir
.gentestdata3
.gentestdata3
.gentestdata2
11 changes: 6 additions & 5 deletions internal/testutils/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"time"

"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/utils/throw"
"github.com/go-jet/jet/v2/qrm"
Expand All @@ -13,11 +19,6 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)

// UnixTimeComparer will compare time equality while ignoring time zone
Expand Down
10 changes: 8 additions & 2 deletions qrm/qrm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/go-jet/jet/v2/internal/utils/must"
"reflect"

"github.com/go-jet/jet/v2/internal/utils/must"
)

// Config holds the configuration settings for QRM scanning behavior.
Expand Down Expand Up @@ -429,7 +430,12 @@ func mapRowToStruct(
value, ok := scannedValue.Interface().([]byte)

if !ok {
return updated, qrmAssignError(scannedValue, field, fmt.Errorf("value not convertable to []byte"))
valueStr, ok := scannedValue.Interface().(string)
if !ok {
return updated, qrmAssignError(scannedValue, field, fmt.Errorf("value not convertable to []byte"))
}

value = []byte(valueStr)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this code is ever called in your tests. This case is active only when SELECT_JSON appears as a column inside regular SELECT statement - wiki.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, now that I check, it seems that this test is missing for mysql.

Copy link
Author

@oscar345 oscar345 Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed though since I was using the select_json inside a regular select in my own code and otherwise there was convert error. I can copy the tests from postgres into sqlite to see check if all cases work correctly. The tests for mysql should probably be in another PR?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense.


fieldInterface := fieldValue.Addr().Interface()
Expand Down
19 changes: 19 additions & 0 deletions sqlite/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqlite
import (
"encoding/hex"
"fmt"

"github.com/go-jet/jet/v2/internal/jet"
)

Expand All @@ -29,6 +30,24 @@ func newDialect() jet.Dialect {
ValuesDefaultColumnName: func(index int) string {
return fmt.Sprintf("column%d", index+1)
},
JsonValueEncode: func(expr Expression) Expression {
switch e := expr.(type) {
case BlobExpression:
return TO_BASE64(e)

// CustomExpression used bellow (instead DATE_FORMAT function) so that only expr is parametrized
case TimestampExpression:
return CustomExpression(Token("strftime('%Y-%m-%dT%H:%M:%fZ', "), e, Token(")"))
case TimeExpression:
return CustomExpression(Token("strftime('%Y-%m-%dT%H:%M:%fZ', "), e, Token(")"))
case DateExpression:
return CustomExpression(Token("strftime('%Y-%m-%dT00:00:00Z', "), e, Token(")"))
case BoolExpression:
return CustomExpression(Token("CASE"), e, Token("WHEN 1 THEN json('true') ELSE json('false') END"))
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this types needs to be covered by tests as well. Check TestAllTypesJSON test for mysql.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I will take a look at the mysql tests and add them to the sqlite tests as well


return expr
},
}

return jet.NewDialect(mySQLDialectParams)
Expand Down
9 changes: 8 additions & 1 deletion sqlite/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package sqlite

import (
"fmt"
"github.com/go-jet/jet/v2/internal/jet"
"time"

"github.com/go-jet/jet/v2/internal/jet"
)

// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition
Expand Down Expand Up @@ -212,6 +213,12 @@ func LENGTH(str jet.StringOrBlobExpression) jet.IntegerExpression {
// OCTET_LENGTH returns number of bytes in string expression
var OCTET_LENGTH = jet.OCTET_LENGTH

// TO_BASE64 converts the string argument to base-64 encoded form and returns the
// result as a character string with the connection character set and collation.
func TO_BASE64(data jet.StringOrBlobExpression) StringExpression {
return StringExp(Func("TO_BASE64", data))
}

// LPAD fills up the string to length length by prepending the characters
// fill (a space by default). If the string is already longer than length
// then it is truncated (on the right).
Expand Down
78 changes: 78 additions & 0 deletions sqlite/select_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package sqlite

import (
"github.com/go-jet/jet/v2/internal/jet"
)

type SelectJsonStatement interface {
Statement
jet.Serializer

AS(alias string) Projection

FROM(table ReadableTable) SelectJsonStatement
WHERE(condition BoolExpression) SelectJsonStatement
ORDER_BY(orderByClauses ...OrderByClause) SelectJsonStatement
LIMIT(limit int64) SelectJsonStatement
OFFSET(offset int64) SelectJsonStatement
}

// SELECT_JSON_ARR creates a new SelectJsonStatement with a list of projections.
func SELECT_JSON_ARR(projections ...Projection) SelectJsonStatement {
return newSelectStatementJson(projections, jet.SelectJsonArrStatementType)
}

// SELECT_JSON_OBJ creates a new SelectJsonStatement with a list of projections.
func SELECT_JSON_OBJ(projections ...Projection) SelectJsonStatement {
return newSelectStatementJson(projections, jet.SelectJsonObjStatementType)
}

type selectJsonStatement struct {
*selectStatementImpl
}

func newSelectStatementJson(projections []Projection, statementType jet.StatementType) SelectJsonStatement {
newSelect := &selectJsonStatement{
selectStatementImpl: newSelectStatement(statementType, nil, nil),
}

newSelect.Select.ProjectionList = ProjectionList{constructJsonFunc(projections, statementType).AS("json")}

return newSelect
}

func constructJsonFunc(projections []Projection, statementType jet.StatementType) Expression {
jsonObj := Func("JSON_OBJECT", CustomExpression(jet.JsonObjProjectionList(projections)))

if statementType == jet.SelectJsonArrStatementType {
return Func("JSON_GROUP_ARRAY", jsonObj)
}

return jsonObj
}

func (s *selectJsonStatement) FROM(table ReadableTable) SelectJsonStatement {
s.From.Tables = []jet.Serializer{table}

return s
}

func (s *selectJsonStatement) WHERE(condition BoolExpression) SelectJsonStatement {
s.Where.Condition = condition
return s
}

func (s *selectJsonStatement) ORDER_BY(orderByClauses ...OrderByClause) SelectJsonStatement {
s.OrderBy.List = orderByClauses
return s
}

func (s *selectJsonStatement) LIMIT(limit int64) SelectJsonStatement {
s.Limit.Count = limit
return s
}

func (s *selectJsonStatement) OFFSET(offset int64) SelectJsonStatement {
s.Offset.Count = Int(offset)
return s
}
6 changes: 3 additions & 3 deletions sqlite/select_statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ type SelectStatement interface {

// SELECT creates new SelectStatement with list of projections
func SELECT(projection Projection, projections ...Projection) SelectStatement {
return newSelectStatement(nil, append([]Projection{projection}, projections...))
return newSelectStatement(jet.SelectStatementType, nil, append([]Projection{projection}, projections...))
}

func newSelectStatement(table ReadableTable, projections []Projection) SelectStatement {
func newSelectStatement(stmtType jet.StatementType, table ReadableTable, projections []Projection) *selectStatementImpl {
newSelect := &selectStatementImpl{}
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SelectStatementType, newSelect, &newSelect.Select,
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, stmtType, newSelect, &newSelect.Select,
&newSelect.From, &newSelect.Where, &newSelect.GroupBy, &newSelect.Having, &newSelect.Window, &newSelect.OrderBy,
&newSelect.Limit, &newSelect.Offset, &newSelect.For, &newSelect.ShareLock)

Expand Down
2 changes: 1 addition & 1 deletion sqlite/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type readableTableInterfaceImpl struct {

// Generates a select query on the current tableName.
func (r readableTableInterfaceImpl) SELECT(projection1 Projection, projections ...Projection) SelectStatement {
return newSelectStatement(r.root, append([]Projection{projection1}, projections...))
return newSelectStatement(jet.SelectStatementType, r.root, append([]Projection{projection1}, projections...))
}

// Creates a inner join tableName Expression using onCondition.
Expand Down
Loading
Loading