diff --git a/dbm-services/common/db-resource/internal/controller/manage/rs_labels.go b/dbm-services/common/db-resource/internal/controller/manage/rs_labels.go new file mode 100644 index 0000000000..4f44c26214 --- /dev/null +++ b/dbm-services/common/db-resource/internal/controller/manage/rs_labels.go @@ -0,0 +1,42 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package manage + +import ( + rf "github.com/gin-gonic/gin" + + "dbm-services/common/db-resource/internal/model" + "dbm-services/common/go-pubpkg/logger" +) + +// AddLabelsParam add labels param +type AddLabelsParam struct { + BkHostIds []int `json:"bk_host_ids" binding:"required,gt=0,dive"` + Labels []string `json:"labels,omitempty"` +} + +// AddLabels add labels +func (c *MachineResourceHandler) AddLabels(r *rf.Context) { + var input AddLabelsParam + if err := c.Prepare(r, &input); err != nil { + logger.Error("Preare Error %s", err.Error()) + return + } + db := model.DB.Self.Table("tb_rp_detail").Exec("update tb_rp_detail set labels=? where bk_host_id in (?)", + model.JsonMerge("labels", input.Labels), input.BkHostIds) + err := db.Error + if err != nil { + logger.Error("failed to add labels:%s", err.Error()) + c.SendResponse(r, err, nil) + return + } + c.SendResponse(r, nil, map[string]interface{}{"affected_count": db.RowsAffected}) +} diff --git a/dbm-services/common/db-resource/internal/controller/manage/rs_lable.go b/dbm-services/common/db-resource/internal/controller/manage/rs_lable.go deleted file mode 100644 index 9c9f45697a..0000000000 --- a/dbm-services/common/db-resource/internal/controller/manage/rs_lable.go +++ /dev/null @@ -1,52 +0,0 @@ -package manage - -import ( - "fmt" - - "dbm-services/common/db-resource/internal/controller" - "dbm-services/common/db-resource/internal/model" - "dbm-services/common/go-pubpkg/cmutil" - "dbm-services/common/go-pubpkg/logger" - - rf "github.com/gin-gonic/gin" -) - -// LableHandler TODO -type LableHandler struct { - controller.BaseHandler -} - -// LableEditInput TODO -type LableEditInput struct { - BkHostIds []int `json:"bk_host_ids" binding:"required"` - Labels map[string]string `json:"labels"` -} - -// Edit TODO -func (c *LableHandler) Edit(r *rf.Context) { - var input LableEditInput - if err := c.Prepare(r, &input); err != nil { - logger.Error(fmt.Sprintf("Preare Error %s", err.Error())) - return - } - // requestId := r.GetString("request_id") - lableJson, err := cmutil.ConverMapToJsonStr(cmutil.CleanStrMap(input.Labels)) - if err != nil { - logger.Error(fmt.Sprintf("ConverLableToJsonStr Failed,Error:%s", err.Error())) - c.SendResponse(r, err, nil) - return - } - if len(input.BkHostIds) == 0 { - c.SendResponse(r, nil, nil) - return - } - err = model.DB.Self.Table(model.TbRpDetailName()).Where("bk_host_id in ? ", input.BkHostIds).Update("labels", - lableJson). - Error - if err != nil { - logger.Error(fmt.Sprintf("Update Lable Failed %s", err.Error())) - c.SendResponse(r, err, nil) - return - } - c.SendResponse(r, nil, nil) -} diff --git a/dbm-services/common/db-resource/internal/controller/manage/rs_manage.go b/dbm-services/common/db-resource/internal/controller/manage/rs_manage.go index 7a075bdd7e..3105543e06 100644 --- a/dbm-services/common/db-resource/internal/controller/manage/rs_manage.go +++ b/dbm-services/common/db-resource/internal/controller/manage/rs_manage.go @@ -23,6 +23,7 @@ import ( "dbm-services/common/go-pubpkg/logger" rf "github.com/gin-gonic/gin" + "github.com/samber/lo" ) // MachineResourceHandler 主机处理handler @@ -45,6 +46,7 @@ func (c *MachineResourceHandler) RegisterRouter(engine *rf.Engine) { r.POST("/list/all", c.ListAll) r.POST("/update", c.Update) r.POST("/batch/update", c.BatchUpdate) + r.POST("/append/labels", c.AddLabels) r.POST("/delete", c.Delete) r.POST("/import", c.Import) r.POST("/mountpoints", c.GetMountPoints) @@ -307,15 +309,15 @@ func (c *MachineResourceHandler) GroupByLabelCount(r *rf.Context) { logger.Info("rs len %d", len(rs)) ret := make(map[string]int) for _, v := range rs { - var lables []string + var labels []string logger.Info("labels %s", string(v.Labels)) - if err := json.Unmarshal(v.Labels, &lables); err != nil { + if err := json.Unmarshal(v.Labels, &labels); err != nil { logger.Error("unmarshal failed %s", err.Error()) c.SendResponse(r, err, err.Error()) return } - if len(lables) > 0 { - for _, l := range lables { + if len(labels) > 0 { + for _, l := range lo.Uniq(labels) { ret[l]++ } } diff --git a/dbm-services/common/db-resource/internal/model/json_merge.go b/dbm-services/common/db-resource/internal/model/json_merge.go new file mode 100644 index 0000000000..1105c9de9e --- /dev/null +++ b/dbm-services/common/db-resource/internal/model/json_merge.go @@ -0,0 +1,49 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package model + +import ( + "github.com/samber/lo" + "gorm.io/gorm/clause" +) + +// JSONMergeBuilder json query expression, implements clause.Expression interface to use as querier +type JSONMergeBuilder struct { + column string + keys []string +} + +// Build json merge expression +// nolint +func (m *JSONMergeBuilder) Build(builder clause.Builder) { + builder.WriteString("JSON_MERGE(") + builder.WriteString(m.column) + builder.WriteString(",") + builder.WriteByte('\'') + builder.WriteByte('[') + for i, key := range lo.Uniq(m.keys) { + if i > 0 { + builder.WriteByte(',') + } + builder.WriteString("\"" + key + "\"") + } + builder.WriteByte(']') + builder.WriteByte('\'') + builder.WriteByte(')') +} + +// JsonMerge IntArry json merge int array +func JsonMerge(column string, keys []string) *JSONMergeBuilder { + return &JSONMergeBuilder{ + column: column, + keys: keys, + } +} diff --git a/dbm-services/common/db-resource/internal/model/json_query.go b/dbm-services/common/db-resource/internal/model/json_query.go new file mode 100644 index 0000000000..78a038b82d --- /dev/null +++ b/dbm-services/common/db-resource/internal/model/json_query.go @@ -0,0 +1,335 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package model + +import ( + "strconv" + "strings" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +// JSONQueryExpression json query expression, implements clause.Expression interface to use as querier +type JSONQueryExpression struct { + column string + keys []string + hasKeys bool + equals bool + equalsValue interface{} + extract bool + path string + numranges bool + numRange NumRange + Gtv int + gte bool + Ltv int + lte bool + contains bool + containVals []string + mapcontains bool + mapcontainVals []string + subcontains bool + subcontainVal string + jointOrContains bool + jointOrContainVals []string +} + +// NumRange num range +type NumRange struct { + Min int + Max int +} + +// JSONQuery query column as json +func JSONQuery(column string) *JSONQueryExpression { + return &JSONQueryExpression{column: column} +} + +// SubValContains sub value contains +func (jsonQuery *JSONQueryExpression) SubValContains(val string, key string) *JSONQueryExpression { + jsonQuery.subcontains = true + jsonQuery.subcontainVal = val + jsonQuery.keys = []string{key} + return jsonQuery +} + +// KeysContains key contains +func (jsonQuery *JSONQueryExpression) KeysContains(val []string) *JSONQueryExpression { + jsonQuery.mapcontains = true + jsonQuery.mapcontainVals = val + return jsonQuery +} + +// Contains contains +// Extract extract json with path +func (jsonQuery *JSONQueryExpression) Contains(val []string) *JSONQueryExpression { + jsonQuery.contains = true + jsonQuery.containVals = val + return jsonQuery +} + +// JointOrContains jointOrContains +func (jsonQuery *JSONQueryExpression) JointOrContains(val []string) *JSONQueryExpression { + jsonQuery.jointOrContains = true + jsonQuery.jointOrContainVals = val + + return jsonQuery +} + +// Extract extract json with path +func (jsonQuery *JSONQueryExpression) Extract(path string) *JSONQueryExpression { + jsonQuery.extract = true + jsonQuery.path = path + return jsonQuery +} + +// NumRange num range +// HasKey returns clause.Expression +func (jsonQuery *JSONQueryExpression) NumRange(min int, max int, keys ...string) *JSONQueryExpression { + jsonQuery.keys = keys + jsonQuery.numRange = NumRange{ + Min: min, + Max: max, + } + jsonQuery.numranges = true + return jsonQuery +} + +// Gte gte +func (jsonQuery *JSONQueryExpression) Gte(val int, keys ...string) *JSONQueryExpression { + jsonQuery.keys = keys + jsonQuery.Gtv = val + jsonQuery.gte = true + return jsonQuery +} + +// Lte lte +func (jsonQuery *JSONQueryExpression) Lte(val int, keys ...string) *JSONQueryExpression { + jsonQuery.keys = keys + jsonQuery.Ltv = val + jsonQuery.lte = true + return jsonQuery +} + +// HasKey returns clause.Expression +func (jsonQuery *JSONQueryExpression) HasKey(keys ...string) *JSONQueryExpression { + jsonQuery.keys = keys + jsonQuery.hasKeys = true + return jsonQuery +} + +// Equals equals +// Keys returns clause.Expression +func (jsonQuery *JSONQueryExpression) Equals(value interface{}, keys ...string) *JSONQueryExpression { + jsonQuery.keys = keys + jsonQuery.equals = true + jsonQuery.equalsValue = value + return jsonQuery +} + +// jointOrContainsBuild jointOrContainsBuild +// nolint +func (jsonQuery *JSONQueryExpression) jointOrContainsBuild(builder clause.Builder) { + for idx, v := range jsonQuery.jointOrContainVals { + if idx != 0 { + builder.WriteString(" OR ") + } + builder.WriteString("JSON_CONTAINS(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteString(",'") + builder.WriteString("[\"") + builder.WriteString(v) + builder.WriteString("\"]') ") + } +} + +// extractBuild extractBuild +// nolint +func (jsonQuery *JSONQueryExpression) extractBuild(stmt *gorm.Statement, builder clause.Builder) { + builder.WriteString("JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteByte(',') + builder.AddVar(stmt, jsonQuery.path) + builder.WriteString(")") +} + +// hasKeysBuild build has key query +// nolint +func (jsonQuery *JSONQueryExpression) hasKeysBuild(stmt *gorm.Statement, builder clause.Builder) { + if len(jsonQuery.keys) > 0 { + builder.WriteString("JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteByte(',') + builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) + builder.WriteString(") IS NOT NULL") + } +} + +// gteBuild build gte query +// nolint +func (jsonQuery *JSONQueryExpression) gteBuild(stmt *gorm.Statement, builder clause.Builder) { + builder.WriteString("JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteByte(',') + builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) + builder.WriteString(") >=") + builder.WriteString(strconv.Itoa(jsonQuery.Gtv)) +} + +// lteBuild build lte query +// nolint +func (jsonQuery *JSONQueryExpression) lteBuild(stmt *gorm.Statement, builder clause.Builder) { + builder.WriteString("JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteByte(',') + builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) + builder.WriteString(") <=") + builder.WriteString(strconv.Itoa(jsonQuery.Ltv)) +} + +// numrangesBuild build num range query +// nolint +func (jsonQuery *JSONQueryExpression) numrangesBuild(stmt *gorm.Statement, builder clause.Builder) { + builder.WriteString("JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteByte(',') + builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) + builder.WriteString(") ") + builder.WriteString(" BETWEEN ") + builder.WriteString(strconv.Itoa(jsonQuery.numRange.Min)) + builder.WriteString(" AND ") + builder.WriteString(strconv.Itoa(jsonQuery.numRange.Max)) +} + +// mapcontainsBuild build map contains query +// nolint +func (jsonQuery *JSONQueryExpression) mapcontainsBuild(builder clause.Builder) { + builder.WriteString("JSON_CONTAINS(JSON_KEYS(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteString("),'[") + builder.WriteString(jsonArryJoin(jsonQuery.mapcontainVals)) + builder.WriteString("]') ") +} + +// containsBuild build contains query +// nolint +func (jsonQuery *JSONQueryExpression) containsBuild(builder clause.Builder) { + builder.WriteString("JSON_CONTAINS(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteString(",'") + builder.WriteString("[") + builder.WriteString(jsonArryJoin(jsonQuery.containVals)) + builder.WriteString("]') ") +} + +// subcontainsBuild build subcontains query +// nolint +func (jsonQuery *JSONQueryExpression) subcontainsBuild(builder clause.Builder) { + builder.WriteString("JSON_CONTAINS(JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteString(",'$.*.\"") + builder.WriteString(jsonQuery.keys[0]) + builder.WriteString("\"'),'[\"") + builder.WriteString(jsonQuery.subcontainVal) + builder.WriteString("\"]') ") +} + +// equalsBuild build equal query +// nolint +func (jsonQuery *JSONQueryExpression) equalsBuild(stmt *gorm.Statement, builder clause.Builder) { + if len(jsonQuery.keys) > 0 { + builder.WriteString("JSON_EXTRACT(") + builder.WriteQuoted(jsonQuery.column) + builder.WriteByte(',') + builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) + builder.WriteString(") = ") + if value, ok := jsonQuery.equalsValue.(bool); ok { + builder.WriteString(strconv.FormatBool(value)) + } else { + stmt.AddVar(builder, jsonQuery.equalsValue) + } + } +} + +// Build implements clause.Expression +func (jsonQuery *JSONQueryExpression) Build(builder clause.Builder) { + if stmt, ok := builder.(*gorm.Statement); ok { + if stmt.Dialector.Name() == "mysql" { + switch { + case jsonQuery.extract: + jsonQuery.extractBuild(stmt, builder) + case jsonQuery.hasKeys: + jsonQuery.hasKeysBuild(stmt, builder) + case jsonQuery.gte: + jsonQuery.gteBuild(stmt, builder) + case jsonQuery.lte: + jsonQuery.lteBuild(stmt, builder) + case jsonQuery.numranges: + jsonQuery.numrangesBuild(stmt, builder) + case jsonQuery.mapcontains: + jsonQuery.mapcontainsBuild(builder) + case jsonQuery.contains: + jsonQuery.containsBuild(builder) + case jsonQuery.jointOrContains: + jsonQuery.jointOrContainsBuild(builder) + case jsonQuery.subcontains: + jsonQuery.subcontainsBuild(builder) + case jsonQuery.equals: + jsonQuery.equalsBuild(stmt, builder) + } + } + } +} + +func jsonArryJoin(vals []string) string { + n := len(vals) - 1 + for i := 0; i < len(vals); i++ { + n += len(vals[i]) + } + var b strings.Builder + b.Grow(n) + for idx, val := range vals { + b.WriteString("\"") + b.WriteString(val) + b.WriteString("\"") + if idx < len(vals)-1 { + b.WriteString(",") + } + } + return b.String() +} + +const prefix = "$." + +func jsonQueryJoin(keys []string) string { + if len(keys) == 1 { + return prefix + keys[0] + } + + n := len(prefix) + n += len(keys) - 1 + for i := 0; i < len(keys); i++ { + n += len(keys[i]) + } + + var b strings.Builder + b.Grow(n) + b.WriteString(prefix) + b.WriteString("\"") + b.WriteString(keys[0]) + b.WriteString("\"") + for _, key := range keys[1:] { + b.WriteString(".") + b.WriteString(key) + } + return b.String() +} diff --git a/dbm-services/common/db-resource/internal/model/model.go b/dbm-services/common/db-resource/internal/model/model.go index 1f5ffe0e57..005044e7ec 100644 --- a/dbm-services/common/db-resource/internal/model/model.go +++ b/dbm-services/common/db-resource/internal/model/model.go @@ -16,8 +16,6 @@ import ( "fmt" "log" "os" - "strconv" - "strings" "time" "dbm-services/common/db-resource/assets" @@ -26,7 +24,6 @@ import ( "gorm.io/driver/mysql" "gorm.io/gorm" - "gorm.io/gorm/clause" gormlogger "gorm.io/gorm/logger" ) @@ -147,319 +144,3 @@ func initSelfDB() *gorm.DB { config.AppConfig.Db.Name, ) } - -// JSONQueryExpression json query expression, implements clause.Expression interface to use as querier -type JSONQueryExpression struct { - column string - keys []string - hasKeys bool - equals bool - equalsValue interface{} - extract bool - path string - numranges bool - numRange NumRange - Gtv int - gte bool - Ltv int - lte bool - contains bool - containVals []string - mapcontains bool - mapcontainVals []string - subcontains bool - subcontainVal string - jointOrContains bool - jointOrContainVals []string -} - -// NumRange num range -type NumRange struct { - Min int - Max int -} - -// JSONQuery query column as json -func JSONQuery(column string) *JSONQueryExpression { - return &JSONQueryExpression{column: column} -} - -// SubValContains sub value contains -func (jsonQuery *JSONQueryExpression) SubValContains(val string, key string) *JSONQueryExpression { - jsonQuery.subcontains = true - jsonQuery.subcontainVal = val - jsonQuery.keys = []string{key} - return jsonQuery -} - -// KeysContains key contains -func (jsonQuery *JSONQueryExpression) KeysContains(val []string) *JSONQueryExpression { - jsonQuery.mapcontains = true - jsonQuery.mapcontainVals = val - return jsonQuery -} - -// Contains contains -// Extract extract json with path -func (jsonQuery *JSONQueryExpression) Contains(val []string) *JSONQueryExpression { - jsonQuery.contains = true - jsonQuery.containVals = val - return jsonQuery -} - -// JointOrContains jointOrContains -func (jsonQuery *JSONQueryExpression) JointOrContains(val []string) *JSONQueryExpression { - jsonQuery.jointOrContains = true - jsonQuery.jointOrContainVals = val - - return jsonQuery -} - -// Extract extract json with path -func (jsonQuery *JSONQueryExpression) Extract(path string) *JSONQueryExpression { - jsonQuery.extract = true - jsonQuery.path = path - return jsonQuery -} - -// NumRange num range -// HasKey returns clause.Expression -func (jsonQuery *JSONQueryExpression) NumRange(min int, max int, keys ...string) *JSONQueryExpression { - jsonQuery.keys = keys - jsonQuery.numRange = NumRange{ - Min: min, - Max: max, - } - jsonQuery.numranges = true - return jsonQuery -} - -// Gte gte -func (jsonQuery *JSONQueryExpression) Gte(val int, keys ...string) *JSONQueryExpression { - jsonQuery.keys = keys - jsonQuery.Gtv = val - jsonQuery.gte = true - return jsonQuery -} - -// Lte lte -func (jsonQuery *JSONQueryExpression) Lte(val int, keys ...string) *JSONQueryExpression { - jsonQuery.keys = keys - jsonQuery.Ltv = val - jsonQuery.lte = true - return jsonQuery -} - -// HasKey returns clause.Expression -func (jsonQuery *JSONQueryExpression) HasKey(keys ...string) *JSONQueryExpression { - jsonQuery.keys = keys - jsonQuery.hasKeys = true - return jsonQuery -} - -// Equals equals -// Keys returns clause.Expression -func (jsonQuery *JSONQueryExpression) Equals(value interface{}, keys ...string) *JSONQueryExpression { - jsonQuery.keys = keys - jsonQuery.equals = true - jsonQuery.equalsValue = value - return jsonQuery -} - -// jointOrContainsBuild jointOrContainsBuild -// nolint -func (jsonQuery *JSONQueryExpression) jointOrContainsBuild(builder clause.Builder) { - for idx, v := range jsonQuery.jointOrContainVals { - if idx != 0 { - builder.WriteString(" OR ") - } - builder.WriteString("JSON_CONTAINS(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteString(",'") - builder.WriteString("[\"") - builder.WriteString(v) - builder.WriteString("\"]') ") - } -} - -// extractBuild extractBuild -// nolint -func (jsonQuery *JSONQueryExpression) extractBuild(stmt *gorm.Statement, builder clause.Builder) { - builder.WriteString("JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteByte(',') - builder.AddVar(stmt, jsonQuery.path) - builder.WriteString(")") -} - -// hasKeysBuild build has key query -// nolint -func (jsonQuery *JSONQueryExpression) hasKeysBuild(stmt *gorm.Statement, builder clause.Builder) { - if len(jsonQuery.keys) > 0 { - builder.WriteString("JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteByte(',') - builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) - builder.WriteString(") IS NOT NULL") - } -} - -// gteBuild build gte query -// nolint -func (jsonQuery *JSONQueryExpression) gteBuild(stmt *gorm.Statement, builder clause.Builder) { - builder.WriteString("JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteByte(',') - builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) - builder.WriteString(") >=") - builder.WriteString(strconv.Itoa(jsonQuery.Gtv)) -} - -// lteBuild build lte query -// nolint -func (jsonQuery *JSONQueryExpression) lteBuild(stmt *gorm.Statement, builder clause.Builder) { - builder.WriteString("JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteByte(',') - builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) - builder.WriteString(") <=") - builder.WriteString(strconv.Itoa(jsonQuery.Ltv)) -} - -// numrangesBuild build num range query -// nolint -func (jsonQuery *JSONQueryExpression) numrangesBuild(stmt *gorm.Statement, builder clause.Builder) { - builder.WriteString("JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteByte(',') - builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) - builder.WriteString(") ") - builder.WriteString(" BETWEEN ") - builder.WriteString(strconv.Itoa(jsonQuery.numRange.Min)) - builder.WriteString(" AND ") - builder.WriteString(strconv.Itoa(jsonQuery.numRange.Max)) -} - -// mapcontainsBuild build map contains query -// nolint -func (jsonQuery *JSONQueryExpression) mapcontainsBuild(builder clause.Builder) { - builder.WriteString("JSON_CONTAINS(JSON_KEYS(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteString("),'[") - builder.WriteString(jsonArryJoin(jsonQuery.mapcontainVals)) - builder.WriteString("]') ") -} - -// containsBuild build contains query -// nolint -func (jsonQuery *JSONQueryExpression) containsBuild(builder clause.Builder) { - builder.WriteString("JSON_CONTAINS(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteString(",'") - builder.WriteString("[") - builder.WriteString(jsonArryJoin(jsonQuery.containVals)) - builder.WriteString("]') ") -} - -// subcontainsBuild build subcontains query -// nolint -func (jsonQuery *JSONQueryExpression) subcontainsBuild(builder clause.Builder) { - builder.WriteString("JSON_CONTAINS(JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteString(",'$.*.\"") - builder.WriteString(jsonQuery.keys[0]) - builder.WriteString("\"'),'[\"") - builder.WriteString(jsonQuery.subcontainVal) - builder.WriteString("\"]') ") -} - -// equalsBuild build equal query -// nolint -func (jsonQuery *JSONQueryExpression) equalsBuild(stmt *gorm.Statement, builder clause.Builder) { - if len(jsonQuery.keys) > 0 { - builder.WriteString("JSON_EXTRACT(") - builder.WriteQuoted(jsonQuery.column) - builder.WriteByte(',') - builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys)) - builder.WriteString(") = ") - if value, ok := jsonQuery.equalsValue.(bool); ok { - builder.WriteString(strconv.FormatBool(value)) - } else { - stmt.AddVar(builder, jsonQuery.equalsValue) - } - } -} - -// Build implements clause.Expression -func (jsonQuery *JSONQueryExpression) Build(builder clause.Builder) { - if stmt, ok := builder.(*gorm.Statement); ok { - if stmt.Dialector.Name() == "mysql" { - switch { - case jsonQuery.extract: - jsonQuery.extractBuild(stmt, builder) - case jsonQuery.hasKeys: - jsonQuery.hasKeysBuild(stmt, builder) - case jsonQuery.gte: - jsonQuery.gteBuild(stmt, builder) - case jsonQuery.lte: - jsonQuery.lteBuild(stmt, builder) - case jsonQuery.numranges: - jsonQuery.numrangesBuild(stmt, builder) - case jsonQuery.mapcontains: - jsonQuery.mapcontainsBuild(builder) - case jsonQuery.contains: - jsonQuery.containsBuild(builder) - case jsonQuery.jointOrContains: - jsonQuery.jointOrContainsBuild(builder) - case jsonQuery.subcontains: - jsonQuery.subcontainsBuild(builder) - case jsonQuery.equals: - jsonQuery.equalsBuild(stmt, builder) - } - } - } -} - -func jsonArryJoin(vals []string) string { - n := len(vals) - 1 - for i := 0; i < len(vals); i++ { - n += len(vals[i]) - } - var b strings.Builder - b.Grow(n) - for idx, val := range vals { - b.WriteString("\"") - b.WriteString(val) - b.WriteString("\"") - if idx < len(vals)-1 { - b.WriteString(",") - } - } - return b.String() -} - -const prefix = "$." - -func jsonQueryJoin(keys []string) string { - if len(keys) == 1 { - return prefix + keys[0] - } - - n := len(prefix) - n += len(keys) - 1 - for i := 0; i < len(keys); i++ { - n += len(keys[i]) - } - - var b strings.Builder - b.Grow(n) - b.WriteString(prefix) - b.WriteString("\"") - b.WriteString(keys[0]) - b.WriteString("\"") - for _, key := range keys[1:] { - b.WriteString(".") - b.WriteString(key) - } - return b.String() -}