Skip to content

Commit 1f3215c

Browse files
authored
Merge pull request #457 from go-jet/select_json
Add support for SELECT_JSON statements
2 parents 3155f99 + ac3275d commit 1f3215c

File tree

104 files changed

+5251
-902
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+5251
-902
lines changed

.circleci/config.yml

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ jobs:
1111
- image: cimg/go:1.22.8
1212

1313
# Please keep the version in sync with test/docker-compose.yaml
14-
- image: cimg/postgres:14.10
14+
- image: cimg/postgres:14.1
1515
environment:
1616
POSTGRES_USER: jet
1717
POSTGRES_PASSWORD: jet
1818
POSTGRES_DB: jetdb
1919
PGPORT: 50901
2020

2121
# Please keep the version in sync with test/docker-compose.yaml
22-
- image: circleci/mysql:8.0.27
22+
- image: cimg/mysql:8.0.27
2323
command: [ --default-authentication-plugin=mysql_native_password ]
2424
environment:
2525
MYSQL_ROOT_PASSWORD: jet
@@ -29,7 +29,7 @@ jobs:
2929
MYSQL_TCP_PORT: 50902
3030

3131
# Please keep the version in sync with test/docker-compose.yaml
32-
- image: circleci/mariadb:10.3
32+
- image: cimg/mariadb:11.4
3333
command: [ '--default-authentication-plugin=mysql_native_password', '--port=50903' ]
3434
environment:
3535
MYSQL_ROOT_PASSWORD: jet
@@ -116,25 +116,27 @@ jobs:
116116
name: Create MySQL/MariaDB user and test databases
117117
command: |
118118
mysql -h 127.0.0.1 -P 50902 -u root -pjet -e "grant all privileges on *.* to 'jet'@'%';"
119-
mysql -h 127.0.0.1 -P 50902 -u root -pjet -e "set global sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
119+
mysql -h 127.0.0.1 -P 50902 -u root -pjet -e "set global sql_mode = 'STRICT_ALL_TABLES,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
120120
mysql -h 127.0.0.1 -P 50902 -u jet -pjet -e "create database test_sample"
121121
mysql -h 127.0.0.1 -P 50902 -u jet -pjet -e "create database dvds2"
122122
123123
mysql -h 127.0.0.1 -P 50903 -u root -pjet -e "grant all privileges on *.* to 'jet'@'%';"
124-
mysql -h 127.0.0.1 -P 50903 -u root -pjet -e "set global sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
124+
mysql -h 127.0.0.1 -P 50903 -u root -pjet -e "set global sql_mode = 'STRICT_ALL_TABLES,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
125125
mysql -h 127.0.0.1 -P 50903 -u jet -pjet -e "create database test_sample"
126126
mysql -h 127.0.0.1 -P 50903 -u jet -pjet -e "create database dvds2"
127127
128-
- run:
129-
name: Init databases
130-
command: |
131-
cd tests
132-
go run ./init/init.go -testsuite all
133-
134128
- run:
135129
name: Install gotestsum
136130
command: go install gotest.tools/gotestsum@latest
137131

132+
- run:
133+
name: Init databases (postgres, mysql, sqlite) and generate jet files
134+
command: |
135+
cd tests
136+
go run ./init/init.go -testsuite postgres
137+
go run ./init/init.go -testsuite mysql
138+
go run ./init/init.go -testsuite sqlite
139+
138140
# to create test results report
139141
- run: mkdir -p $TEST_RESULTS
140142

@@ -146,14 +148,14 @@ jobs:
146148
name: Running tests with statement caching enabled
147149
command: JET_TESTS_WITH_STMT_CACHE=true go test -tags postgres -v ./tests/...
148150

149-
# run mariaDB and cockroachdb tests. No need to collect coverage, because coverage is already included with mysql and postgres tests
150151
- run:
151-
name: Jet generate mariadb and cockroachdb
152+
name: Init databases (mariadb, cockroachdb) and generate jet files
152153
command: |
153154
cd tests
154-
make jet-gen-mariadb
155-
make jet-gen-cockroach
155+
go run ./init/init.go -testsuite mariadb
156+
go run ./init/init.go -testsuite cockroach
156157
158+
# run mariaDB and cockroachdb tests. No need to collect coverage, because coverage is already included with mysql and postgres tests
157159
- run: MY_SQL_SOURCE=MariaDB go test -v ./tests/mysql/
158160
- run: PG_SOURCE=COCKROACH_DB go test -v ./tests/postgres/
159161

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,5 +579,5 @@ To run the tests, additional dependencies are required:
579579

580580
## License
581581

582-
Copyright 2019-2024 Goran Bjelanovic
582+
Copyright 2019-2025 Goran Bjelanovic
583583
Licensed under the Apache License, Version 2.0.

