Skip to content

Commit 43c7fcc

Browse files
committed
fix(bulker): snowflake: properly handle reserved column names
1 parent 348f517 commit 43c7fcc

File tree

5 files changed

+53
-33
lines changed

5 files changed

+53
-33
lines changed

bulkerlib/go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ require (
99
cloud.google.com/go/storage v1.43.0
1010
github.com/ClickHouse/clickhouse-go/v2 v2.37.1
1111
github.com/jackc/pgx/v5 v5.7.5
12-
github.com/aws/aws-sdk-go-v2 v1.32.5
12+
github.com/aws/aws-sdk-go-v2 v1.39.0
1313
github.com/marcboeker/go-duckdb/v2 v2.3.2
14-
github.com/aws/aws-sdk-go-v2/config v1.28.1
15-
github.com/aws/aws-sdk-go-v2/credentials v1.17.42
16-
github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.31.3
17-
github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0
18-
github.com/aws/aws-sdk-go-v2/service/sts v1.32.3
14+
github.com/aws/aws-sdk-go-v2/config v1.31.8
15+
github.com/aws/aws-sdk-go-v2/credentials v1.18.12
16+
github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.37.4
17+
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1
18+
github.com/aws/aws-sdk-go-v2/service/sts v1.38.4
1919
github.com/docker/go-connections v0.5.0
2020
github.com/go-sql-driver/mysql v1.9.0
2121
github.com/hamba/avro/v2 v2.24.0

bulkerlib/implementations/sql/bigquery.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
package sql
22

33
import (
4-
"cloud.google.com/go/bigquery"
5-
"cloud.google.com/go/civil"
64
"context"
75
"errors"
86
"fmt"
7+
"math"
8+
"net/http"
9+
"os"
10+
"regexp"
11+
"strconv"
12+
"strings"
13+
"time"
14+
15+
"cloud.google.com/go/bigquery"
16+
"cloud.google.com/go/civil"
917
"github.com/hashicorp/go-multierror"
1018
bulker "github.com/jitsucom/bulker/bulkerlib"
1119
"github.com/jitsucom/bulker/bulkerlib/implementations"
@@ -19,13 +27,6 @@ import (
1927
"github.com/jitsucom/bulker/jitsubase/utils"
2028
"google.golang.org/api/googleapi"
2129
"google.golang.org/api/iterator"
22-
"math"
23-
"net/http"
24-
"os"
25-
"regexp"
26-
"strconv"
27-
"strings"
28-
"time"
2930
)
3031

3132
func init() {
@@ -241,7 +242,8 @@ func (bq *BigQuery) CopyTables(ctx context.Context, targetTable *Table, sourceTa
241242
columnsString := strings.Join(quotedColumns, ",")
242243
updateSet := make([]string, len(quotedColumns))
243244
for i, name := range quotedColumns {
244-
updateSet[i] = fmt.Sprintf("T.%s = S.%s", name, name)
245+
n := forceQuote(name)
246+
updateSet[i] = fmt.Sprintf("T.%s = S.%s", n, n)
245247
}
246248
var joinConditions []string
247249
targetTable.PKFields.ForEach(func(pkField string) {
@@ -1173,3 +1175,10 @@ func (bq *BigQuery) RunJob(ctx context.Context, runner JobRunner, jobDescription
11731175
return job, state, nil
11741176
}
11751177
}
1178+
1179+
func forceQuote(identifier string) string {
1180+
if strings.HasPrefix(identifier, "`") && strings.HasSuffix(identifier, "`") {
1181+
return identifier
1182+
}
1183+
return "`" + identifier + "`"
1184+
}

bulkerlib/implementations/sql/naming_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package sql
22

33
import (
4+
"testing"
5+
46
bulker "github.com/jitsucom/bulker/bulkerlib"
57
"github.com/jitsucom/bulker/jitsubase/utils"
6-
"testing"
78
)
89

910
func TestNaming(t *testing.T) {
@@ -18,7 +19,7 @@ func TestNaming(t *testing.T) {
1819
expectedRowsCount: 1,
1920
expectedTableCaseChecking: true,
2021
expectedTable: ExpectedTable{
21-
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_e", "camelCase", "int", "user", "select", "__ROOT__", "hash", "default"),
22+
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_e", "camelCase", "int", "user", "select", "__ROOT__", "hash", "default", "current_time"),
2223
},
2324
configIds: utils.ArrayExcluding(allBulkerConfigs, RedshiftBulkerTypeId+"_serverless", RedshiftBulkerTypeId+"_iam", RedshiftBulkerTypeId, SnowflakeBulkerTypeId, BigqueryBulkerTypeId, ClickHouseBulkerTypeId, ClickHouseBulkerTypeId+"_cluster", ClickHouseBulkerTypeId+"_cluster_noshards"),
2425
},
@@ -31,7 +32,7 @@ func TestNaming(t *testing.T) {
3132
expectedRowsCount: 1,
3233
expectedTableCaseChecking: true,
3334
expectedTable: ExpectedTable{
34-
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_eni", "camelCase", "int", "user", "select", "__ROOT__", "hash", "default"),
35+
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_eni", "camelCase", "int", "user", "select", "__ROOT__", "hash", "default", "current_time"),
3536
},
3637
configIds: []string{ClickHouseBulkerTypeId},
3738
},
@@ -47,7 +48,7 @@ func TestNaming(t *testing.T) {
4748
expectedTableCaseChecking: false,
4849
expectedRowsCount: 1,
4950
expectedTable: ExpectedTable{
50-
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_eni", "camelCase", "int", "user", "select", "__ROOT__", "hash", "default"),
51+
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_eni", "camelCase", "int", "user", "select", "__ROOT__", "hash", "default", "current_time"),
5152
//Columns: justColumns("id", "name", "column_12b241e808ae6c964a5bb9f1c012e63d", "1test_name", "2", "column_c16da609b86c01f16a2c609eac4ccb0c", "test name", "test name drop database public select 1 from dual", "université français", "_timestamp", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_eni", "странное имя", "秒速センチメートル", "camelcase", "int", "user", "select","__root__"),
5253
},
5354
configIds: []string{RedshiftBulkerTypeId + "_serverless", RedshiftBulkerTypeId + "_iam", RedshiftBulkerTypeId},
@@ -61,7 +62,7 @@ func TestNaming(t *testing.T) {
6162
expectedTableCaseChecking: true,
6263
expectedRowsCount: 1,
6364
expectedTable: ExpectedTable{
64-
Columns: justColumns("ID", "NAME", "_TIMESTAMP", "COLUMN_C16DA609B86C01F16A2C609EAC4CCB0C", "COLUMN_12B241E808AE6C964A5BB9F1C012E63D", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_UNNAMED", "LOREM_IPSUM_DOLOR_SIT_AMET_CONSECTETUR_ADIPISCING_ELIT_SED_DO_EIUSMOD_TEMPOR_INCIDIDUNT_UT_LABORE_ET_DOLORE_MAGNA_ALIQUA_UT_ENIM_AD_MINIM_VENIAM_QUIS_NOSTRUD_EXERCITATION_ULLAMCO_LABORIS_NISI_UT_ALIQUIP_EX_EA_COMMODO_CONSEQUAT", "camelCase", "INT", "USER", "SELECT", "__ROOT__", "HASH", "DEFAULT"),
65+
Columns: justColumns("ID", "NAME", "_TIMESTAMP", "COLUMN_C16DA609B86C01F16A2C609EAC4CCB0C", "COLUMN_12B241E808AE6C964A5BB9F1C012E63D", "秒速_センチメートル", "Université Français", "Странное Имя", "Test Name_ DROP DATABASE public_ SELECT 1 from DUAL_", "Test Name", "1test_name", "2", "_UNNAMED", "LOREM_IPSUM_DOLOR_SIT_AMET_CONSECTETUR_ADIPISCING_ELIT_SED_DO_EIUSMOD_TEMPOR_INCIDIDUNT_UT_LABORE_ET_DOLORE_MAGNA_ALIQUA_UT_ENIM_AD_MINIM_VENIAM_QUIS_NOSTRUD_EXERCITATION_ULLAMCO_LABORIS_NISI_UT_ALIQUIP_EX_EA_COMMODO_CONSEQUAT", "camelCase", "INT", "USER", "SELECT", "__ROOT__", "HASH", "DEFAULT", "_CURRENT_TIME"),
6566
},
6667
configIds: []string{SnowflakeBulkerTypeId},
6768
},
@@ -74,7 +75,7 @@ func TestNaming(t *testing.T) {
7475
expectedTableCaseChecking: true,
7576
expectedTable: ExpectedTable{
7677
Name: "strangetablename_replace_table",
77-
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "université français", "странное имя", "test name_ drop database public_ select 1 from dual_", "test name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_e", "camelcase", "int", "user", "select", "__root__", "hash", "default"),
78+
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "秒速_センチメートル", "université français", "странное имя", "test name_ drop database public_ select 1 from dual_", "test name", "1test_name", "2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_e", "camelcase", "int", "user", "select", "__root__", "hash", "default", "current_time"),
7879
},
7980
streamOptions: []bulker.StreamOption{bulker.WithToSameCase()},
8081
configIds: []string{PostgresBulkerTypeId},
@@ -88,7 +89,7 @@ func TestNaming(t *testing.T) {
8889
expectedRowsCount: 1,
8990
expectedTable: ExpectedTable{
9091
Name: "STRANGETABLENAME_BATCH",
91-
Columns: justColumns("ID", "NAME", "_TIMESTAMP", "COLUMN_C16DA609B86C01F16A2C609EAC4CCB0C", "COLUMN_12B241E808AE6C964A5BB9F1C012E63D", "秒速_センチメートル", "UNIVERSITÉ FRANÇAIS", "СТРАННОЕ ИМЯ", "TEST NAME_ DROP DATABASE PUBLIC_ SELECT 1 FROM DUAL_", "TEST NAME", "1TEST_NAME", "2", "_UNNAMED", "LOREM_IPSUM_DOLOR_SIT_AMET_CONSECTETUR_ADIPISCING_ELIT_SED_DO_EIUSMOD_TEMPOR_INCIDIDUNT_UT_LABORE_ET_DOLORE_MAGNA_ALIQUA_UT_ENIM_AD_MINIM_VENIAM_QUIS_NOSTRUD_EXERCITATION_ULLAMCO_LABORIS_NISI_UT_ALIQUIP_EX_EA_COMMODO_CONSEQUAT", "CAMELCASE", "INT", "USER", "SELECT", "__ROOT__", "HASH", "DEFAULT"),
92+
Columns: justColumns("ID", "NAME", "_TIMESTAMP", "COLUMN_C16DA609B86C01F16A2C609EAC4CCB0C", "COLUMN_12B241E808AE6C964A5BB9F1C012E63D", "秒速_センチメートル", "UNIVERSITÉ FRANÇAIS", "СТРАННОЕ ИМЯ", "TEST NAME_ DROP DATABASE PUBLIC_ SELECT 1 FROM DUAL_", "TEST NAME", "1TEST_NAME", "2", "_UNNAMED", "LOREM_IPSUM_DOLOR_SIT_AMET_CONSECTETUR_ADIPISCING_ELIT_SED_DO_EIUSMOD_TEMPOR_INCIDIDUNT_UT_LABORE_ET_DOLORE_MAGNA_ALIQUA_UT_ENIM_AD_MINIM_VENIAM_QUIS_NOSTRUD_EXERCITATION_ULLAMCO_LABORIS_NISI_UT_ALIQUIP_EX_EA_COMMODO_CONSEQUAT", "CAMELCASE", "INT", "USER", "SELECT", "__ROOT__", "HASH", "DEFAULT", "_CURRENT_TIME"),
9293
},
9394
configIds: []string{SnowflakeBulkerTypeId},
9495
streamOptions: []bulker.StreamOption{bulker.WithToSameCase()},
@@ -103,7 +104,7 @@ func TestNaming(t *testing.T) {
103104
expectedRowsCount: 1,
104105
expectedTable: ExpectedTable{
105106
PKFields: []string{"id"},
106-
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "column_b4de5a5c8f92f77af9904705b3f08253", "Universit_Franais", "column_c41d0d6c9ff6db34c6df393bdd283e19", "Test_Name__DROP_DATABASE_public__SELECT_1_from_DUAL_", "Test_Name", "_1test_name", "_2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_enim_ad_minim_veniam_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat", "camelCase", "int", "user", "select", "___ROOT__", "hash", "default"),
107+
Columns: justColumns("id", "name", "_timestamp", "column_c16da609b86c01f16a2c609eac4ccb0c", "column_12b241e808ae6c964a5bb9f1c012e63d", "column_b4de5a5c8f92f77af9904705b3f08253", "Universit_Franais", "column_c41d0d6c9ff6db34c6df393bdd283e19", "Test_Name__DROP_DATABASE_public__SELECT_1_from_DUAL_", "Test_Name", "_1test_name", "_2", "_unnamed", "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_enim_ad_minim_veniam_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat", "camelCase", "int", "user", "select", "___ROOT__", "hash", "default", "current_time"),
107108
},
108109
configIds: []string{BigqueryBulkerTypeId},
109110
streamOptions: []bulker.StreamOption{bulker.WithPrimaryKey("id"), bulker.WithDeduplicate()},

bulkerlib/implementations/sql/snowflake.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ import (
66
"database/sql"
77
"errors"
88
"fmt"
9-
"github.com/hashicorp/go-multierror"
10-
bulker "github.com/jitsucom/bulker/bulkerlib"
11-
types2 "github.com/jitsucom/bulker/bulkerlib/types"
12-
"github.com/jitsucom/bulker/jitsubase/errorj"
13-
"github.com/jitsucom/bulker/jitsubase/logging"
14-
"github.com/jitsucom/bulker/jitsubase/types"
15-
"github.com/jitsucom/bulker/jitsubase/utils"
169
"os"
1710
"path"
1811
"regexp"
@@ -21,6 +14,14 @@ import (
2114
"text/template"
2215
"time"
2316

17+
"github.com/hashicorp/go-multierror"
18+
bulker "github.com/jitsucom/bulker/bulkerlib"
19+
types2 "github.com/jitsucom/bulker/bulkerlib/types"
20+
"github.com/jitsucom/bulker/jitsubase/errorj"
21+
"github.com/jitsucom/bulker/jitsubase/logging"
22+
"github.com/jitsucom/bulker/jitsubase/types"
23+
"github.com/jitsucom/bulker/jitsubase/utils"
24+
2425
sf "github.com/snowflakedb/gosnowflake"
2526
)
2627

@@ -47,8 +48,11 @@ const (
4748
)
4849

4950
var (
50-
sfReservedWords = []string{"all", "alter", "and", "any", "as", "between", "by", "case", "cast", "check", "column", "connect", "constraint", "create", "cross", "current", "current_date", "current_time", "current_timestamp", "current_user", "default", "delete", "distinct", "drop", "else", "exists", "false", "following", "for", "from", "full", "grant", "group", "having", "ilike", "in", "increment", "inner", "insert", "intersect", "into", "is", "join", "lateral", "left", "like", "localtime", "localtimestamp", "minus", "natural", "not", "null", "of", "on", "or", "order", "qualify", "regexp", "revoke", "right", "rlike", "row", "rows", "sample", "select", "set", "some", "start", "table", "tablesample", "then", "to", "trigger", "true", "try_cast", "union", "unique", "update", "using", "values", "when", "whenever", "where", "with"}
51+
sfReservedWords = []string{"all", "alter", "and", "any", "as", "between", "by", "case", "cast", "check", "column", "connect", "constraint", "create", "cross", "current", "current_date", "current_time", "current_timestamp", "current_user", "default", "delete", "distinct", "drop", "else", "exists", "false", "following", "for", "from", "full", "grant", "group", "having", "ilike", "in", "increment", "inner", "insert", "intersect", "into", "is", "join", "lateral", "left", "like", "localtime", "localtimestamp", "minus", "natural", "not", "null", "of", "on", "or", "order", "qualify", "regexp", "revoke", "right", "rlike", "row", "rows", "sample", "select", "set", "some", "start", "table", "tablesample", "then", "to", "trigger", "true", "try_cast", "union", "unique", "update", "using", "values", "when", "whenever", "where", "with"}
52+
sfReservedColumnNames = []string{"constraint", "current_date", "current_time", "current_timestamp", "current_user", "localtime", "localtimestamp"}
53+
5154
sfReservedWordsSet = types.NewSet(sfReservedWords...)
55+
sfReservedColumnNamesSet = types.NewSet(sfReservedColumnNames...)
5256
sfUnquotedIdentifierPattern = regexp.MustCompile(`^[a-z_][0-9a-z_]*$|^[A-Z_][0-9A-Z_]*$`)
5357

5458
sfMergeQueryTemplate, _ = template.New("snowflakeMergeQuery").Parse(sfMergeStatement)
@@ -84,6 +88,9 @@ func init() {
8488
for _, word := range sfReservedWords {
8589
sfReservedWordsSet.Put(strings.ToUpper(word))
8690
}
91+
for _, word := range sfReservedColumnNames {
92+
sfReservedColumnNamesSet.Put(strings.ToUpper(word))
93+
}
8794
}
8895

8996
// Validate required fields in SnowflakeConfig
@@ -537,6 +544,9 @@ func (s *Snowflake) Select(ctx context.Context, namespace string, tableName stri
537544
}
538545

539546
func sfIdentifierFunction(value string, alphanumeric bool) (adapted string, needQuotes bool) {
547+
if sfReservedColumnNamesSet.Contains(value) {
548+
return "_" + strings.ToUpper(value), false
549+
}
540550
if sfReservedWordsSet.Contains(value) {
541551
return strings.ToUpper(value), true
542552
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"id":0, "name": "dummy", "_timestamp": "2022-08-18T14:17:22.375Z","?!": 1,"%#@": 2, "秒速5センチメートル": 3, "Université Français": 4, "Странное Имя": 5, "Test Name; DROP DATABASE public; SELECT 1 from DUAL;": 6, "Test Name": 7, "1test_name": 8, "2": 9, "": 10, "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_enim_ad_minim_veniam_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat": 11, "camelCase": 12, "int": 13, "user": 14, "select": 15, "__ROOT__": 16, "hash": 17, "default": 18}
1+
{"id":0, "name": "dummy", "_timestamp": "2022-08-18T14:17:22.375Z","?!": 1,"%#@": 2, "秒速5センチメートル": 3, "Université Français": 4, "Странное Имя": 5, "Test Name; DROP DATABASE public; SELECT 1 from DUAL;": 6, "Test Name": 7, "1test_name": 8, "2": 9, "": 10, "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua_ut_enim_ad_minim_veniam_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat": 11, "camelCase": 12, "int": 13, "user": 14, "select": 15, "__ROOT__": 16, "hash": 17, "default": 18, "current_time": 19}

0 commit comments

Comments
 (0)