github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/sqlquery/impl.go (about) 1 /* 2 * Copyright (c) 2022-present unTill Pro, Ltd. 3 */ 4 5 package sqlquery 6 7 import ( 8 "context" 9 "fmt" 10 "net/http" 11 "strconv" 12 13 "github.com/blastrain/vitess-sqlparser/sqlparser" 14 15 "github.com/voedger/voedger/pkg/appdef" 16 "github.com/voedger/voedger/pkg/appparts" 17 "github.com/voedger/voedger/pkg/goutils/logger" 18 "github.com/voedger/voedger/pkg/istructs" 19 "github.com/voedger/voedger/pkg/processors" 20 "github.com/voedger/voedger/pkg/sys/authnz" 21 coreutils "github.com/voedger/voedger/pkg/utils" 22 ) 23 24 func execQrySqlQuery(asp istructs.IAppStructsProvider, appQName istructs.AppQName) func(ctx context.Context, args istructs.ExecQueryArgs, callback istructs.ExecQueryCallback) (err error) { 25 return func(ctx context.Context, args istructs.ExecQueryArgs, callback istructs.ExecQueryCallback) (err error) { 26 27 query := args.ArgumentObject.AsString(field_Query) 28 app := appQName 29 wsID := args.WSID 30 31 if a, w, c, err := parseQueryAppWs(query); err == nil { 32 if a != istructs.NullAppQName { 33 app = a 34 } 35 if w != 0 { 36 wsID = w 37 } 38 query = c 39 } 40 41 appStructs, err := asp.AppStructs(app) 42 if err != nil { 43 return err 44 } 45 46 if wsID != args.WSID { 47 wsDesc, err := appStructs.Records().GetSingleton(wsID, authnz.QNameCDocWorkspaceDescriptor) 48 if err != nil { 49 // notest 50 return err 51 } 52 if wsDesc.QName() == appdef.NullQName { 53 return coreutils.NewHTTPErrorf(http.StatusBadRequest, fmt.Sprintf("wsid %d: %s", wsID, processors.ErrWSNotInited.Message)) 54 } 55 if ws := appStructs.AppDef().WorkspaceByDescriptor(wsDesc.AsQName(authnz.Field_WSKind)); ws == nil { 56 return coreutils.NewHTTPErrorf(http.StatusBadRequest, fmt.Sprintf("no workspace by QName of its descriptor %s from wsid %d", wsDesc.QName(), wsID)) 57 } 58 } 59 60 stmt, err := sqlparser.Parse(query) 61 if err != nil { 62 return err 63 } 64 s := stmt.(*sqlparser.Select) 65 66 f := &filter{fields: make(map[string]bool)} 67 for _, intf := range s.SelectExprs { 68 switch expr := intf.(type) { 69 case *sqlparser.StarExpr: 70 f.acceptAll = true 71 case *sqlparser.AliasedExpr: 72 column := expr.Expr.(*sqlparser.ColName) 73 if !column.Qualifier.Name.IsEmpty() { 74 f.fields[fmt.Sprintf("%s.%s", column.Qualifier.Name, column.Name)] = true 75 } else { 76 f.fields[column.Name.String()] = true 77 } 78 } 79 } 80 81 var whereExpr sqlparser.Expr 82 if s.Where == nil { 83 whereExpr = nil 84 } else { 85 whereExpr = s.Where.Expr 86 } 87 88 table := s.From[0].(*sqlparser.AliasedTableExpr).Expr.(sqlparser.TableName) 89 source := appdef.NewQName(table.Qualifier.String(), table.Name.String()) 90 91 switch appStructs.AppDef().Type(source).Kind() { 92 case appdef.TypeKind_ViewRecord: 93 return readViewRecords(ctx, wsID, appdef.NewQName(table.Qualifier.String(), table.Name.String()), whereExpr, appStructs, f, callback) 94 case appdef.TypeKind_CDoc: 95 fallthrough 96 case appdef.TypeKind_CRecord: 97 fallthrough 98 case appdef.TypeKind_WDoc: 99 return readRecords(wsID, source, whereExpr, appStructs, f, callback) 100 default: 101 if source != plog && source != wlog { 102 break 103 } 104 limit, offset, e := params(whereExpr, s.Limit) 105 if e != nil { 106 return e 107 } 108 appParts := args.Workpiece.(interface { 109 AppPartitions() appparts.IAppPartitions 110 }).AppPartitions() 111 if source == plog { 112 return readPlog(ctx, wsID, offset, limit, appStructs, f, callback, appStructs.AppDef(), appParts) 113 } 114 return readWlog(ctx, wsID, offset, limit, appStructs, f, callback, appStructs.AppDef()) 115 } 116 117 return fmt.Errorf("unsupported source: %s", source) 118 } 119 } 120 121 func params(expr sqlparser.Expr, limit *sqlparser.Limit) (int, istructs.Offset, error) { 122 l, err := lim(limit) 123 if err != nil { 124 return 0, 0, err 125 } 126 o, eq, err := offs(expr) 127 if err != nil { 128 return 0, 0, err 129 } 130 if eq && l != 0 { 131 l = 1 132 } 133 return l, o, nil 134 } 135 136 func lim(limit *sqlparser.Limit) (int, error) { 137 if limit == nil { 138 return DefaultLimit, nil 139 } 140 v, err := parseInt64(limit.Rowcount.(*sqlparser.SQLVal).Val) 141 if err != nil { 142 return 0, err 143 } 144 if v < -1 { 145 return 0, fmt.Errorf("limit must be greater than -2") 146 } 147 if v == -1 { 148 return istructs.ReadToTheEnd, nil 149 } 150 return int(v), err 151 } 152 153 func offs(expr sqlparser.Expr) (istructs.Offset, bool, error) { 154 o := DefaultOffset 155 eq := false 156 switch r := expr.(type) { 157 case *sqlparser.ComparisonExpr: 158 if r.Left.(*sqlparser.ColName).Name.String() != "offset" { 159 return 0, false, fmt.Errorf("unsupported column name: %s", r.Left.(*sqlparser.ColName).Name.String()) 160 } 161 v, e := parseInt64(r.Right.(*sqlparser.SQLVal).Val) 162 if e != nil { 163 return 0, false, e 164 } 165 switch r.Operator { 166 case sqlparser.EqualStr: 167 eq = true 168 fallthrough 169 case sqlparser.GreaterEqualStr: 170 o = istructs.Offset(v) 171 case sqlparser.GreaterThanStr: 172 o = istructs.Offset(v + 1) 173 default: 174 return 0, false, fmt.Errorf("unsupported operation: %s", r.Operator) 175 } 176 if o <= 0 { 177 return 0, false, fmt.Errorf("offset must be greater than zero") 178 } 179 case nil: 180 default: 181 return 0, false, fmt.Errorf("unsupported expression: %T", r) 182 } 183 return o, eq, nil 184 } 185 186 func parseInt64(bb []byte) (int64, error) { 187 return strconv.ParseInt(string(bb), base, bitSize64) 188 } 189 190 func getFilter(f func(string) bool) coreutils.MapperOpt { 191 return coreutils.Filter(func(name string, kind appdef.DataKind) bool { 192 return f(name) 193 }) 194 } 195 196 func renderDbEvent(data map[string]interface{}, f *filter, event istructs.IDbEvent, appDef appdef.IAppDef, offset istructs.Offset) { 197 defer func() { 198 if r := recover(); r != nil { 199 eventKind := "plog" 200 if _, ok := event.(istructs.IWLogEvent); ok { 201 eventKind = "wlog" 202 } 203 logger.Error(fmt.Sprintf("failed to render %s event %s offset %d registered at %s: %v", eventKind, event.QName(), offset, event.RegisteredAt().String(), r)) 204 panic(r) 205 } 206 }() 207 if f.filter("QName") { 208 data["QName"] = event.QName().String() 209 } 210 if f.filter("ArgumentObject") { 211 data["ArgumentObject"] = coreutils.ObjectToMap(event.ArgumentObject(), appDef) 212 } 213 if f.filter("CUDs") { 214 data["CUDs"] = coreutils.CUDsToMap(event, appDef) 215 } 216 if f.filter("RegisteredAt") { 217 data["RegisteredAt"] = event.RegisteredAt() 218 } 219 if f.filter("Synced") { 220 data["Synced"] = event.Synced() 221 } 222 if f.filter("DeviceID") { 223 data["DeviceID"] = event.DeviceID() 224 } 225 if f.filter("SyncedAt") { 226 data["SyncedAt"] = event.SyncedAt() 227 } 228 if f.filter("Error") { 229 if event.Error() != nil { 230 errorData := make(map[string]interface{}) 231 errorData["ErrStr"] = event.Error().ErrStr() 232 errorData["QNameFromParams"] = event.Error().QNameFromParams().String() 233 errorData["ValidEvent"] = event.Error().ValidEvent() 234 errorData["OriginalEventBytes"] = event.Error().OriginalEventBytes() 235 data["Error"] = errorData 236 } 237 } 238 }