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  }