generator/mysql/query_set.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ func (m mySqlQuerySet) GetTablesMetaData(db *sql.DB, schemaName string, tableTyp
1818
SELECT
1919
t.table_name as "table.name",
2020
col.COLUMN_NAME AS "column.Name",
21-
col.COLUMN_DEFAULT IS NOT NULL AND t.table_type != 'VIEW' as "column.HasDefault",
22-
col.IS_NULLABLE = "YES" AS "column.IsNullable",
21+
(col.COLUMN_DEFAULT IS NOT NULL AND col.COLUMN_DEFAULT != 'NULL') AND t.table_type != 'VIEW' as "column.HasDefault",
22+
col.IS_NULLABLE = 'YES' AS "column.IsNullable",
2323
col.COLUMN_COMMENT AS "column.Comment",
2424
COALESCE(pk.IsPrimaryKey, 0) AS "column.IsPrimaryKey",
2525
IF (col.COLUMN_TYPE = 'tinyint(1)',

generator/template/sql_builder_template.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,15 @@ func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
180180
return "Timez"
181181
case "interval":
182182
return "Interval"
183-
case "user-defined", "enum", "text", "character", "character varying", "bytea", "uuid",
183+
case "user-defined", "enum", "text", "character", "character varying", "uuid",
184184
"tsvector", "bit", "bit varying", "money", "json", "jsonb", "xml", "point", "line", "ARRAY",
185-
"char", "varchar", "nvarchar", "binary", "varbinary", "bpchar", "varbit",
186-
"tinyblob", "blob", "mediumblob", "longblob", "tinytext", "mediumtext", "longtext": // MySQL
185+
"char", "varchar", "nvarchar", "bpchar", "varbit",
186+
"tinytext", "mediumtext", "longtext": // MySQL
187187
return "String"
188+
case "bytea": // postgres
189+
return "Bytea"
190+
case "binary", "varbinary", "tinyblob", "mediumblob", "longblob", "blob": // mysql and sqlite
191+
return "Blob"
188192
case "real", "numeric", "decimal", "double precision", "float", "float4", "float8",
189193
"double": // MySQL
190194
return "Float"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/go-jet/jet/v2
22

3-
go 1.21
3+
go 1.22
44

55
// used by jet generator
66
require (

internal/3rdparty/snaker/snaker.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,23 @@ func snakeToCamel(s string, upperCase bool) string {
4040

4141
if upperCase || i > 0 {
4242
result += camelizeWord(word, len(words) > 1)
43-
} else {
44-
result += word
43+
} else { // lowerCase and i == 0
44+
result += toLowerFirstLetter(word)
4545
}
4646
}
4747

4848
return result
4949
}
5050

51+
func toLowerFirstLetter(s string) string {
52+
if s == "" {
53+
return s
54+
}
55+
runes := []rune(s)
56+
runes[0] = unicode.ToLower(runes[0])
57+
return string(runes)
58+
}
59+
5160
func camelizeWord(word string, force bool) string {
5261
runes := []rune(word)
5362

internal/3rdparty/snaker/snaker_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import (
77

88
func TestSnakeToCamel(t *testing.T) {
99
require.Equal(t, SnakeToCamel(""), "")
10+
require.Equal(t, SnakeToCamel("_", false), "")
1011
require.Equal(t, SnakeToCamel("potato_"), "Potato")
12+
require.Equal(t, SnakeToCamel("potato_", false), "potato")
13+
require.Equal(t, SnakeToCamel("Potato_", false), "potato")
1114
require.Equal(t, SnakeToCamel("this_has_to_be_uppercased"), "ThisHasToBeUppercased")
1215
require.Equal(t, SnakeToCamel("this_is_an_id"), "ThisIsAnID")
1316
require.Equal(t, SnakeToCamel("this_is_an_identifier"), "ThisIsAnIdentifier")

internal/jet/alias.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,45 @@ func (a *alias) fromImpl(subQuery SelectTable) Projection {
1818
// Generated columns have default aliasing.
1919
tableName, columnName := extractTableAndColumnName(a.alias)
2020

21-
column := NewColumnImpl(columnName, tableName, nil)
22-
column.subQuery = subQuery
21+
newDummyColumn := newDummyColumnForExpression(a.expression, columnName)
22+
newDummyColumn.setTableName(tableName)
23+
newDummyColumn.setSubQuery(subQuery)
2324

24-
return &column
25+
return newDummyColumn
26+
}
27+
28+
// This function is used to create dummy columns when exporting sub-query columns using subQuery.AllColumns()
29+
// In most case we don't care about type of the column, except when sub-query columns are used as SELECT_JSON projection.
30+
// We need to know type to encode value for json unmarshal. At the moment only bool, time and blob columns are of interest,
31+
// so we don't have to support every column type.
32+
func newDummyColumnForExpression(exp Expression, name string) ColumnExpression {
33+
34+
switch exp.(type) {
35+
case BoolExpression:
36+
return BoolColumn(name)
37+
case IntegerExpression:
38+
return IntegerColumn(name)
39+
case FloatExpression:
40+
return FloatColumn(name)
41+
case BlobExpression:
42+
return BlobColumn(name)
43+
case DateExpression:
44+
return DateColumn(name)
45+
case TimeExpression:
46+
return TimeColumn(name)
47+
case TimezExpression:
48+
return TimezColumn(name)
49+
case TimestampExpression:
50+
return TimestampColumn(name)
51+
case TimestampzExpression:
52+
return TimestampzColumn(name)
53+
case IntervalExpression:
54+
return IntervalColumn(name)
55+
case StringExpression:
56+
return StringColumn(name)
57+
}
58+
59+
return StringColumn(name)
2560
}
2661

2762
func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) {
@@ -30,3 +65,15 @@ func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder)
3065
out.WriteString("AS")
3166
out.WriteAlias(a.alias)
3267
}
68+
69+
func (a *alias) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
70+
out.WriteJsonObjKey(a.alias)
71+
a.expression.serializeForJsonValue(statement, out)
72+
}
73+
74+
func (a *alias) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
75+
a.expression.serializeForJsonValue(statement, out)
76+
77+
out.WriteString("AS")
78+
out.WriteAlias(a.alias)
79+
}

internal/jet/blob_expression.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package jet
2+
3+
// BlobExpression interface
4+
type BlobExpression interface {
5+
Expression
6+
7+
isStringOrBlob()
8+
9+
EQ(rhs BlobExpression) BoolExpression
10+
NOT_EQ(rhs BlobExpression) BoolExpression
11+
IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression
12+
IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression
13+
14+
LT(rhs BlobExpression) BoolExpression
15+
LT_EQ(rhs BlobExpression) BoolExpression
16+
GT(rhs BlobExpression) BoolExpression
17+
GT_EQ(rhs BlobExpression) BoolExpression
18+
BETWEEN(min, max BlobExpression) BoolExpression
19+
NOT_BETWEEN(min, max BlobExpression) BoolExpression
20+
21+
CONCAT(rhs BlobExpression) BlobExpression
22+
23+
LIKE(pattern BlobExpression) BoolExpression
24+
NOT_LIKE(pattern BlobExpression) BoolExpression
25+
}
26+
27+
type blobInterfaceImpl struct {
28+
parent BlobExpression
29+
}
30+
31+
func (b *blobInterfaceImpl) isStringOrBlob() {}
32+
33+
func (b *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
34+
return Eq(b.parent, rhs)
35+
}
36+
37+
func (b *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
38+
return NotEq(b.parent, rhs)
39+
}
40+
41+
func (b *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
42+
return IsDistinctFrom(b.parent, rhs)
43+
}
44+
45+
func (b *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
46+
return IsNotDistinctFrom(b.parent, rhs)
47+
}
48+
49+
func (b *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
50+
return Gt(b.parent, rhs)
51+
}
52+
53+
func (b *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
54+
return GtEq(b.parent, rhs)
55+
}
56+
57+
func (b *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
58+
return Lt(b.parent, rhs)
59+
}
60+
61+
func (b *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
62+
return LtEq(b.parent, rhs)
63+
}
64+
65+
func (b *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
66+
return NewBetweenOperatorExpression(b.parent, min, max, false)
67+
}
68+
69+
func (b *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
70+
return NewBetweenOperatorExpression(b.parent, min, max, true)
71+
}
72+
73+
func (b *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
74+
return BlobExp(newBinaryStringOperatorExpression(b.parent, rhs, StringConcatOperator))
75+
}
76+
77+
func (b *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
78+
return newBinaryBoolOperatorExpression(b.parent, pattern, "LIKE")
79+
}
80+
81+
func (b *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
82+
return newBinaryBoolOperatorExpression(b.parent, pattern, "NOT LIKE")
83+
}
84+
85+
//---------------------------------------------------//
86+
87+
type blobExpressionWrapper struct {
88+
Expression
89+
blobInterfaceImpl
90+
}
91+
92+
func newBlobExpressionWrap(expression Expression) BlobExpression {
93+
blobExpressionWrap := &blobExpressionWrapper{Expression: expression}
94+
blobExpressionWrap.blobInterfaceImpl.parent = blobExpressionWrap
95+
expression.setParent(blobExpressionWrap)
96+
return blobExpressionWrap
97+
}
98+
99+
// BlobExp is blob expression wrapper around arbitrary expression.
100+
// Allows go compiler to see any expression as blob expression.
101+
// Does not add sql cast to generated sql builder output.
102+
func BlobExp(expression Expression) BlobExpression {
103+
return newBlobExpressionWrap(expression)
104+
}

internal/jet/bool_expression.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ type boolExpressionWrapper struct {
102102
}
103103

104104
func newBoolExpressionWrap(expression Expression) BoolExpression {
105-
boolExpressionWrap := boolExpressionWrapper{Expression: expression}
106-
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap
107-
return &boolExpressionWrap
105+
boolExpressionWrap := &boolExpressionWrapper{Expression: expression}
106+
boolExpressionWrap.boolInterfaceImpl.parent = boolExpressionWrap
107+
expression.setParent(boolExpressionWrap)
108+
return boolExpressionWrap
108109
}
109110

110111
// BoolExp is bool expression wrapper around arbitrary expression.

0 commit comments

Comments
 (0)