2024-01-24 02:28:32 +08:00
|
|
|
package common
|
|
|
|
|
|
|
|
import (
|
2024-06-25 21:27:44 +08:00
|
|
|
"encoding/json"
|
2024-01-24 02:28:32 +08:00
|
|
|
"fmt"
|
2024-06-25 21:27:44 +08:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"hash/crc32"
|
2024-01-24 02:28:32 +08:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
func GetStreamCursorPropsFromBrowserMessage(browserMessage BrowserSubscribeMessage) (string, string, interface{}) {
|
2024-01-24 02:28:32 +08:00
|
|
|
streamCursorField := ""
|
|
|
|
streamCursorVariableName := ""
|
|
|
|
var streamCursorInitialValue interface{}
|
|
|
|
|
|
|
|
cursorInitialValueRePattern := regexp.MustCompile(`cursor:\s*\{\s*initial_value\s*:\s*\{\s*([^:]+):\s*([^}]+)\s*}\s*}`)
|
2024-06-25 21:27:44 +08:00
|
|
|
matches := cursorInitialValueRePattern.FindStringSubmatch(browserMessage.Payload.Query)
|
2024-01-24 02:28:32 +08:00
|
|
|
if matches != nil {
|
|
|
|
streamCursorField = matches[1]
|
|
|
|
if strings.HasPrefix(matches[2], "$") {
|
|
|
|
streamCursorVariableName, _ = strings.CutPrefix(matches[2], "$")
|
2024-06-25 21:27:44 +08:00
|
|
|
if targetVariableValue, okTargetVariableValue := browserMessage.Payload.Variables[streamCursorVariableName]; okTargetVariableValue {
|
|
|
|
streamCursorInitialValue = targetVariableValue
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
streamCursorInitialValue = matches[2]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return streamCursorField, streamCursorVariableName, streamCursorInitialValue
|
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
func GetLastStreamCursorValueFromReceivedMessage(message []byte, streamCursorField string) interface{} {
|
|
|
|
dataChecksum := crc32.ChecksumIEEE(message)
|
|
|
|
GlobalCacheLocks.Lock(dataChecksum)
|
|
|
|
|
|
|
|
if streamCursorValueCache, streamCursorValueCacheExists := GetStreamCursorValueCache(dataChecksum); streamCursorValueCacheExists {
|
|
|
|
//Unlock immediately once the cache was already created by other routine
|
|
|
|
GlobalCacheLocks.Unlock(dataChecksum)
|
|
|
|
return streamCursorValueCache
|
|
|
|
} else {
|
|
|
|
//It will create the cache and then Unlock (others will wait to benefit from this cache)
|
|
|
|
defer GlobalCacheLocks.Unlock(dataChecksum)
|
|
|
|
}
|
|
|
|
|
2024-01-24 02:28:32 +08:00
|
|
|
var lastStreamCursorValue interface{}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
var messageAsMap map[string]interface{}
|
|
|
|
err := json.Unmarshal(message, &messageAsMap)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to unmarshal message: %v", err)
|
|
|
|
//return
|
|
|
|
}
|
|
|
|
|
2024-01-24 02:28:32 +08:00
|
|
|
if payload, okPayload := messageAsMap["payload"].(map[string]interface{}); okPayload {
|
|
|
|
if data, okData := payload["data"].(map[string]interface{}); okData {
|
|
|
|
//Data will have only one prop, `range` because its name is unknown
|
|
|
|
for _, dataItem := range data {
|
|
|
|
currentDataProp, okCurrentDataProp := dataItem.([]interface{})
|
|
|
|
if okCurrentDataProp && len(currentDataProp) > 0 {
|
|
|
|
// Get the last item directly (once it will contain the last cursor value)
|
|
|
|
lastItemOfMessage := currentDataProp[len(currentDataProp)-1]
|
|
|
|
if lastItemOfMessageAsMap, currDataOk := lastItemOfMessage.(map[string]interface{}); currDataOk {
|
|
|
|
if lastItemValue, okLastItemValue := lastItemOfMessageAsMap[streamCursorField]; okLastItemValue {
|
|
|
|
lastStreamCursorValue = lastItemValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
StoreStreamCursorValueCache(dataChecksum, lastStreamCursorValue)
|
2024-01-24 02:28:32 +08:00
|
|
|
return lastStreamCursorValue
|
|
|
|
}
|
|
|
|
|
|
|
|
func PatchQueryIncludingCursorField(originalQuery string, cursorField string) string {
|
|
|
|
if cursorField == "" {
|
|
|
|
return originalQuery
|
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
lastIndexOfTypename := LastButOneIndex(originalQuery, "}")
|
|
|
|
if lastIndexOfTypename == -1 {
|
2024-01-24 02:28:32 +08:00
|
|
|
return originalQuery
|
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
// It will include the cursorField at the end of the list of fields
|
2024-01-24 02:28:32 +08:00
|
|
|
// It's not a problem if the field be duplicated in the list, Hasura just ignore the second occurrence
|
2024-06-25 21:27:44 +08:00
|
|
|
return originalQuery[:lastIndexOfTypename] + " " + cursorField + "\n " + originalQuery[lastIndexOfTypename:]
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
func PatchQuerySettingLastCursorValue(subscription GraphQlSubscription) []byte {
|
|
|
|
var browserMessage BrowserSubscribeMessage
|
|
|
|
err := json.Unmarshal(subscription.Message, &browserMessage)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to unmarshal message: %v", err)
|
|
|
|
return subscription.Message
|
|
|
|
}
|
2024-01-24 02:28:32 +08:00
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
if subscription.StreamCursorVariableName != "" {
|
|
|
|
/**** This stream has its cursor value set through variables ****/
|
|
|
|
if browserMessage.Payload.Variables[subscription.StreamCursorVariableName] == subscription.StreamCursorCurrValue {
|
|
|
|
return subscription.Message
|
|
|
|
}
|
|
|
|
browserMessage.Payload.Variables[subscription.StreamCursorVariableName] = subscription.StreamCursorCurrValue
|
|
|
|
} else {
|
|
|
|
/**** This stream has its cursor value set through inline value (not variables) ****/
|
|
|
|
cursorInitialValueRePattern := regexp.MustCompile(`cursor:\s*\{\s*initial_value\s*:\s*\{\s*([^:]+:\s*[^}]+)\s*}\s*}`)
|
|
|
|
newValue := ""
|
|
|
|
|
|
|
|
replaceInitialValueFunc := func(match string) string {
|
|
|
|
switch v := subscription.StreamCursorCurrValue.(type) {
|
|
|
|
case string:
|
|
|
|
newValue = v
|
|
|
|
|
|
|
|
//Append quotes if it is missing, it will be necessary when appending to the query
|
|
|
|
if !strings.HasPrefix(v, "\"") {
|
|
|
|
newValue = "\"" + newValue
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
2024-06-25 21:27:44 +08:00
|
|
|
if !strings.HasSuffix(v, "\"") {
|
|
|
|
newValue = newValue + "\""
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
2024-06-25 21:27:44 +08:00
|
|
|
case int:
|
|
|
|
newValue = strconv.Itoa(v)
|
|
|
|
case float32:
|
|
|
|
myFloat64 := float64(v)
|
|
|
|
newValue = strconv.FormatFloat(myFloat64, 'f', -1, 32)
|
|
|
|
case float64:
|
|
|
|
newValue = strconv.FormatFloat(v, 'f', -1, 64)
|
|
|
|
default:
|
|
|
|
newValue = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if newValue != "" {
|
|
|
|
replacement := subscription.StreamCursorField + ": " + newValue
|
|
|
|
return fmt.Sprintf("cursor: {initial_value: {%s}}", replacement)
|
|
|
|
} else {
|
|
|
|
return match
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
|
|
|
}
|
2024-06-25 21:27:44 +08:00
|
|
|
|
|
|
|
newQuery := cursorInitialValueRePattern.ReplaceAllStringFunc(browserMessage.Payload.Query, replaceInitialValueFunc)
|
|
|
|
if browserMessage.Payload.Query == newQuery {
|
|
|
|
return subscription.Message
|
|
|
|
}
|
|
|
|
|
|
|
|
browserMessage.Payload.Query = newQuery
|
|
|
|
}
|
|
|
|
|
|
|
|
newMessageJson, _ := json.Marshal(browserMessage)
|
|
|
|
|
|
|
|
return newMessageJson
|
|
|
|
}
|
|
|
|
|
|
|
|
func LastButOneIndex(s, substr string) int {
|
|
|
|
last := strings.LastIndex(s, substr)
|
|
|
|
if last == -1 {
|
|
|
|
return -1
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
|
|
|
|
2024-06-25 21:27:44 +08:00
|
|
|
return strings.LastIndex(s[:last], substr)
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|