Compare commits

..

13 Commits

Author SHA1 Message Date
Pijus Kamandulis dca5851795 Update dependencies 2026-08-05 20:24:14 +03:00
Pijus Kamandulis a3eec6aa24 Fix failing test 2026-08-05 20:18:30 +03:00
Pijus Kamandulis 92d956e8fa Fix ExcludedKeywords parsing 2026-08-05 20:06:19 +03:00
Pijus Kamandulis c78842726b Support single-quoted string literals in query parsing (#18)
* Support single-quoted string literals in query parsing

Add single-quoted string literal support to the NoSQL query parser,
matching Cosmos DB behavior where apostrophes are escaped by doubling
them (''). Includes parser unit tests and an API integration test.

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Use backslash escape for apostrophes in single-quoted strings

Cosmos DB escapes apostrophes with \' rather than SQL-style doubling.
Also fix EscapeCharacter to return proper values for \', \", and \\
which were previously producing empty substitutions.

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-12 14:02:23 +03:00
Pijus Kamandulis 90de21c7b0 1.26.x (#17)
* Upgrade to go 1.26.3

* Update dependencies

* Update actions
2026-06-11 23:27:46 +03:00
Pijus Kamandulis 7c9c8ec9dc Update packages 2026-06-11 22:58:05 +03:00
Pijus Kamandulis be761badae Initial RNTBD server implementation 2026-06-11 22:43:05 +03:00
Pijus Kamandulis 36fd7f48cc Add document ETag optimistic concurrency (#16)
* Add ETag optimistic concurrency for document replace

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Expose precondition error code header

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Stop Badger GC before closing datastore

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-05 23:26:25 +03:00
Pijus Kamandulis 05e8cd2842 Implement REGEXMATCH function (#15)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-05-30 21:31:45 +03:00
Pijus Kamandulis c3726a6633 Fix ARRAY_CONTAINS panic when optional partial-match argument is omitted (#14)
* Fix ARRAY_CONTAINS panic when partial match arg is omitted

The NoSQL parser always emits a third (nil) argument for the optional
partial-match flag of ARRAY_CONTAINS. The executor checked only
len(arguments) > 2 before type-asserting arguments[2] to
parsers.SelectItem, which panicked on the nil value whenever the query
omitted the partial-match argument (e.g. ARRAY_CONTAINS(c.arr, 2)).

Guard the type assertion with a nil check and add an API test covering
ARRAY_CONTAINS with and without the optional partial-match argument.

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Remove comments from ARRAY_CONTAINS API test

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-05-30 17:43:05 +03:00
Pijus Kamandulis d76cc88175 Fix 'NOT (bool)' statements 2026-04-04 15:04:42 +03:00
Pijus Kamandulis 3daba9d0eb Update dependencies 2026-01-29 21:47:35 +02:00
Pijus Kamandulis d3d238fa98 Implement continuation tokens 2026-01-29 21:45:46 +02:00
38 changed files with 4347 additions and 1803 deletions
@@ -12,13 +12,13 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v6
- name: Cross-Compile with xgo - name: Cross-Compile with xgo
uses: crazy-max/ghaction-xgo@acf46aa99b919eb9ef6bba89dfd13bafa680667f # v3.2.0 uses: crazy-max/ghaction-xgo@de82f877ff4552f03b66c146f608233849e9c3dc # v4.0.0
with: with:
xgo_version: latest xgo_version: latest
go_version: 1.25.1 go_version: 1.26.3
dest: dist dest: dist
pkg: sharedlibrary pkg: sharedlibrary
prefix: cosmium prefix: cosmium
@@ -28,7 +28,7 @@ jobs:
buildvcs: true buildvcs: true
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: shared-libraries name: shared-libraries
path: dist/* path: dist/*
@@ -48,10 +48,10 @@ jobs:
lib_ext: arm64.dylib lib_ext: arm64.dylib
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: Download shared libraries - name: Download shared libraries
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: shared-libraries name: shared-libraries
path: libs path: libs
+5 -5
View File
@@ -3,7 +3,7 @@ name: goreleaser
on: on:
push: push:
tags: tags:
- '*' - "*"
permissions: permissions:
contents: write contents: write
@@ -14,20 +14,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.25.1 go-version: 1.26.3
- name: Cross-Compile with xgo - name: Cross-Compile with xgo
uses: crazy-max/ghaction-xgo@acf46aa99b919eb9ef6bba89dfd13bafa680667f # v3.2.0 uses: crazy-max/ghaction-xgo@de82f877ff4552f03b66c146f608233849e9c3dc # v4.0.0
with: with:
xgo_version: latest xgo_version: latest
go_version: 1.25.1 go_version: 1.26.3
dest: sharedlibrary_dist dest: sharedlibrary_dist
pkg: sharedlibrary pkg: sharedlibrary
prefix: cosmium prefix: cosmium
+1 -1
View File
@@ -9,7 +9,7 @@ SERVER_LOCATION=./cmd/server
SHARED_LIB_LOCATION=./sharedlibrary SHARED_LIB_LOCATION=./sharedlibrary
SHARED_LIB_OPT=-buildmode=c-shared SHARED_LIB_OPT=-buildmode=c-shared
XGO_TARGETS=linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64 XGO_TARGETS=linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64
GOVERSION=1.25.1 GOVERSION=1.26.3
DIST_DIR=dist DIST_DIR=dist
+5
View File
@@ -23,6 +23,7 @@ const (
func ParseFlags() ServerConfig { func ParseFlags() ServerConfig {
host := flag.String("Host", "localhost", "Hostname") host := flag.String("Host", "localhost", "Hostname")
port := flag.Int("Port", 8081, "Listen port") port := flag.Int("Port", 8081, "Listen port")
rntbdPort := flag.Int("RntbdPort", 10000, "RNTBD listen port")
explorerPath := flag.String("ExplorerDir", "", "Path to cosmos-explorer files") explorerPath := flag.String("ExplorerDir", "", "Path to cosmos-explorer files")
tlsCertificatePath := flag.String("Cert", "", "Hostname") tlsCertificatePath := flag.String("Cert", "", "Hostname")
tlsCertificateKey := flag.String("CertKey", "", "Hostname") tlsCertificateKey := flag.String("CertKey", "", "Hostname")
@@ -35,6 +36,7 @@ func ParseFlags() ServerConfig {
flag.Var(logLevel, "LogLevel", fmt.Sprintf("Sets the logging level %s", logLevel.AllowedValuesList())) flag.Var(logLevel, "LogLevel", fmt.Sprintf("Sets the logging level %s", logLevel.AllowedValuesList()))
dataStore := NewEnumValue("json", []string{DataStoreJson, DataStoreBadger}) dataStore := NewEnumValue("json", []string{DataStoreJson, DataStoreBadger})
flag.Var(dataStore, "DataStore", fmt.Sprintf("Sets the data store %s", dataStore.AllowedValuesList())) flag.Var(dataStore, "DataStore", fmt.Sprintf("Sets the data store %s", dataStore.AllowedValuesList()))
enableRntbd := flag.Bool("ExperimentalEnableRntbd", false, "EXPERIMENTAL: Enable RNTBD (CosmosDB Direct Connection Mode)")
flag.Parse() flag.Parse()
setFlagsFromEnvironment() setFlagsFromEnvironment()
@@ -42,6 +44,7 @@ func ParseFlags() ServerConfig {
config := ServerConfig{} config := ServerConfig{}
config.Host = *host config.Host = *host
config.Port = *port config.Port = *port
config.RntbdPort = *rntbdPort
config.ExplorerPath = *explorerPath config.ExplorerPath = *explorerPath
config.TLS_CertificatePath = *tlsCertificatePath config.TLS_CertificatePath = *tlsCertificatePath
config.TLS_CertificateKey = *tlsCertificateKey config.TLS_CertificateKey = *tlsCertificateKey
@@ -52,6 +55,7 @@ func ParseFlags() ServerConfig {
config.AccountKey = *accountKey config.AccountKey = *accountKey
config.LogLevel = logLevel.value config.LogLevel = logLevel.value
config.DataStore = dataStore.value config.DataStore = dataStore.value
config.EnableRntbd = *enableRntbd
config.PopulateCalculatedFields() config.PopulateCalculatedFields()
@@ -62,6 +66,7 @@ func (c *ServerConfig) PopulateCalculatedFields() {
c.DatabaseAccount = c.Host c.DatabaseAccount = c.Host
c.DatabaseDomain = c.Host c.DatabaseDomain = c.Host
c.DatabaseEndpoint = fmt.Sprintf("https://%s:%d/", c.Host, c.Port) c.DatabaseEndpoint = fmt.Sprintf("https://%s:%d/", c.Host, c.Port)
c.RntbdEndpoint = fmt.Sprintf("rntbd://%s:%d/", c.Host, c.RntbdPort)
c.ExplorerBaseUrlLocation = ExplorerBaseUrlLocation c.ExplorerBaseUrlLocation = ExplorerBaseUrlLocation
switch c.LogLevel { switch c.LogLevel {
+3
View File
@@ -4,10 +4,12 @@ type ServerConfig struct {
DatabaseAccount string `json:"databaseAccount"` DatabaseAccount string `json:"databaseAccount"`
DatabaseDomain string `json:"databaseDomain"` DatabaseDomain string `json:"databaseDomain"`
DatabaseEndpoint string `json:"databaseEndpoint"` DatabaseEndpoint string `json:"databaseEndpoint"`
RntbdEndpoint string `json:"rntbdEndpoint"`
AccountKey string `json:"accountKey"` AccountKey string `json:"accountKey"`
ExplorerPath string `json:"explorerPath"` ExplorerPath string `json:"explorerPath"`
Port int `json:"port"` Port int `json:"port"`
RntbdPort int `json:"rntbdPort"`
Host string `json:"host"` Host string `json:"host"`
TLS_CertificatePath string `json:"tlsCertificatePath"` TLS_CertificatePath string `json:"tlsCertificatePath"`
TLS_CertificateKey string `json:"tlsCertificateKey"` TLS_CertificateKey string `json:"tlsCertificateKey"`
@@ -17,6 +19,7 @@ type ServerConfig struct {
DisableTls bool `json:"disableTls"` DisableTls bool `json:"disableTls"`
LogLevel string `json:"logLevel"` LogLevel string `json:"logLevel"`
ExplorerBaseUrlLocation string `json:"explorerBaseUrlLocation"` ExplorerBaseUrlLocation string `json:"explorerBaseUrlLocation"`
EnableRntbd bool `json:"enableRntbd"`
DataStore string `json:"dataStore"` DataStore string `json:"dataStore"`
} }
+70 -11
View File
@@ -11,6 +11,7 @@ import (
apimodels "github.com/pikami/cosmium/api/api_models" apimodels "github.com/pikami/cosmium/api/api_models"
"github.com/pikami/cosmium/api/headers" "github.com/pikami/cosmium/api/headers"
"github.com/pikami/cosmium/internal/constants" "github.com/pikami/cosmium/internal/constants"
continuationtoken "github.com/pikami/cosmium/internal/continuation_token"
"github.com/pikami/cosmium/internal/converters" "github.com/pikami/cosmium/internal/converters"
"github.com/pikami/cosmium/internal/datastore" "github.com/pikami/cosmium/internal/datastore"
"github.com/pikami/cosmium/internal/logger" "github.com/pikami/cosmium/internal/logger"
@@ -46,6 +47,9 @@ func (h *Handlers) GetDocument(c *gin.Context) {
document, status := h.dataStore.GetDocument(databaseId, collectionId, documentId) document, status := h.dataStore.GetDocument(databaseId, collectionId, documentId)
if status == datastore.StatusOk { if status == datastore.StatusOk {
if etag, ok := document["_etag"].(string); ok {
c.Header(headers.ETag, etag)
}
c.IndentedJSON(http.StatusOK, document) c.IndentedJSON(http.StatusOK, document)
return return
} }
@@ -89,7 +93,25 @@ func (h *Handlers) ReplaceDocument(c *gin.Context) {
return return
} }
status := h.dataStore.DeleteDocument(databaseId, collectionId, documentId) existingDocument, status := h.dataStore.GetDocument(databaseId, collectionId, documentId)
if status == datastore.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
return
}
if status != datastore.StatusOk {
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
return
}
if ifMatch := c.GetHeader(headers.IfMatch); ifMatch != "" {
if existingDocument["_etag"] != ifMatch {
c.Header(headers.ErrorCode, "PreconditionFailed")
c.JSON(http.StatusPreconditionFailed, constants.PreconditionFailedResponse)
return
}
}
status = h.dataStore.DeleteDocument(databaseId, collectionId, documentId)
if status == datastore.StatusNotFound { if status == datastore.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse) c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
return return
@@ -262,20 +284,50 @@ func (h *Handlers) handleDocumentQuery(c *gin.Context, requestBody map[string]in
queryParameters = parametersToMap(paramsArray) queryParameters = parametersToMap(paramsArray)
} }
collection, collectionStatus := h.dataStore.GetCollection(databaseId, collectionId)
if collectionStatus == datastore.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
return
}
if collectionStatus != datastore.StatusOk {
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
return
}
continuationToken := continuationtoken.GenerateDefault(collection.ResourceID)
continuationTokenHeader := c.GetHeader(headers.ContinuationToken)
if continuationTokenHeader != "" {
continuationToken = continuationtoken.FromString(continuationTokenHeader)
}
pageMaxItemCount, maxItemCountError := strconv.Atoi(c.GetHeader(headers.MaxItemCount))
if maxItemCountError != nil {
pageMaxItemCount = 1000
}
queryText := requestBody["query"].(string) queryText := requestBody["query"].(string)
docs, status := h.executeQueryDocuments(databaseId, collectionId, queryText, queryParameters) executeQueryResult, status := h.executeQueryDocuments(
databaseId, collectionId, queryText, queryParameters, pageMaxItemCount, continuationToken.Token.TotalResults)
if status != datastore.StatusOk { if status != datastore.StatusOk {
// TODO: Currently we return everything if the query fails // TODO: Currently we return everything if the query fails
logger.Infof("Query failed: %s", queryText)
h.GetAllDocuments(c) h.GetAllDocuments(c)
return return
} }
collection, _ := h.dataStore.GetCollection(databaseId, collectionId) resultCount := len(executeQueryResult.Rows)
c.Header(headers.ItemCount, fmt.Sprintf("%d", len(docs))) if executeQueryResult.HasMorePages {
nextContinuationToken := continuationtoken.Generate(
collection.ResourceID, continuationToken.Token.PageIndex+1, continuationToken.Token.TotalResults+resultCount)
c.Header(headers.ContinuationToken, nextContinuationToken.ToString())
}
c.Header(headers.ItemCount, fmt.Sprintf("%d", resultCount))
c.IndentedJSON(http.StatusOK, gin.H{ c.IndentedJSON(http.StatusOK, gin.H{
"_rid": collection.ResourceID, "_rid": collection.ResourceID,
"Documents": docs, "Documents": executeQueryResult.Rows,
"_count": len(docs), "_count": resultCount,
}) })
} }
@@ -377,16 +429,23 @@ func dataStoreStatusToResponseCode(status datastore.DataStoreStatus) int {
} }
} }
func (h *Handlers) executeQueryDocuments(databaseId string, collectionId string, query string, queryParameters map[string]interface{}) ([]memoryexecutor.RowType, datastore.DataStoreStatus) { func (h *Handlers) executeQueryDocuments(
databaseId string,
collectionId string,
query string,
queryParameters map[string]interface{},
pageMaxItemCount int,
pageCursor int,
) (memoryexecutor.ExecuteQueryResult, datastore.DataStoreStatus) {
parsedQuery, err := nosql.Parse("", []byte(query)) parsedQuery, err := nosql.Parse("", []byte(query))
if err != nil { if err != nil {
logger.Errorf("Failed to parse query: %s\nerr: %v", query, err) logger.Errorf("Failed to parse query: %s\nerr: %v", query, err)
return nil, datastore.BadRequest return memoryexecutor.ExecuteQueryResult{}, datastore.BadRequest
} }
allDocumentsIterator, status := h.dataStore.GetDocumentIterator(databaseId, collectionId) allDocumentsIterator, status := h.dataStore.GetDocumentIterator(databaseId, collectionId)
if status != datastore.StatusOk { if status != datastore.StatusOk {
return nil, status return memoryexecutor.ExecuteQueryResult{}, status
} }
defer allDocumentsIterator.Close() defer allDocumentsIterator.Close()
@@ -394,8 +453,8 @@ func (h *Handlers) executeQueryDocuments(databaseId string, collectionId string,
if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok { if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok {
typedQuery.Parameters = queryParameters typedQuery.Parameters = queryParameters
return memoryexecutor.ExecuteQuery(typedQuery, rowsIterator), datastore.StatusOk return memoryexecutor.ExecuteQuery(typedQuery, rowsIterator, pageCursor, pageMaxItemCount), datastore.StatusOk
} }
return nil, datastore.BadRequest return memoryexecutor.ExecuteQueryResult{}, datastore.BadRequest
} }
+44
View File
@@ -3,6 +3,7 @@ package handlers
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -41,3 +42,46 @@ func (h *Handlers) GetServerInfo(c *gin.Context) {
"queryEngineConfiguration": "{\"allowNewKeywords\":true,\"maxJoinsPerSqlQuery\":10,\"maxQueryRequestTimeoutFraction\":0.9,\"maxSqlQueryInputLength\":524288,\"maxUdfRefPerSqlQuery\":10,\"queryMaxInMemorySortDocumentCount\":-1000,\"spatialMaxGeometryPointCount\":256,\"sqlAllowNonFiniteNumbers\":false,\"sqlDisableOptimizationFlags\":0,\"enableSpatialIndexing\":true,\"maxInExpressionItemsCount\":2147483647,\"maxLogicalAndPerSqlQuery\":2147483647,\"maxLogicalOrPerSqlQuery\":2147483647,\"maxSpatialQueryCells\":2147483647,\"sqlAllowAggregateFunctions\":true,\"sqlAllowGroupByClause\":true,\"sqlAllowLike\":true,\"sqlAllowSubQuery\":true,\"sqlAllowScalarSubQuery\":true,\"sqlAllowTop\":true}", "queryEngineConfiguration": "{\"allowNewKeywords\":true,\"maxJoinsPerSqlQuery\":10,\"maxQueryRequestTimeoutFraction\":0.9,\"maxSqlQueryInputLength\":524288,\"maxUdfRefPerSqlQuery\":10,\"queryMaxInMemorySortDocumentCount\":-1000,\"spatialMaxGeometryPointCount\":256,\"sqlAllowNonFiniteNumbers\":false,\"sqlDisableOptimizationFlags\":0,\"enableSpatialIndexing\":true,\"maxInExpressionItemsCount\":2147483647,\"maxLogicalAndPerSqlQuery\":2147483647,\"maxLogicalOrPerSqlQuery\":2147483647,\"maxSpatialQueryCells\":2147483647,\"sqlAllowAggregateFunctions\":true,\"sqlAllowGroupByClause\":true,\"sqlAllowLike\":true,\"sqlAllowSubQuery\":true,\"sqlAllowScalarSubQuery\":true,\"sqlAllowTop\":true}",
}) })
} }
type Address struct {
IsPrimary bool `json:"isPrimary"`
PhyscialUri string `json:"physcialUri"`
IsAuxiliary bool `json:"isAuxiliary"`
PartitionTargetReplicaSetSize int `json:"partitionTargetReplicaSetSize"`
Protocol string `json:"protocol"`
PartitionKeyRangeId string `json:"partitionKeyRangeId"`
PartitionIndex string `json:"partitionIndex"`
}
func (h *Handlers) GetAddresses(c *gin.Context) {
addresses := []Address{}
if h.config.EnableRntbd {
addresses = append(addresses, Address{
IsPrimary: true,
PhyscialUri: h.config.RntbdEndpoint,
IsAuxiliary: false,
PartitionTargetReplicaSetSize: 1,
Protocol: "rntbd",
PartitionKeyRangeId: "0",
PartitionIndex: "0@0",
})
}
if !strings.Contains(c.Request.RequestURI, "protocol%20eq%20rntbd") {
addresses = append(addresses, Address{
IsPrimary: true,
PhyscialUri: h.config.DatabaseEndpoint,
IsAuxiliary: false,
PartitionTargetReplicaSetSize: 1,
Protocol: "https",
PartitionKeyRangeId: "0",
PartitionIndex: "0@0",
})
}
c.IndentedJSON(http.StatusOK, gin.H{
"Addresss": addresses,
"_count": len(addresses),
})
}
+10
View File
@@ -4,8 +4,10 @@ const (
AIM = "A-Im" AIM = "A-Im"
Authorization = "authorization" Authorization = "authorization"
CosmosLsn = "x-ms-cosmos-llsn" CosmosLsn = "x-ms-cosmos-llsn"
ErrorCode = "x-ms-error-code"
ETag = "etag" ETag = "etag"
GlobalCommittedLsn = "x-ms-global-committed-lsn" GlobalCommittedLsn = "x-ms-global-committed-lsn"
IfMatch = "if-match"
IfNoneMatch = "if-none-match" IfNoneMatch = "if-none-match"
IsBatchRequest = "x-ms-cosmos-is-batch-request" IsBatchRequest = "x-ms-cosmos-is-batch-request"
IsQueryPlanRequest = "x-ms-cosmos-is-query-plan-request" IsQueryPlanRequest = "x-ms-cosmos-is-query-plan-request"
@@ -13,8 +15,16 @@ const (
ItemCount = "x-ms-item-count" ItemCount = "x-ms-item-count"
LSN = "lsn" LSN = "lsn"
XDate = "x-ms-date" XDate = "x-ms-date"
MaxItemCount = "x-ms-max-item-count"
ContinuationToken = "x-ms-continuation"
// Kinda retarded, but what can I do ¯\_(ツ)_/¯ // Kinda retarded, but what can I do ¯\_(ツ)_/¯
IsQuery = "x-ms-documentdb-isquery" // Sent from python sdk and web explorer IsQuery = "x-ms-documentdb-isquery" // Sent from python sdk and web explorer
Query = "x-ms-documentdb-query" // Sent from Go sdk Query = "x-ms-documentdb-query" // Sent from Go sdk
// I kinda don't use these, but I've seen them in the wild xd
SupportedCapabilities = "x-ms-cosmos-sdk-supportedcapabilities"
ClientRetryAttemptCount = "x-ms-client-retry-attempt-count"
RemainingTimeInMsOnClient = "x-ms-remaining-time-in-ms-on-client"
ConsistencyLevel = "x-ms-consistency-level"
) )
+2
View File
@@ -31,6 +31,7 @@ func (s *ApiServer) CreateRouter(dataStore datastore.DataStore) {
router := gin.Default(func(e *gin.Engine) { router := gin.Default(func(e *gin.Engine) {
e.RedirectTrailingSlash = false e.RedirectTrailingSlash = false
e.RemoveExtraSlash = true
}) })
if s.config.LogLevel == "debug" { if s.config.LogLevel == "debug" {
@@ -79,6 +80,7 @@ func (s *ApiServer) CreateRouter(dataStore datastore.DataStore) {
router.GET("/offers", handlers.GetOffers) router.GET("/offers", handlers.GetOffers)
router.GET("/", routeHandlers.GetServerInfo) router.GET("/", routeHandlers.GetServerInfo)
router.GET("//addresses", routeHandlers.GetAddresses)
router.GET("/cosmium/export", routeHandlers.CosmiumExport) router.GET("/cosmium/export", routeHandlers.CosmiumExport)
@@ -0,0 +1,47 @@
package tests_test
import (
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos"
)
func Test_Documents_ArrayContains(t *testing.T) {
presets := []testPreset{PresetJsonStore, PresetBadgerStore}
runTestsWithPresets(t, "Test_Documents_ArrayContains", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
collectionClient := documents_InitializeDb(t, ts)
t.Run("Should execute ARRAY_CONTAINS() without partial match argument", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT VALUE ARRAY_CONTAINS(["apple", "banana", "cherry"], "banana") FROM c ORDER BY c.id`,
nil,
[]interface{}{true, true},
)
})
t.Run("Should execute ARRAY_CONTAINS() returning false for missing item", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT VALUE ARRAY_CONTAINS(["apple", "banana", "cherry"], "grape") FROM c ORDER BY c.id`,
nil,
[]interface{}{false, false},
)
})
t.Run("Should execute ARRAY_CONTAINS() with object full match", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT VALUE ARRAY_CONTAINS([{"name": "apple", "color": "red"}], {"name": "apple"}) FROM c ORDER BY c.id`,
nil,
[]interface{}{false, false},
)
})
t.Run("Should execute ARRAY_CONTAINS() with object partial match", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT VALUE ARRAY_CONTAINS([{"name": "apple", "color": "red"}], {"name": "apple"}, true) FROM c ORDER BY c.id`,
nil,
[]interface{}{true, true},
)
})
})
}
@@ -0,0 +1,73 @@
package tests_test
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos"
"github.com/pikami/cosmium/api/config"
"github.com/pikami/cosmium/internal/datastore"
"github.com/stretchr/testify/assert"
)
func documents_InitializeSingleDocumentDb(t *testing.T, ts *TestServer) *azcosmos.ContainerClient {
ts.DataStore.CreateDatabase(datastore.Database{ID: testDatabaseName})
ts.DataStore.CreateCollection(testDatabaseName, datastore.Collection{
ID: testCollectionName,
PartitionKey: struct {
Paths []string "json:\"paths\""
Kind string "json:\"kind\""
Version int "json:\"Version\""
}{
Paths: []string{"/pk"},
},
})
ts.DataStore.CreateDocument(testDatabaseName, testCollectionName, map[string]interface{}{"id": "regexmatch-test", "pk": "regexmatch-test"})
client, err := azcosmos.NewClientFromConnectionString(
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, config.DefaultAccountKey),
&azcosmos.ClientOptions{},
)
assert.Nil(t, err)
collectionClient, err := client.NewContainer(testDatabaseName, testCollectionName)
assert.Nil(t, err)
return collectionClient
}
func Test_Documents_RegexMatch(t *testing.T) {
presets := []testPreset{PresetJsonStore, PresetBadgerStore}
runTestsWithPresets(t, "Test_Documents_RegexMatch", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
collectionClient := documents_InitializeSingleDocumentDb(t, ts)
t.Run("Should execute REGEXMATCH()", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT VALUE {
noModifiers: REGEXMATCH("abcd", "ABC"),
caseInsensitive: REGEXMATCH("abcd", "ABC", "i"),
wildcardCharacter: REGEXMATCH("abcd", "ab.", ""),
ignoreWhiteSpace: REGEXMATCH("abcd", "ab c", "x"),
caseInsensitiveAndIgnoreWhiteSpace: REGEXMATCH("abcd", "aB c", "ix"),
containNumberBetweenZeroAndNine: REGEXMATCH("03a", "[0-9]"),
containPrefix: REGEXMATCH("salt3824908", "salt{1}"),
containsFiveLetterWordStartingWithS: REGEXMATCH("shame", "s....", "i")
}`,
nil,
[]interface{}{
map[string]interface{}{
"noModifiers": false,
"caseInsensitive": true,
"wildcardCharacter": true,
"ignoreWhiteSpace": true,
"caseInsensitiveAndIgnoreWhiteSpace": true,
"containNumberBetweenZeroAndNine": true,
"containPrefix": true,
"containsFiveLetterWordStartingWithS": true,
},
},
)
})
})
}
+157
View File
@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"reflect" "reflect"
"sync" "sync"
@@ -14,6 +15,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos"
"github.com/pikami/cosmium/api/config" "github.com/pikami/cosmium/api/config"
continuationtoken "github.com/pikami/cosmium/internal/continuation_token"
"github.com/pikami/cosmium/internal/datastore" "github.com/pikami/cosmium/internal/datastore"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -97,6 +99,17 @@ func Test_Documents(t *testing.T) {
) )
}) })
t.Run("Should query document with 'orderByItems' alias", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT c.id, c["pk"] AS orderByItems FROM c ORDER BY c.id`,
nil,
[]interface{}{
map[string]interface{}{"id": "12345", "orderByItems": "123"},
map[string]interface{}{"id": "67890", "orderByItems": "456"},
},
)
})
t.Run("Should query VALUE array", func(t *testing.T) { t.Run("Should query VALUE array", func(t *testing.T) {
testCosmosQuery(t, collectionClient, testCosmosQuery(t, collectionClient,
"SELECT VALUE [c.id, c[\"pk\"]] FROM c ORDER BY c.id", "SELECT VALUE [c.id, c[\"pk\"]] FROM c ORDER BY c.id",
@@ -132,6 +145,19 @@ func Test_Documents(t *testing.T) {
) )
}) })
t.Run("Should query document with single-quoted string literal", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`select c.id
FROM c
WHERE c.id='67890'
ORDER BY c.id`,
nil,
[]interface{}{
map[string]interface{}{"id": "67890"},
},
)
})
t.Run("Should query document with query parameters", func(t *testing.T) { t.Run("Should query document with query parameters", func(t *testing.T) {
testCosmosQuery(t, collectionClient, testCosmosQuery(t, collectionClient,
`select c.id `select c.id
@@ -227,6 +253,21 @@ func Test_Documents(t *testing.T) {
} }
} }
}) })
t.Run("Should query document with escaped single-quoted string literal", func(t *testing.T) {
ts.DataStore.CreateDocument(testDatabaseName, testCollectionName, map[string]interface{}{"id": "apostrophe-doc", "pk": "ap", "name": "it's"})
testCosmosQuery(t, collectionClient,
`select c.id
FROM c
WHERE c.name='it\'s'
ORDER BY c.id`,
nil,
[]interface{}{
map[string]interface{}{"id": "apostrophe-doc"},
},
)
})
}) })
runTestsWithPresets(t, "Test_Documents_Patch", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) { runTestsWithPresets(t, "Test_Documents_Patch", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
@@ -378,6 +419,80 @@ func Test_Documents(t *testing.T) {
}) })
}) })
runTestsWithPresets(t, "Test_Documents_ETag_OptimisticConcurrency", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
collectionClient := documents_InitializeDb(t, ts)
t.Run("Should fail replace with incorrect etag", func(t *testing.T) {
context := context.TODO()
item := map[string]interface{}{"id": "12345", "pk": "123", "isCool": true}
bytes, err := json.Marshal(item)
assert.Nil(t, err)
wrongETag := azcore.ETag("\"incorrect-etag\"")
_, err = collectionClient.ReplaceItem(
context,
azcosmos.PartitionKey{},
"12345",
bytes,
&azcosmos.ItemOptions{IfMatchEtag: &wrongETag},
)
assert.NotNil(t, err)
var respErr *azcore.ResponseError
if errors.As(err, &respErr) {
assert.Equal(t, http.StatusPreconditionFailed, respErr.StatusCode)
assert.Equal(t, "PreconditionFailed", respErr.RawResponse.Header.Get("x-ms-error-code"))
responseBody, readErr := io.ReadAll(respErr.RawResponse.Body)
assert.Nil(t, readErr)
assert.JSONEq(t,
`{"code":"PreconditionFailed","message":"Operation cannot be performed because one of the specified precondition is not met."}`,
string(responseBody),
)
} else {
panic(err)
}
document, status := ts.DataStore.GetDocument(testDatabaseName, testCollectionName, "12345")
assert.Equal(t, datastore.StatusOk, status)
assert.Equal(t, false, document["isCool"])
})
t.Run("Should replace with correct etag", func(t *testing.T) {
context := context.TODO()
readResponse, err := collectionClient.ReadItem(context, azcosmos.PartitionKey{}, "12345", nil)
assert.Nil(t, err)
assert.NotEmpty(t, readResponse.ETag)
var item map[string]interface{}
err = json.Unmarshal(readResponse.Value, &item)
assert.Nil(t, err)
assert.Equal(t, string(readResponse.ETag), item["_etag"])
item["pk"] = "999"
item["isCool"] = true
bytes, err := json.Marshal(item)
assert.Nil(t, err)
etag := readResponse.ETag
_, err = collectionClient.ReplaceItem(
context,
azcosmos.PartitionKey{},
"12345",
bytes,
&azcosmos.ItemOptions{IfMatchEtag: &etag},
)
assert.Nil(t, err)
document, status := ts.DataStore.GetDocument(testDatabaseName, testCollectionName, "12345")
assert.Equal(t, datastore.StatusOk, status)
assert.Equal(t, "999", document["pk"])
assert.Equal(t, true, document["isCool"])
})
})
runTestsWithPresets(t, "Test_Documents_TransactionalBatch", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) { runTestsWithPresets(t, "Test_Documents_TransactionalBatch", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
collectionClient := documents_InitializeDb(t, ts) collectionClient := documents_InitializeDb(t, ts)
@@ -512,4 +627,46 @@ func Test_Documents(t *testing.T) {
assert.Equal(t, "67890", itemResponseBody["id"]) assert.Equal(t, "67890", itemResponseBody["id"])
}) })
}) })
runTestsWithPresets(t, "Test_Documents_With_Continuation_Token", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
collectionClient := documents_InitializeDb(t, ts)
t.Run("Should query document with continuation token", func(t *testing.T) {
context := context.TODO()
pager := collectionClient.NewQueryItemsPager(
"SELECT c.id, c[\"pk\"] FROM c ORDER BY c.id",
azcosmos.PartitionKey{},
&azcosmos.QueryOptions{
PageSizeHint: 1,
})
assert.True(t, pager.More())
firstResponse, err := pager.NextPage(context)
assert.Nil(t, err)
assert.Equal(t, 1, len(firstResponse.Items))
var firstItem map[string]interface{}
err = json.Unmarshal(firstResponse.Items[0], &firstItem)
assert.Nil(t, err)
assert.Equal(t, "12345", firstItem["id"])
assert.Equal(t, "123", firstItem["pk"])
firstContinuationToken := continuationtoken.FromString(*firstResponse.ContinuationToken)
assert.Equal(t, 1, firstContinuationToken.Token.PageIndex)
assert.Equal(t, 1, firstContinuationToken.Token.TotalResults)
assert.True(t, pager.More())
secondResponse, err := pager.NextPage(context)
assert.Nil(t, err)
assert.Equal(t, 1, len(secondResponse.Items))
var secondItem map[string]interface{}
err = json.Unmarshal(secondResponse.Items[0], &secondItem)
assert.Nil(t, err)
assert.Equal(t, "67890", secondItem["id"])
assert.Equal(t, "456", secondItem["pk"])
assert.Nil(t, secondResponse.ContinuationToken)
assert.False(t, pager.More())
})
})
} }
+63
View File
@@ -0,0 +1,63 @@
package main
import (
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"github.com/pikami/cosmium/internal/rntbd"
)
func main() {
input := flag.String("input", "", "Input hex string")
isResponse := flag.Bool("response", false, "Is response")
flag.Parse()
data, err := hex.DecodeString(*input)
if err != nil {
fmt.Printf("Error decoding hex string: %v\n", err)
return
}
frame, err := rntbd.ParseFrame(data, *isResponse)
if err != nil {
fmt.Printf("Error parsing frame: %v\n", err)
return
}
fmt.Printf("Activity ID: %s\n", hex.EncodeToString(frame.ActivityId))
fmt.Printf("Resource Type: %s\n", frame.ResourceType.String())
fmt.Printf("Operation Type: %s\n", frame.OperationType.String())
if len(frame.RequestHeaders) > 0 {
fmt.Printf("=== Request Headers ===\n")
for header, value := range frame.RequestHeaders {
fmt.Printf("%s: %v\n", header.String(), value)
}
}
if len(frame.ResponseHeaders) > 0 {
fmt.Printf("=== Response Headers ===\n")
for header, value := range frame.ResponseHeaders {
fmt.Printf("%s: %v\n", header.String(), value)
}
}
if len(frame.ContextHeaders) > 0 {
fmt.Printf("=== Context Headers ===\n")
for header, value := range frame.ContextHeaders {
fmt.Printf("%s: %v\n", header.String(), value)
}
}
if len(frame.Payload) > 0 {
var jsonObj any
err := json.Unmarshal(frame.Payload, &jsonObj)
if err != nil {
fmt.Printf("Payload: %s\n", hex.EncodeToString(frame.Payload))
} else {
fmt.Printf("Payload: %+v\n", jsonObj)
}
}
}
+10 -1
View File
@@ -11,6 +11,7 @@ import (
badgerdatastore "github.com/pikami/cosmium/internal/datastore/badger_datastore" badgerdatastore "github.com/pikami/cosmium/internal/datastore/badger_datastore"
jsondatastore "github.com/pikami/cosmium/internal/datastore/json_datastore" jsondatastore "github.com/pikami/cosmium/internal/datastore/json_datastore"
"github.com/pikami/cosmium/internal/logger" "github.com/pikami/cosmium/internal/logger"
"github.com/pikami/cosmium/internal/rntbd"
) )
func main() { func main() {
@@ -38,6 +39,15 @@ func main() {
panic(err) panic(err)
} }
if configuration.EnableRntbd {
rntbdServer := rntbd.NewRntbdServer(configuration.RntbdPort, server)
err = rntbdServer.Start()
if err != nil {
panic(err)
}
defer rntbdServer.Stop()
}
waitForExit(server, dataStore) waitForExit(server, dataStore)
} }
@@ -50,6 +60,5 @@ func waitForExit(server *api.ApiServer, dataStore datastore.DataStore) {
// Stop the server // Stop the server
server.Stop() server.Stop()
dataStore.Close() dataStore.Close()
} }
+36 -37
View File
@@ -1,64 +1,63 @@
module github.com/pikami/cosmium module github.com/pikami/cosmium
go 1.25.1 go 1.26.3
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.1 github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.5.0
github.com/cosmiumdev/json-patch/v5 v5.9.11 github.com/cosmiumdev/json-patch/v5 v5.9.11
github.com/dgraph-io/badger/v4 v4.8.0 github.com/dgraph-io/badger/v4 v4.9.6
github.com/gin-gonic/gin v1.11.0 github.com/gin-gonic/gin v1.12.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/vmihailenco/msgpack/v5 v5.4.1
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 golang.org/x/exp v0.0.0-20260727155853-b88d891fe743
) )
require ( require (
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.5.2 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect github.com/dgraph-io/ristretto/v2 v2.4.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.11 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/go-playground/validator/v10 v10.30.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/flatbuffers v25.9.23+incompatible // indirect github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.1 // indirect github.com/klauspost/compress v1.19.1 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.5.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.57.1 // indirect github.com/quic-go/quic-go v0.61.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect github.com/ugorji/go/codec v1.3.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect
go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.29.0 // indirect
golang.org/x/arch v0.23.0 // indirect golang.org/x/crypto v0.54.0 // indirect
golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.57.0 // indirect
golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.40.0 // indirect
golang.org/x/text v0.31.0 // indirect google.golang.org/protobuf v1.36.11 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+78 -79
View File
@@ -1,47 +1,45 @@
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0/go.mod h1:/WYEx9pcM9Y+Dd/APJaNlSvVSvzl54rrMdZT5+Oi2LM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.5.0 h1:wtCn7MemMD9eo4/NdpJ6S/MFD2BV2CDwoEfvl5th2vM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.5.0/go.mod h1:MIyTWizpwnsX4LS9/tW1II9JL+D25Ypzj6URaT9NcgQ=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.1 h1:ToPLhnXvatKVN4ZkcxLOwcXOJhdu4iQl8w0efeuDz9Y= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.1/go.mod h1:Krtog/7tz27z75TwM5cIS8bxEH4dcBUezcq+kGVeZEo= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
github.com/cosmiumdev/json-patch/v5 v5.9.11 h1:WD2Wqaz/vO987z2FFdqgkj15HgYZ/Y5TpqE3I4T/iOQ= github.com/cosmiumdev/json-patch/v5 v5.9.11 h1:WD2Wqaz/vO987z2FFdqgkj15HgYZ/Y5TpqE3I4T/iOQ=
github.com/cosmiumdev/json-patch/v5 v5.9.11/go.mod h1:YPZmckmv4ZY+oxKIOjgq3sIudHVB6VEMcicCS9LtVLM= github.com/cosmiumdev/json-patch/v5 v5.9.11/go.mod h1:YPZmckmv4ZY+oxKIOjgq3sIudHVB6VEMcicCS9LtVLM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= github.com/dgraph-io/badger/v4 v4.9.6 h1:IQqMPVGLNCQr1b4Mu8lHkYm/xyqFRsyKaFEtyLi9CCQ=
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/badger/v4 v4.9.6/go.mod h1:Xa9dAupjbwAacupWFCpa6YEn9E1PjBXkfZYr2I/8aWg=
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= github.com/dgraph-io/ristretto/v2 v2.4.2 h1:x0cvjmUKxt764Yxdk2nr94we1AvPPAMh1rh5TQ+Jo80=
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= github.com/dgraph-io/ristretto/v2 v2.4.2/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -50,16 +48,16 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU= github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -67,35 +65,37 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -111,39 +111,38 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 h1:ex206bKw+v3K0dm3andkrIF+ijyQKJG1pLgwQ2PYdQM=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+4
View File
@@ -35,3 +35,7 @@ var UnknownErrorResponse = gin.H{"message": "Unknown error"}
var NotFoundResponse = gin.H{"message": "NotFound"} var NotFoundResponse = gin.H{"message": "NotFound"}
var ConflictResponse = gin.H{"message": "Conflict"} var ConflictResponse = gin.H{"message": "Conflict"}
var BadRequestResponse = gin.H{"message": "BadRequest"} var BadRequestResponse = gin.H{"message": "BadRequest"}
var PreconditionFailedResponse = gin.H{
"code": "PreconditionFailed",
"message": "Operation cannot be performed because one of the specified precondition is not met.",
}
@@ -0,0 +1,145 @@
package continuationtoken
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/pikami/cosmium/internal/logger"
)
type ContinuationTokenExternal struct {
Token string `json:"token"`
Range struct {
Min string `json:"min"`
Max string `json:"max"`
} `json:"range"`
}
type ContinuationToken struct {
Token struct {
ResourceId string // RID
PageIndex int // RT
TotalResults int // TRC
ISV int // ISV
IEO int // IEO
QCF int // QCF
LR int // LR
}
Range struct {
Min string
Max string
}
}
func Generate(resourceid string, pageIndex int, totalResults int) ContinuationToken {
ct := ContinuationToken{}
ct.Token.ResourceId = resourceid
ct.Token.PageIndex = pageIndex
ct.Token.TotalResults = totalResults
ct.Token.ISV = 2
ct.Token.IEO = 65567
ct.Token.QCF = 8
ct.Token.LR = 1
ct.Range.Min = ""
ct.Range.Max = "FF"
return ct
}
func GenerateDefault(resourceid string) ContinuationToken {
return Generate(resourceid, 0, 0)
}
func (ct *ContinuationToken) ToString() string {
token := fmt.Sprintf(
"-RID:~%s#RT:%d#TRC:%d#ISV:%d#IEO:%d#QCF:%d#LR:%d",
ct.Token.ResourceId,
ct.Token.PageIndex,
ct.Token.TotalResults,
ct.Token.ISV,
ct.Token.IEO,
ct.Token.QCF,
ct.Token.LR,
)
ect := ContinuationTokenExternal{}
ect.Token = token
ect.Range.Min = ct.Range.Min
ect.Range.Max = ct.Range.Max
json, err := json.Marshal(ect)
if err != nil {
logger.Error(err, "failed to marshal continuation token")
return ""
}
return string(json)
}
func FromString(token string) ContinuationToken {
ect := ContinuationTokenExternal{}
err := json.Unmarshal([]byte(token), &ect)
if err != nil {
logger.Error(err, "failed to unmarshal continuation token")
return ContinuationToken{}
}
ct, err := parseContinuationToken(ect.Token, ect.Range.Min, ect.Range.Max)
if err != nil {
logger.Error(err, "failed to parse continuation token")
return ContinuationToken{}
}
return *ct
}
func parseContinuationToken(token string, minRange string, maxRange string) (*ContinuationToken, error) {
const prefix = "-RID:~"
if !strings.HasPrefix(token, prefix) {
return nil, fmt.Errorf("invalid token prefix")
}
parts := strings.Split(token[len(prefix):], "#")
if len(parts) != 7 {
return nil, fmt.Errorf("invalid token format: expected 7 fields, got %d", len(parts))
}
ct := &ContinuationToken{}
ct.Token.ResourceId = parts[0]
parseIntField := func(part, key string) (int, error) {
if !strings.HasPrefix(part, key+":") {
return 0, fmt.Errorf("expected %s field", key)
}
return strconv.Atoi(strings.TrimPrefix(part, key+":"))
}
var err error
if ct.Token.PageIndex, err = parseIntField(parts[1], "RT"); err != nil {
return nil, err
}
if ct.Token.TotalResults, err = parseIntField(parts[2], "TRC"); err != nil {
return nil, err
}
if ct.Token.ISV, err = parseIntField(parts[3], "ISV"); err != nil {
return nil, err
}
if ct.Token.IEO, err = parseIntField(parts[4], "IEO"); err != nil {
return nil, err
}
if ct.Token.QCF, err = parseIntField(parts[5], "QCF"); err != nil {
return nil, err
}
if ct.Token.LR, err = parseIntField(parts[6], "LR"); err != nil {
return nil, err
}
ct.Range.Min = minRange
ct.Range.Max = maxRange
return ct, nil
}
@@ -0,0 +1,35 @@
package continuationtoken
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Generate(t *testing.T) {
token := Generate("test-resource-id", 1, 100)
assert.Equal(t, "test-resource-id", token.Token.ResourceId)
assert.Equal(t, 1, token.Token.PageIndex)
assert.Equal(t, 100, token.Token.TotalResults)
}
func Test_FromString(t *testing.T) {
token := FromString("{\"token\":\"-RID:~test-resource-id#RT:1#TRC:100#ISV:2#IEO:65567#QCF:8#LR:1\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}")
assert.Equal(t, "test-resource-id", token.Token.ResourceId)
assert.Equal(t, 1, token.Token.PageIndex)
assert.Equal(t, 100, token.Token.TotalResults)
}
func Test_ToString(t *testing.T) {
token := Generate("test-resource-id", 1, 100)
assert.Equal(t, "{\"token\":\"-RID:~test-resource-id#RT:1#TRC:100#ISV:2#IEO:65567#QCF:8#LR:1\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}", token.ToString())
}
func Test_GenerateDefault(t *testing.T) {
token := GenerateDefault("test-resource-id")
assert.Equal(t, "test-resource-id", token.Token.ResourceId)
assert.Equal(t, 0, token.Token.PageIndex)
assert.Equal(t, 0, token.Token.TotalResults)
}
@@ -14,6 +14,8 @@ import (
type BadgerDataStore struct { type BadgerDataStore struct {
db *badger.DB db *badger.DB
gcTicker *time.Ticker gcTicker *time.Ticker
gcDone chan struct{}
gcStopped chan struct{}
} }
type BadgerDataStoreOptions struct { type BadgerDataStoreOptions struct {
@@ -38,6 +40,8 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
ds := &BadgerDataStore{ ds := &BadgerDataStore{
db: db, db: db,
gcTicker: gcTicker, gcTicker: gcTicker,
gcDone: make(chan struct{}),
gcStopped: make(chan struct{}),
} }
ds.initializeDataStore(options.InitialDataFilePath) ds.initializeDataStore(options.InitialDataFilePath)
@@ -50,7 +54,8 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
func (r *BadgerDataStore) Close() { func (r *BadgerDataStore) Close() {
if r.gcTicker != nil { if r.gcTicker != nil {
r.gcTicker.Stop() r.gcTicker.Stop()
r.gcTicker = nil close(r.gcDone)
<-r.gcStopped
} }
r.db.Close() r.db.Close()
@@ -63,11 +68,19 @@ func (r *BadgerDataStore) DumpToJson() (string, error) {
} }
func (r *BadgerDataStore) runGarbageCollector() { func (r *BadgerDataStore) runGarbageCollector() {
for range r.gcTicker.C { defer close(r.gcStopped)
again:
for {
select {
case <-r.gcTicker.C:
for {
err := r.db.RunValueLogGC(0.7) err := r.db.RunValueLogGC(0.7)
if err == nil { if err != nil {
goto again break
}
}
case <-r.gcDone:
return
} }
} }
} }
+138
View File
@@ -0,0 +1,138 @@
package rntbd
import (
"bytes"
"encoding/binary"
)
type RntbdResponseFrame struct {
StatusCode uint16
ResourceType RntbdResourceType
ActivityId []byte
ResponseHeaders []RntbdResponseHeader
Payload []byte
}
type RntbdResponseHeader struct {
HeaderId uint16
TokenType RntbdTokenType
TokenValue any
}
type RntbdResponseFrameBuilder struct {
frame RntbdResponseFrame
}
func (b *RntbdResponseFrameBuilder) AddHeader(headerId uint16, tokenType RntbdTokenType, tokenValue any) {
b.frame.ResponseHeaders = append(b.frame.ResponseHeaders, RntbdResponseHeader{
HeaderId: headerId,
TokenType: tokenType,
TokenValue: tokenValue,
})
}
func (b *RntbdResponseFrameBuilder) AddPayload(payload []byte) {
b.frame.Payload = payload
}
func (b *RntbdResponseFrameBuilder) SetStatusCode(statusCode uint16) {
b.frame.StatusCode = statusCode
}
func (b *RntbdResponseFrameBuilder) SetResourceType(resourceType RntbdResourceType) {
b.frame.ResourceType = resourceType
}
func (b *RntbdResponseFrameBuilder) SetActivityId(activityId []byte) {
b.frame.ActivityId = activityId
}
func (b *RntbdResponseFrameBuilder) Build() *RntbdResponseFrame {
return &b.frame
}
func (f *RntbdResponseFrame) ToBytes() []byte {
var buffer bytes.Buffer
binary.Write(&buffer, binary.LittleEndian, f.StatusCode)
binary.Write(&buffer, binary.LittleEndian, uint16(f.ResourceType))
binary.Write(&buffer, binary.LittleEndian, f.ActivityId)
for _, header := range f.ResponseHeaders {
binary.Write(&buffer, binary.LittleEndian, header.HeaderId)
binary.Write(&buffer, binary.LittleEndian, uint8(header.TokenType))
switch header.TokenType {
case RntbdTokenTypeByte:
buffer.Write(header.TokenValue.([]byte))
case RntbdTokenTypeUShort:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(uint16))
case RntbdTokenTypeULong:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(uint32))
case RntbdTokenTypeLong:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(int32))
case RntbdTokenTypeULongLong:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(uint64))
case RntbdTokenTypeLongLong:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(int64))
case RntbdTokenTypeGuid:
buffer.Write(header.TokenValue.([]byte))
case RntbdTokenTypeSmallString:
binary.Write(&buffer, binary.LittleEndian, uint8(len(header.TokenValue.(string))))
buffer.WriteString(header.TokenValue.(string))
case RntbdTokenTypeString:
binary.Write(&buffer, binary.LittleEndian, uint16(len(header.TokenValue.(string))))
buffer.WriteString(header.TokenValue.(string))
case RntbdTokenTypeULongString:
binary.Write(&buffer, binary.LittleEndian, uint32(len(header.TokenValue.(string))))
buffer.WriteString(header.TokenValue.(string))
case RntbdTokenTypeSmallBytes:
binary.Write(&buffer, binary.LittleEndian, uint8(len(header.TokenValue.([]byte))))
buffer.Write(header.TokenValue.([]byte))
case RntbdTokenTypeBytes:
binary.Write(&buffer, binary.LittleEndian, uint16(len(header.TokenValue.([]byte))))
buffer.Write(header.TokenValue.([]byte))
case RntbdTokenTypeULongBytes:
binary.Write(&buffer, binary.LittleEndian, uint32(len(header.TokenValue.([]byte))))
buffer.Write(header.TokenValue.([]byte))
case RntbdTokenTypeFloat:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(float32))
case RntbdTokenTypeDouble:
binary.Write(&buffer, binary.LittleEndian, header.TokenValue.(float64))
case RntbdTokenTypeInvalid:
panic("invalid token type")
default:
panic("invalid token type")
}
}
payloadSize := uint32(0)
if len(f.Payload) > 0 {
payloadSize = uint32(len(f.Payload)) + 4
}
frameSize := uint32(buffer.Len()) + 4
result := make([]byte, frameSize+payloadSize)
binary.LittleEndian.PutUint32(result, frameSize)
copy(result[4:], buffer.Bytes())
if len(f.Payload) > 0 {
binary.LittleEndian.PutUint32(result[frameSize:], payloadSize-4)
copy(result[frameSize+4:], f.Payload)
}
return result
}
func buildContextFrame(requestFrame *RntbdFrame) []byte {
builder := RntbdResponseFrameBuilder{}
builder.SetStatusCode(200)
builder.SetResourceType(RntbdResourceTypeConnection)
builder.SetActivityId(requestFrame.ActivityId)
builder.AddHeader(uint16(RntbdContextHeaderServerAgent), RntbdTokenTypeSmallString, "DocumentDB Server")
builder.AddHeader(uint16(RntbdContextHeaderServerVersion), RntbdTokenTypeSmallString, " version=2.14.0.0")
builder.AddHeader(uint16(RntbdContextHeaderIdleTimeoutInSeconds), RntbdTokenTypeULong, uint32(120))
builder.AddHeader(uint16(RntbdContextHeaderUnauthenticatedTimeoutInSeconds), RntbdTokenTypeULong, uint32(25))
return builder.Build().ToBytes()
}
+746
View File
@@ -0,0 +1,746 @@
package rntbd
import (
"fmt"
"github.com/pikami/cosmium/api/headers"
)
type RntbdOperationType uint16
const (
RntbdOperationTypeConnection RntbdOperationType = 0x0000
RntbdOperationTypeCreate RntbdOperationType = 0x0001
RntbdOperationTypeUpdate RntbdOperationType = 0x0002
RntbdOperationTypeRead RntbdOperationType = 0x0003
RntbdOperationTypeReadFeed RntbdOperationType = 0x0004
RntbdOperationTypeDelete RntbdOperationType = 0x0005
RntbdOperationTypeReplace RntbdOperationType = 0x0006
RntbdOperationTypeExecuteJavaScript RntbdOperationType = 0x0008
RntbdOperationTypeSQLQuery RntbdOperationType = 0x0009
RntbdOperationTypePause RntbdOperationType = 0x000A
RntbdOperationTypeResume RntbdOperationType = 0x000B
RntbdOperationTypeStop RntbdOperationType = 0x000C
RntbdOperationTypeRecycle RntbdOperationType = 0x000D
RntbdOperationTypeCrash RntbdOperationType = 0x000E
RntbdOperationTypeQuery RntbdOperationType = 0x000F
RntbdOperationTypeForceConfigRefresh RntbdOperationType = 0x0010
RntbdOperationTypeHead RntbdOperationType = 0x0011
RntbdOperationTypeHeadFeed RntbdOperationType = 0x0012
RntbdOperationTypeUpsert RntbdOperationType = 0x0013
RntbdOperationTypeRecreate RntbdOperationType = 0x0014
RntbdOperationTypeThrottle RntbdOperationType = 0x0015
RntbdOperationTypeGetSplitPoint RntbdOperationType = 0x0016
RntbdOperationTypePreCreateValidation RntbdOperationType = 0x0017
RntbdOperationTypeBatchApply RntbdOperationType = 0x0018
RntbdOperationTypeAbortSplit RntbdOperationType = 0x0019
RntbdOperationTypeCompleteSplit RntbdOperationType = 0x001A
RntbdOperationTypeOfferUpdateOperation RntbdOperationType = 0x001B
RntbdOperationTypeOfferPreGrowValidation RntbdOperationType = 0x001C
RntbdOperationTypeBatchReportThroughputUtilization RntbdOperationType = 0x001D
RntbdOperationTypeCompletePartitionMigration RntbdOperationType = 0x001E
RntbdOperationTypeAbortPartitionMigration RntbdOperationType = 0x001F
RntbdOperationTypePreReplaceValidation RntbdOperationType = 0x0020
RntbdOperationTypeAddComputeGatewayRequestCharges RntbdOperationType = 0x0021
RntbdOperationTypeMigratePartition RntbdOperationType = 0x0022
)
type RntbdResourceType uint16
const (
RntbdResourceTypeConnection RntbdResourceType = 0x0000
RntbdResourceTypeDatabase RntbdResourceType = 0x0001
RntbdResourceTypeCollection RntbdResourceType = 0x0002
RntbdResourceTypeDocument RntbdResourceType = 0x0003
RntbdResourceTypeAttachment RntbdResourceType = 0x0004
RntbdResourceTypeUser RntbdResourceType = 0x0005
RntbdResourceTypePermission RntbdResourceType = 0x0006
RntbdResourceTypeStoredProcedure RntbdResourceType = 0x0007
RntbdResourceTypeConflict RntbdResourceType = 0x0008
RntbdResourceTypeTrigger RntbdResourceType = 0x0009
RntbdResourceTypeUserDefinedFunction RntbdResourceType = 0x000A
RntbdResourceTypeModule RntbdResourceType = 0x000B
RntbdResourceTypeReplica RntbdResourceType = 0x000C
RntbdResourceTypeModuleCommand RntbdResourceType = 0x000D
RntbdResourceTypeRecord RntbdResourceType = 0x000E
RntbdResourceTypeOffer RntbdResourceType = 0x000F
RntbdResourceTypePartitionSetInformation RntbdResourceType = 0x0010
RntbdResourceTypeXPReplicatorAddress RntbdResourceType = 0x0011
RntbdResourceTypeMasterPartition RntbdResourceType = 0x0012
RntbdResourceTypeServerPartition RntbdResourceType = 0x0013
RntbdResourceTypeDatabaseAccount RntbdResourceType = 0x0014
RntbdResourceTypeTopology RntbdResourceType = 0x0015
RntbdResourceTypePartitionKeyRange RntbdResourceType = 0x0016
RntbdResourceTypeSchema RntbdResourceType = 0x0018
RntbdResourceTypeBatchApply RntbdResourceType = 0x0019
RntbdResourceTypeRestoreMetadata RntbdResourceType = 0x001A
RntbdResourceTypeComputeGatewayCharges RntbdResourceType = 0x001B
RntbdResourceTypeRidRange RntbdResourceType = 0x001C
RntbdResourceTypeUserDefinedType RntbdResourceType = 0x001D
)
type RntbdRequestHeader uint16
const (
RntbdRequestHeaderResourceId RntbdRequestHeader = 0x0000 // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderAuthorizationToken RntbdRequestHeader = 0x0001 // RntbdTokenType.String, required = false
RntbdRequestHeaderPayloadPresent RntbdRequestHeader = 0x0002 // RntbdTokenType.Byte, required = true
RntbdRequestHeaderDate RntbdRequestHeader = 0x0003 // RntbdTokenType.SmallString, required = false
RntbdRequestHeaderPageSize RntbdRequestHeader = 0x0004 // RntbdTokenType.ULong, required = false
RntbdRequestHeaderSessionToken RntbdRequestHeader = 0x0005 // RntbdTokenType.String, required = false
RntbdRequestHeaderContinuationToken RntbdRequestHeader = 0x0006 // RntbdTokenType.String, required = false
RntbdRequestHeaderIndexingDirective RntbdRequestHeader = 0x0007 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderMatch RntbdRequestHeader = 0x0008 // RntbdTokenType.String, required = false
RntbdRequestHeaderPreTriggerInclude RntbdRequestHeader = 0x0009 // RntbdTokenType.String, required = false
RntbdRequestHeaderPostTriggerInclude RntbdRequestHeader = 0x000A // RntbdTokenType.String, required = false
RntbdRequestHeaderIsFanout RntbdRequestHeader = 0x000B // RntbdTokenType.Byte, required = false
RntbdRequestHeaderCollectionPartitionIndex RntbdRequestHeader = 0x000C // RntbdTokenType.ULong, required = false
RntbdRequestHeaderCollectionServiceIndex RntbdRequestHeader = 0x000D // RntbdTokenType.ULong, required = false
RntbdRequestHeaderPreTriggerExclude RntbdRequestHeader = 0x000E // RntbdTokenType.String, required = false
RntbdRequestHeaderPostTriggerExclude RntbdRequestHeader = 0x000F // RntbdTokenType.String, required = false
RntbdRequestHeaderConsistencyLevel RntbdRequestHeader = 0x0010 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderEntityId RntbdRequestHeader = 0x0011 // RntbdTokenType.String, required = false
RntbdRequestHeaderResourceSchemaName RntbdRequestHeader = 0x0012 // RntbdTokenType.SmallString, required = false
RntbdRequestHeaderReplicaPath RntbdRequestHeader = 0x0013 // RntbdTokenType.String, required = true
RntbdRequestHeaderResourceTokenExpiry RntbdRequestHeader = 0x0014 // RntbdTokenType.ULong, required = false
RntbdRequestHeaderDatabaseName RntbdRequestHeader = 0x0015 // RntbdTokenType.String, required = false
RntbdRequestHeaderCollectionName RntbdRequestHeader = 0x0016 // RntbdTokenType.String, required = false
RntbdRequestHeaderDocumentName RntbdRequestHeader = 0x0017 // RntbdTokenType.String, required = false
RntbdRequestHeaderAttachmentName RntbdRequestHeader = 0x0018 // RntbdTokenType.String, required = false
RntbdRequestHeaderUserName RntbdRequestHeader = 0x0019 // RntbdTokenType.String, required = false
RntbdRequestHeaderPermissionName RntbdRequestHeader = 0x001A // RntbdTokenType.String, required = false
RntbdRequestHeaderStoredProcedureName RntbdRequestHeader = 0x001B // RntbdTokenType.String, required = false
RntbdRequestHeaderUserDefinedFunctionName RntbdRequestHeader = 0x001C // RntbdTokenType.String, required = false
RntbdRequestHeaderTriggerName RntbdRequestHeader = 0x001D // RntbdTokenType.String, required = false
RntbdRequestHeaderEnableScanInQuery RntbdRequestHeader = 0x001E // RntbdTokenType.Byte, required = false
RntbdRequestHeaderEmitVerboseTracesInQuery RntbdRequestHeader = 0x001F // RntbdTokenType.Byte, required = false
RntbdRequestHeaderConflictName RntbdRequestHeader = 0x0020 // RntbdTokenType.String, required = false
RntbdRequestHeaderBindReplicaDirective RntbdRequestHeader = 0x0021 // RntbdTokenType.String, required = false
RntbdRequestHeaderPrimaryMasterKey RntbdRequestHeader = 0x0022 // RntbdTokenType.String, required = false
RntbdRequestHeaderSecondaryMasterKey RntbdRequestHeader = 0x0023 // RntbdTokenType.String, required = false
RntbdRequestHeaderPrimaryReadonlyKey RntbdRequestHeader = 0x0024 // RntbdTokenType.String, required = false
RntbdRequestHeaderSecondaryReadonlyKey RntbdRequestHeader = 0x0025 // RntbdTokenType.String, required = false
RntbdRequestHeaderProfileRequest RntbdRequestHeader = 0x0026 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderEnableLowPrecisionOrderBy RntbdRequestHeader = 0x0027 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderClientVersion RntbdRequestHeader = 0x0028 // RntbdTokenType.SmallString, required = false
RntbdRequestHeaderCanCharge RntbdRequestHeader = 0x0029 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderCanThrottle RntbdRequestHeader = 0x002A // RntbdTokenType.Byte, required = false
RntbdRequestHeaderPartitionKey RntbdRequestHeader = 0x002B // RntbdTokenType.String, required = false
RntbdRequestHeaderPartitionKeyRangeId RntbdRequestHeader = 0x002C // RntbdTokenType.String, required = false
RntbdRequestHeaderNotUsed2D RntbdRequestHeader = 0x002D // RntbdTokenType.Invalid, required = false
RntbdRequestHeaderNotUsed2E RntbdRequestHeader = 0x002E // RntbdTokenType.Invalid, required = false
RntbdRequestHeaderNotUsed2F RntbdRequestHeader = 0x002F // RntbdTokenType.Invalid, required = false
RntbdRequestHeaderMigrateCollectionDirective RntbdRequestHeader = 0x0031 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderNotUsed32 RntbdRequestHeader = 0x0032 // RntbdTokenType.Invalid, required = false
RntbdRequestHeaderSupportSpatialLegacyCoordinates RntbdRequestHeader = 0x0033 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderPartitionCount RntbdRequestHeader = 0x0034 // RntbdTokenType.ULong, required = false
RntbdRequestHeaderCollectionRid RntbdRequestHeader = 0x0035 // RntbdTokenType.String, required = false
RntbdRequestHeaderPartitionKeyRangeName RntbdRequestHeader = 0x0036 // RntbdTokenType.String, required = false
RntbdRequestHeaderSchemaName RntbdRequestHeader = 0x003A // RntbdTokenType.String, required = false
RntbdRequestHeaderFilterBySchemaRid RntbdRequestHeader = 0x003B // RntbdTokenType.String, required = false
RntbdRequestHeaderUsePolygonsSmallerThanAHemisphere RntbdRequestHeader = 0x003C // RntbdTokenType.Byte, required = false
RntbdRequestHeaderGatewaySignature RntbdRequestHeader = 0x003D // RntbdTokenType.String, required = false
RntbdRequestHeaderEnableLogging RntbdRequestHeader = 0x003E // RntbdTokenType.Byte, required = false
RntbdRequestHeaderAIM RntbdRequestHeader = 0x003F // RntbdTokenType.String, required = false
RntbdRequestHeaderPopulateQuotaInfo RntbdRequestHeader = 0x0040 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderDisableRUPerMinuteUsage RntbdRequestHeader = 0x0041 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderPopulateQueryMetrics RntbdRequestHeader = 0x0042 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderResponseContinuationTokenLimitInKb RntbdRequestHeader = 0x0043 // RntbdTokenType.ULong, required = false
RntbdRequestHeaderPopulatePartitionStatistics RntbdRequestHeader = 0x0044 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderRemoteStorageType RntbdRequestHeader = 0x0045 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderCollectionRemoteStorageSecurityIdentifier RntbdRequestHeader = 0x0046 // RntbdTokenType.String, required = false
RntbdRequestHeaderIfModifiedSince RntbdRequestHeader = 0x0047 // RntbdTokenType.String, required = false
RntbdRequestHeaderPopulateCollectionThroughputInfo RntbdRequestHeader = 0x0048 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderRemainingTimeInMsOnClientRequest RntbdRequestHeader = 0x0049 // RntbdTokenType.ULong, required = false
RntbdRequestHeaderClientRetryAttemptCount RntbdRequestHeader = 0x004A // RntbdTokenType.ULong, required = false
RntbdRequestHeaderTargetLsn RntbdRequestHeader = 0x004B // RntbdTokenType.LongLong, required = false
RntbdRequestHeaderTargetGlobalCommittedLsn RntbdRequestHeader = 0x004C // RntbdTokenType.LongLong, required = false
RntbdRequestHeaderTransportRequestID RntbdRequestHeader = 0x004D // RntbdTokenType.ULong, required = false
RntbdRequestHeaderRestoreMetadaFilter RntbdRequestHeader = 0x004E // RntbdTokenType.String, required = false
RntbdRequestHeaderRestoreParams RntbdRequestHeader = 0x004F // RntbdTokenType.String, required = false
RntbdRequestHeaderShareThroughput RntbdRequestHeader = 0x0050 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderPartitionResourceFilter RntbdRequestHeader = 0x0051 // RntbdTokenType.String, required = false
RntbdRequestHeaderIsReadOnlyScript RntbdRequestHeader = 0x0052 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderIsAutoScaleRequest RntbdRequestHeader = 0x0053 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderForceQueryScan RntbdRequestHeader = 0x0054 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderCanOfferReplaceComplete RntbdRequestHeader = 0x0056 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderExcludeSystemProperties RntbdRequestHeader = 0x0057 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderBinaryId RntbdRequestHeader = 0x0058 // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderTimeToLiveInSeconds RntbdRequestHeader = 0x0059 // RntbdTokenType.Long, required = false
RntbdRequestHeaderEffectivePartitionKey RntbdRequestHeader = 0x005A // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderBinaryPassthroughRequest RntbdRequestHeader = 0x005B // RntbdTokenType.Byte, required = false
RntbdRequestHeaderUserDefinedTypeName RntbdRequestHeader = 0x005C // RntbdTokenType.String, required = false
RntbdRequestHeaderEnableDynamicRidRangeAllocation RntbdRequestHeader = 0x005D // RntbdTokenType.Byte, required = false
RntbdRequestHeaderEnumerationDirection RntbdRequestHeader = 0x005E // RntbdTokenType.Byte, required = false
RntbdRequestHeaderStartId RntbdRequestHeader = 0x005F // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderEndId RntbdRequestHeader = 0x0060 // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderFanoutOperationState RntbdRequestHeader = 0x0061 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderStartEpk RntbdRequestHeader = 0x0062 // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderEndEpk RntbdRequestHeader = 0x0063 // RntbdTokenType.Bytes, required = false
RntbdRequestHeaderReadFeedKeyType RntbdRequestHeader = 0x0064 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderContentSerializationFormat RntbdRequestHeader = 0x0065 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderAllowTentativeWrites RntbdRequestHeader = 0x0066 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderIsUserRequest RntbdRequestHeader = 0x0067 // RntbdTokenType.Byte, required = false
RntbdRequestHeaderSharedOfferThroughput RntbdRequestHeader = 0x0068 // RntbdTokenType.ULong, required = false
RntbdRequestHeaderSDKSupportedCapabilities RntbdRequestHeader = 0x00A2 // RntbdTokenType.ULong, required = ?
)
type RntbdResponseHeaderType uint16
const (
RntbdResponseHeaderPayloadPresent RntbdResponseHeaderType = 0x0000 // RntbdTokenType.Byte, required = true
RntbdResponseHeaderLastStateChangeDateTime RntbdResponseHeaderType = 0x0002 // RntbdTokenType.SmallString, required = false
RntbdResponseHeaderContinuationToken RntbdResponseHeaderType = 0x0003 // RntbdTokenType.String, required = false
RntbdResponseHeaderETag RntbdResponseHeaderType = 0x0004 // RntbdTokenType.String, required = false
RntbdResponseHeaderReadsPerformed RntbdResponseHeaderType = 0x0007 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderWritesPerformed RntbdResponseHeaderType = 0x0008 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderQueriesPerformed RntbdResponseHeaderType = 0x0009 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderIndexTermsGenerated RntbdResponseHeaderType = 0x000A // RntbdTokenType.ULong, required = false
RntbdResponseHeaderScriptsExecuted RntbdResponseHeaderType = 0x000B // RntbdTokenType.ULong, required = false
RntbdResponseHeaderRetryAfterMilliseconds RntbdResponseHeaderType = 0x000C // RntbdTokenType.ULong, required = false
RntbdResponseHeaderIndexingDirective RntbdResponseHeaderType = 0x000D // RntbdTokenType.Byte, required = false
RntbdResponseHeaderStorageMaxResoureQuota RntbdResponseHeaderType = 0x000E // RntbdTokenType.String, required = false
RntbdResponseHeaderStorageResourceQuotaUsage RntbdResponseHeaderType = 0x000F // RntbdTokenType.String, required = false
RntbdResponseHeaderSchemaVersion RntbdResponseHeaderType = 0x0010 // RntbdTokenType.SmallString, required = false
RntbdResponseHeaderCollectionPartitionIndex RntbdResponseHeaderType = 0x0011 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderCollectionServiceIndex RntbdResponseHeaderType = 0x0012 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderLSN RntbdResponseHeaderType = 0x0013 // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderItemCount RntbdResponseHeaderType = 0x0014 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderRequestCharge RntbdResponseHeaderType = 0x0015 // RntbdTokenType.Double, required = false
RntbdResponseHeaderOwnerFullName RntbdResponseHeaderType = 0x0017 // RntbdTokenType.String, required = false
RntbdResponseHeaderOwnerId RntbdResponseHeaderType = 0x0018 // RntbdTokenType.String, required = false
RntbdResponseHeaderDatabaseAccountId RntbdResponseHeaderType = 0x0019 // RntbdTokenType.String, required = false
RntbdResponseHeaderQuorumAckedLSN RntbdResponseHeaderType = 0x001A // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderRequestValidationFailure RntbdResponseHeaderType = 0x001B // RntbdTokenType.Byte, required = false
RntbdResponseHeaderSubStatus RntbdResponseHeaderType = 0x001C // RntbdTokenType.ULong, required = false
RntbdResponseHeaderCollectionUpdateProgress RntbdResponseHeaderType = 0x001D // RntbdTokenType.ULong, required = false
RntbdResponseHeaderCurrentWriteQuorum RntbdResponseHeaderType = 0x001E // RntbdTokenType.ULong, required = false
RntbdResponseHeaderCurrentReplicaSetSize RntbdResponseHeaderType = 0x001F // RntbdTokenType.ULong, required = false
RntbdResponseHeaderCollectionLazyIndexProgress RntbdResponseHeaderType = 0x0020 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderPartitionKeyRangeId RntbdResponseHeaderType = 0x0021 // RntbdTokenType.String, required = false
RntbdResponseHeaderLogResults RntbdResponseHeaderType = 0x0025 // RntbdTokenType.String, required = false
RntbdResponseHeaderXPRole RntbdResponseHeaderType = 0x0026 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderIsRUPerMinuteUsed RntbdResponseHeaderType = 0x0027 // RntbdTokenType.Byte, required = false
RntbdResponseHeaderQueryMetrics RntbdResponseHeaderType = 0x0028 // RntbdTokenType.String, required = false
RntbdResponseHeaderGlobalCommittedLSN RntbdResponseHeaderType = 0x0029 // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderNumberOfReadRegions RntbdResponseHeaderType = 0x0030 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderOfferReplacePending RntbdResponseHeaderType = 0x0031 // RntbdTokenType.Byte, required = false
RntbdResponseHeaderItemLSN RntbdResponseHeaderType = 0x0032 // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderRestoreState RntbdResponseHeaderType = 0x0033 // RntbdTokenType.String, required = false
RntbdResponseHeaderCollectionSecurityIdentifier RntbdResponseHeaderType = 0x0034 // RntbdTokenType.String, required = false
RntbdResponseHeaderTransportRequestID RntbdResponseHeaderType = 0x0035 // RntbdTokenType.ULong, required = false
RntbdResponseHeaderShareThroughput RntbdResponseHeaderType = 0x0036 // RntbdTokenType.Byte, required = false
RntbdResponseHeaderDisableRntbdChannel RntbdResponseHeaderType = 0x0038 // RntbdTokenType.Byte, required = false
RntbdResponseHeaderServerDateTimeUtc RntbdResponseHeaderType = 0x0039 // RntbdTokenType.SmallString, required = false
RntbdResponseHeaderLocalLSN RntbdResponseHeaderType = 0x003A // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderQuorumAckedLocalLSN RntbdResponseHeaderType = 0x003B // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderItemLocalLSN RntbdResponseHeaderType = 0x003C // RntbdTokenType.LongLong, required = false
RntbdResponseHeaderHasTentativeWrites RntbdResponseHeaderType = 0x003D // RntbdTokenType.Byte, required = false
RntbdResponseHeaderSessionToken RntbdResponseHeaderType = 0x003E // RntbdTokenType.String, required = false
)
type RntbdContextHeader uint16
const (
RntbdContextHeaderProtocolVersion RntbdContextHeader = 0x0000 // RntbdTokenType.ULong, required = false
RntbdContextHeaderClientVersion RntbdContextHeader = 0x0001 // RntbdTokenType.SmallString, required = false
RntbdContextHeaderServerAgent RntbdContextHeader = 0x0002 // RntbdTokenType.SmallString, required = true
RntbdContextHeaderServerVersion RntbdContextHeader = 0x0003 // RntbdTokenType.SmallString, required = true
RntbdContextHeaderIdleTimeoutInSeconds RntbdContextHeader = 0x0004 // RntbdTokenType.ULong, required = false
RntbdContextHeaderUnauthenticatedTimeoutInSeconds RntbdContextHeader = 0x0005 // RntbdTokenType.ULong, required = false
)
type RntbdTokenType uint8
const (
RntbdTokenTypeByte RntbdTokenType = 0x00 // 8bit boolean
RntbdTokenTypeUShort RntbdTokenType = 0x01 // 16bit unsigned integer
RntbdTokenTypeULong RntbdTokenType = 0x02 // 32bit unsigned integer
RntbdTokenTypeLong RntbdTokenType = 0x03 // 32bit signed integer
RntbdTokenTypeULongLong RntbdTokenType = 0x04 // 64bit unsigned integer
RntbdTokenTypeLongLong RntbdTokenType = 0x05 // 64bit signed integer
RntbdTokenTypeGuid RntbdTokenType = 0x06 // 128bit GUID
RntbdTokenTypeSmallString RntbdTokenType = 0x07 // 8bit len + string
RntbdTokenTypeString RntbdTokenType = 0x08 // 16bit len + string
RntbdTokenTypeULongString RntbdTokenType = 0x09 // 32bit len + string
RntbdTokenTypeSmallBytes RntbdTokenType = 0x0A // 8bit len + bytes
RntbdTokenTypeBytes RntbdTokenType = 0x0B // 16bit len + bytes
RntbdTokenTypeULongBytes RntbdTokenType = 0x0C // 32bit len + bytes
RntbdTokenTypeFloat RntbdTokenType = 0x0D // 32bit float
RntbdTokenTypeDouble RntbdTokenType = 0x0E // 64bit double
RntbdTokenTypeInvalid RntbdTokenType = 0x0F // Invalid token type
)
func (h RntbdRequestHeader) String() string {
switch h {
case RntbdRequestHeaderResourceId:
return "RntbdRequestHeaderResourceId"
case RntbdRequestHeaderAuthorizationToken:
return headers.Authorization
case RntbdRequestHeaderPayloadPresent:
return "RntbdRequestHeaderPayloadPresent"
case RntbdRequestHeaderDate:
return headers.XDate
case RntbdRequestHeaderPageSize:
return "RntbdRequestHeaderPageSize"
case RntbdRequestHeaderSessionToken:
return "RntbdRequestHeaderSessionToken"
case RntbdRequestHeaderContinuationToken:
return "RntbdRequestHeaderContinuationToken"
case RntbdRequestHeaderIndexingDirective:
return "RntbdRequestHeaderIndexingDirective"
case RntbdRequestHeaderMatch:
return "RntbdRequestHeaderMatch"
case RntbdRequestHeaderPreTriggerInclude:
return "RntbdRequestHeaderPreTriggerInclude"
case RntbdRequestHeaderPostTriggerInclude:
return "RntbdRequestHeaderPostTriggerInclude"
case RntbdRequestHeaderIsFanout:
return "RntbdRequestHeaderIsFanout"
case RntbdRequestHeaderCollectionPartitionIndex:
return "RntbdRequestHeaderCollectionPartitionIndex"
case RntbdRequestHeaderCollectionServiceIndex:
return "RntbdRequestHeaderCollectionServiceIndex"
case RntbdRequestHeaderPreTriggerExclude:
return "RntbdRequestHeaderPreTriggerExclude"
case RntbdRequestHeaderPostTriggerExclude:
return "RntbdRequestHeaderPostTriggerExclude"
case RntbdRequestHeaderConsistencyLevel:
return headers.ConsistencyLevel
case RntbdRequestHeaderEntityId:
return "RntbdRequestHeaderEntityId"
case RntbdRequestHeaderResourceSchemaName:
return "RntbdRequestHeaderResourceSchemaName"
case RntbdRequestHeaderReplicaPath:
return "RntbdRequestHeaderReplicaPath"
case RntbdRequestHeaderResourceTokenExpiry:
return "RntbdRequestHeaderResourceTokenExpiry"
case RntbdRequestHeaderDatabaseName:
return "RntbdRequestHeaderDatabaseName"
case RntbdRequestHeaderCollectionName:
return "RntbdRequestHeaderCollectionName"
case RntbdRequestHeaderDocumentName:
return "RntbdRequestHeaderDocumentName"
case RntbdRequestHeaderAttachmentName:
return "RntbdRequestHeaderAttachmentName"
case RntbdRequestHeaderUserName:
return "RntbdRequestHeaderUserName"
case RntbdRequestHeaderPermissionName:
return "RntbdRequestHeaderPermissionName"
case RntbdRequestHeaderStoredProcedureName:
return "RntbdRequestHeaderStoredProcedureName"
case RntbdRequestHeaderUserDefinedFunctionName:
return "RntbdRequestHeaderUserDefinedFunctionName"
case RntbdRequestHeaderTriggerName:
return "RntbdRequestHeaderTriggerName"
case RntbdRequestHeaderEnableScanInQuery:
return "RntbdRequestHeaderEnableScanInQuery"
case RntbdRequestHeaderEmitVerboseTracesInQuery:
return "RntbdRequestHeaderEmitVerboseTracesInQuery"
case RntbdRequestHeaderConflictName:
return "RntbdRequestHeaderConflictName"
case RntbdRequestHeaderBindReplicaDirective:
return "RntbdRequestHeaderBindReplicaDirective"
case RntbdRequestHeaderPrimaryMasterKey:
return "RntbdRequestHeaderPrimaryMasterKey"
case RntbdRequestHeaderSecondaryMasterKey:
return "RntbdRequestHeaderSecondaryMasterKey"
case RntbdRequestHeaderPrimaryReadonlyKey:
return "RntbdRequestHeaderPrimaryReadonlyKey"
case RntbdRequestHeaderSecondaryReadonlyKey:
return "RntbdRequestHeaderSecondaryReadonlyKey"
case RntbdRequestHeaderProfileRequest:
return "RntbdRequestHeaderProfileRequest"
case RntbdRequestHeaderEnableLowPrecisionOrderBy:
return "RntbdRequestHeaderEnableLowPrecisionOrderBy"
case RntbdRequestHeaderClientVersion:
return "RntbdRequestHeaderClientVersion"
case RntbdRequestHeaderCanCharge:
return "RntbdRequestHeaderCanCharge"
case RntbdRequestHeaderCanThrottle:
return "RntbdRequestHeaderCanThrottle"
case RntbdRequestHeaderPartitionKey:
return "RntbdRequestHeaderPartitionKey"
case RntbdRequestHeaderPartitionKeyRangeId:
return "RntbdRequestHeaderPartitionKeyRangeId"
case RntbdRequestHeaderNotUsed2D:
return "RntbdRequestHeaderNotUsed2D"
case RntbdRequestHeaderNotUsed2E:
return "RntbdRequestHeaderNotUsed2E"
case RntbdRequestHeaderNotUsed2F:
return "RntbdRequestHeaderNotUsed2F"
case RntbdRequestHeaderMigrateCollectionDirective:
return "RntbdRequestHeaderMigrateCollectionDirective"
case RntbdRequestHeaderNotUsed32:
return "RntbdRequestHeaderNotUsed32"
case RntbdRequestHeaderSupportSpatialLegacyCoordinates:
return "RntbdRequestHeaderSupportSpatialLegacyCoordinates"
case RntbdRequestHeaderPartitionCount:
return "RntbdRequestHeaderPartitionCount"
case RntbdRequestHeaderCollectionRid:
return "RntbdRequestHeaderCollectionRid"
case RntbdRequestHeaderPartitionKeyRangeName:
return "RntbdRequestHeaderPartitionKeyRangeName"
case RntbdRequestHeaderSchemaName:
return "RntbdRequestHeaderSchemaName"
case RntbdRequestHeaderFilterBySchemaRid:
return "RntbdRequestHeaderFilterBySchemaRid"
case RntbdRequestHeaderUsePolygonsSmallerThanAHemisphere:
return "RntbdRequestHeaderUsePolygonsSmallerThanAHemisphere"
case RntbdRequestHeaderGatewaySignature:
return "RntbdRequestHeaderGatewaySignature"
case RntbdRequestHeaderEnableLogging:
return "RntbdRequestHeaderEnableLogging"
case RntbdRequestHeaderAIM:
return headers.AIM
case RntbdRequestHeaderPopulateQuotaInfo:
return "RntbdRequestHeaderPopulateQuotaInfo"
case RntbdRequestHeaderDisableRUPerMinuteUsage:
return "RntbdRequestHeaderDisableRUPerMinuteUsage"
case RntbdRequestHeaderPopulateQueryMetrics:
return "RntbdRequestHeaderPopulateQueryMetrics"
case RntbdRequestHeaderResponseContinuationTokenLimitInKb:
return "RntbdRequestHeaderResponseContinuationTokenLimitInKb"
case RntbdRequestHeaderPopulatePartitionStatistics:
return "RntbdRequestHeaderPopulatePartitionStatistics"
case RntbdRequestHeaderRemoteStorageType:
return "RntbdRequestHeaderRemoteStorageType"
case RntbdRequestHeaderCollectionRemoteStorageSecurityIdentifier:
return "RntbdRequestHeaderCollectionRemoteStorageSecurityIdentifier"
case RntbdRequestHeaderIfModifiedSince:
return "RntbdRequestHeaderIfModifiedSince"
case RntbdRequestHeaderPopulateCollectionThroughputInfo:
return "RntbdRequestHeaderPopulateCollectionThroughputInfo"
case RntbdRequestHeaderRemainingTimeInMsOnClientRequest:
return headers.RemainingTimeInMsOnClient
case RntbdRequestHeaderClientRetryAttemptCount:
return headers.ClientRetryAttemptCount
case RntbdRequestHeaderTargetLsn:
return "RntbdRequestHeaderTargetLsn"
case RntbdRequestHeaderTargetGlobalCommittedLsn:
return "RntbdRequestHeaderTargetGlobalCommittedLsn"
case RntbdRequestHeaderTransportRequestID:
return "RntbdRequestHeaderTransportRequestID"
case RntbdRequestHeaderRestoreMetadaFilter:
return "RntbdRequestHeaderRestoreMetadaFilter"
case RntbdRequestHeaderRestoreParams:
return "RntbdRequestHeaderRestoreParams"
case RntbdRequestHeaderShareThroughput:
return "RntbdRequestHeaderShareThroughput"
case RntbdRequestHeaderPartitionResourceFilter:
return "RntbdRequestHeaderPartitionResourceFilter"
case RntbdRequestHeaderIsReadOnlyScript:
return "RntbdRequestHeaderIsReadOnlyScript"
case RntbdRequestHeaderIsAutoScaleRequest:
return "RntbdRequestHeaderIsAutoScaleRequest"
case RntbdRequestHeaderForceQueryScan:
return "RntbdRequestHeaderForceQueryScan"
case RntbdRequestHeaderCanOfferReplaceComplete:
return "RntbdRequestHeaderCanOfferReplaceComplete"
case RntbdRequestHeaderExcludeSystemProperties:
return "RntbdRequestHeaderExcludeSystemProperties"
case RntbdRequestHeaderBinaryId:
return "RntbdRequestHeaderBinaryId"
case RntbdRequestHeaderTimeToLiveInSeconds:
return "RntbdRequestHeaderTimeToLiveInSeconds"
case RntbdRequestHeaderEffectivePartitionKey:
return "RntbdRequestHeaderEffectivePartitionKey"
case RntbdRequestHeaderBinaryPassthroughRequest:
return "RntbdRequestHeaderBinaryPassthroughRequest"
case RntbdRequestHeaderUserDefinedTypeName:
return "RntbdRequestHeaderUserDefinedTypeName"
case RntbdRequestHeaderEnableDynamicRidRangeAllocation:
return "RntbdRequestHeaderEnableDynamicRidRangeAllocation"
case RntbdRequestHeaderEnumerationDirection:
return "RntbdRequestHeaderEnumerationDirection"
case RntbdRequestHeaderStartId:
return "RntbdRequestHeaderStartId"
case RntbdRequestHeaderEndId:
return "RntbdRequestHeaderEndId"
case RntbdRequestHeaderFanoutOperationState:
return "RntbdRequestHeaderFanoutOperationState"
case RntbdRequestHeaderStartEpk:
return "RntbdRequestHeaderStartEpk"
case RntbdRequestHeaderEndEpk:
return "RntbdRequestHeaderEndEpk"
case RntbdRequestHeaderReadFeedKeyType:
return "RntbdRequestHeaderReadFeedKeyType"
case RntbdRequestHeaderContentSerializationFormat:
return "RntbdRequestHeaderContentSerializationFormat"
case RntbdRequestHeaderAllowTentativeWrites:
return "RntbdRequestHeaderAllowTentativeWrites"
case RntbdRequestHeaderIsUserRequest:
return "RntbdRequestHeaderIsUserRequest"
case RntbdRequestHeaderSharedOfferThroughput:
return "RntbdRequestHeaderSharedOfferThroughput"
case RntbdRequestHeaderSDKSupportedCapabilities:
return headers.SupportedCapabilities
}
return fmt.Sprintf("RntbdRequestHeader(%d)", h)
}
func (h RntbdContextHeader) String() string {
switch h {
case RntbdContextHeaderProtocolVersion:
return "RntbdContextHeaderProtocolVersion"
case RntbdContextHeaderClientVersion:
return "RntbdContextHeaderClientVersion"
case RntbdContextHeaderServerAgent:
return "RntbdContextHeaderServerAgent"
case RntbdContextHeaderServerVersion:
return "RntbdContextHeaderServerVersion"
case RntbdContextHeaderIdleTimeoutInSeconds:
return "RntbdContextHeaderIdleTimeoutInSeconds"
case RntbdContextHeaderUnauthenticatedTimeoutInSeconds:
return "RntbdContextHeaderUnauthenticatedTimeoutInSeconds"
}
return fmt.Sprintf("RntbdContextHeader(%d)", h)
}
func (h RntbdResponseHeaderType) String() string {
switch h {
case RntbdResponseHeaderPayloadPresent:
return "PayloadPresent"
case RntbdResponseHeaderLastStateChangeDateTime:
return "LastStateChangeDateTime"
case RntbdResponseHeaderContinuationToken:
return "ContinuationToken"
case RntbdResponseHeaderETag:
return "ETag"
case RntbdResponseHeaderReadsPerformed:
return "ReadsPerformed"
case RntbdResponseHeaderWritesPerformed:
return "WritesPerformed"
case RntbdResponseHeaderQueriesPerformed:
return "QueriesPerformed"
case RntbdResponseHeaderIndexTermsGenerated:
return "IndexTermsGenerated"
case RntbdResponseHeaderScriptsExecuted:
return "ScriptsExecuted"
case RntbdResponseHeaderRetryAfterMilliseconds:
return "RetryAfterMilliseconds"
case RntbdResponseHeaderIndexingDirective:
return "IndexingDirective"
case RntbdResponseHeaderStorageMaxResoureQuota:
return "StorageMaxResoureQuota"
case RntbdResponseHeaderStorageResourceQuotaUsage:
return "StorageResourceQuotaUsage"
case RntbdResponseHeaderSchemaVersion:
return "SchemaVersion"
case RntbdResponseHeaderCollectionPartitionIndex:
return "CollectionPartitionIndex"
case RntbdResponseHeaderCollectionServiceIndex:
return "CollectionServiceIndex"
case RntbdResponseHeaderLSN:
return "LSN"
case RntbdResponseHeaderItemCount:
return "ItemCount"
case RntbdResponseHeaderRequestCharge:
return "RequestCharge"
case RntbdResponseHeaderOwnerFullName:
return "OwnerFullName"
case RntbdResponseHeaderOwnerId:
return "OwnerId"
case RntbdResponseHeaderDatabaseAccountId:
return "DatabaseAccountId"
case RntbdResponseHeaderQuorumAckedLSN:
return "QuorumAckedLSN"
case RntbdResponseHeaderRequestValidationFailure:
return "RequestValidationFailure"
case RntbdResponseHeaderSubStatus:
return "SubStatus"
case RntbdResponseHeaderCollectionUpdateProgress:
return "CollectionUpdateProgress"
case RntbdResponseHeaderCurrentWriteQuorum:
return "CurrentWriteQuorum"
case RntbdResponseHeaderCurrentReplicaSetSize:
return "CurrentReplicaSetSize"
case RntbdResponseHeaderCollectionLazyIndexProgress:
return "CollectionLazyIndexProgress"
case RntbdResponseHeaderPartitionKeyRangeId:
return "PartitionKeyRangeId"
case RntbdResponseHeaderLogResults:
return "LogResults"
case RntbdResponseHeaderXPRole:
return "XPRole"
case RntbdResponseHeaderIsRUPerMinuteUsed:
return "IsRUPerMinuteUsed"
case RntbdResponseHeaderQueryMetrics:
return "QueryMetrics"
case RntbdResponseHeaderGlobalCommittedLSN:
return "GlobalCommittedLSN"
case RntbdResponseHeaderNumberOfReadRegions:
return "NumberOfReadRegions"
case RntbdResponseHeaderOfferReplacePending:
return "OfferReplacePending"
case RntbdResponseHeaderItemLSN:
return "ItemLSN"
case RntbdResponseHeaderRestoreState:
return "RestoreState"
case RntbdResponseHeaderCollectionSecurityIdentifier:
return "CollectionSecurityIdentifier"
case RntbdResponseHeaderTransportRequestID:
return "TransportRequestID"
case RntbdResponseHeaderShareThroughput:
return "ShareThroughput"
case RntbdResponseHeaderDisableRntbdChannel:
return "DisableRntbdChannel"
case RntbdResponseHeaderServerDateTimeUtc:
return "ServerDateTimeUtc"
case RntbdResponseHeaderLocalLSN:
return "LocalLSN"
case RntbdResponseHeaderQuorumAckedLocalLSN:
return "QuorumAckedLocalLSN"
case RntbdResponseHeaderItemLocalLSN:
return "ItemLocalLSN"
case RntbdResponseHeaderHasTentativeWrites:
return "HasTentativeWrites"
case RntbdResponseHeaderSessionToken:
return "SessionToken"
}
return fmt.Sprintf("RntbdResponseHeaderType(%d)", h)
}
func (r RntbdResourceType) String() string {
switch r {
case RntbdResourceTypeConnection:
return "Connection"
case RntbdResourceTypeDatabase:
return "Database"
case RntbdResourceTypeCollection:
return "Collection"
case RntbdResourceTypeDocument:
return "Document"
case RntbdResourceTypeAttachment:
return "Attachment"
case RntbdResourceTypeUser:
return "User"
case RntbdResourceTypePermission:
return "Permission"
case RntbdResourceTypeStoredProcedure:
return "StoredProcedure"
case RntbdResourceTypeConflict:
return "Conflict"
case RntbdResourceTypeTrigger:
return "Trigger"
case RntbdResourceTypeUserDefinedFunction:
return "UserDefinedFunction"
case RntbdResourceTypeModule:
return "Module"
case RntbdResourceTypeReplica:
return "Replica"
case RntbdResourceTypeModuleCommand:
return "ModuleCommand"
case RntbdResourceTypeRecord:
return "Record"
case RntbdResourceTypeOffer:
return "Offer"
case RntbdResourceTypePartitionSetInformation:
return "PartitionSetInformation"
case RntbdResourceTypeXPReplicatorAddress:
return "XPReplicatorAddress"
case RntbdResourceTypeMasterPartition:
return "MasterPartition"
case RntbdResourceTypeServerPartition:
return "ServerPartition"
case RntbdResourceTypeDatabaseAccount:
return "DatabaseAccount"
case RntbdResourceTypeTopology:
return "Topology"
case RntbdResourceTypePartitionKeyRange:
return "PartitionKeyRange"
case RntbdResourceTypeSchema:
return "Schema"
case RntbdResourceTypeBatchApply:
return "BatchApply"
case RntbdResourceTypeRestoreMetadata:
return "RestoreMetadata"
case RntbdResourceTypeComputeGatewayCharges:
return "ComputeGatewayCharges"
case RntbdResourceTypeRidRange:
return "RidRange"
case RntbdResourceTypeUserDefinedType:
return "UserDefinedType"
}
return fmt.Sprintf("RntbdResourceType(%d)", r)
}
func (o RntbdOperationType) String() string {
switch o {
case RntbdOperationTypeConnection:
return "Connection"
case RntbdOperationTypeCreate:
return "Create"
case RntbdOperationTypeUpdate:
return "Update"
case RntbdOperationTypeRead:
return "Read"
case RntbdOperationTypeReadFeed:
return "ReadFeed"
case RntbdOperationTypeDelete:
return "Delete"
case RntbdOperationTypeReplace:
return "Replace"
case RntbdOperationTypeExecuteJavaScript:
return "ExecuteJavaScript"
case RntbdOperationTypeSQLQuery:
return "SQLQuery"
case RntbdOperationTypePause:
return "Pause"
case RntbdOperationTypeResume:
return "Resume"
case RntbdOperationTypeStop:
return "Stop"
case RntbdOperationTypeRecycle:
return "Recycle"
case RntbdOperationTypeCrash:
return "Crash"
case RntbdOperationTypeQuery:
return "Query"
case RntbdOperationTypeForceConfigRefresh:
return "ForceConfigRefresh"
case RntbdOperationTypeHead:
return "Head"
case RntbdOperationTypeHeadFeed:
return "HeadFeed"
case RntbdOperationTypeUpsert:
return "Upsert"
case RntbdOperationTypeRecreate:
return "Recreate"
case RntbdOperationTypeThrottle:
return "Throttle"
case RntbdOperationTypeGetSplitPoint:
return "GetSplitPoint"
case RntbdOperationTypePreCreateValidation:
return "PreCreateValidation"
case RntbdOperationTypeBatchApply:
return "BatchApply"
case RntbdOperationTypeAbortSplit:
return "AbortSplit"
case RntbdOperationTypeCompleteSplit:
return "CompleteSplit"
case RntbdOperationTypeOfferUpdateOperation:
return "OfferUpdateOperation"
case RntbdOperationTypeOfferPreGrowValidation:
return "OfferPreGrowValidation"
case RntbdOperationTypeBatchReportThroughputUtilization:
return "BatchReportThroughputUtilization"
case RntbdOperationTypeCompletePartitionMigration:
return "CompletePartitionMigration"
case RntbdOperationTypeAbortPartitionMigration:
return "AbortPartitionMigration"
case RntbdOperationTypePreReplaceValidation:
return "PreReplaceValidation"
case RntbdOperationTypeAddComputeGatewayRequestCharges:
return "AddComputeGatewayRequestCharges"
case RntbdOperationTypeMigratePartition:
return "MigratePartition"
}
return fmt.Sprintf("RntbdOperationType(%d)", o)
}
+128
View File
@@ -0,0 +1,128 @@
package rntbd
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"github.com/pikami/cosmium/api/headers"
)
func (f *RntbdFrame) ToHttpRequest() *http.Request {
req := &http.Request{
Method: operationTypeToHttpMethod(f.OperationType),
URL: &url.URL{Path: frameToPath(f)},
Body: io.NopCloser(bytes.NewReader(f.Payload)),
Header: http.Header{},
}
switch f.OperationType {
case RntbdOperationTypeQuery, RntbdOperationTypeSQLQuery:
req.Header.Set(headers.Query, "true")
case RntbdOperationTypeUpsert:
req.Header.Set(headers.IsUpsert, "true")
}
if ifMatch, ok := f.RequestHeaders[RntbdRequestHeaderMatch]; ok {
if ifMatchString, ok := ifMatch.(string); ok {
req.Header.Set(headers.IfMatch, ifMatchString)
}
}
if continuationToken, ok := f.RequestHeaders[RntbdRequestHeaderContinuationToken]; ok {
if continuationTokenString, ok := continuationToken.(string); ok {
req.Header.Set(headers.ContinuationToken, continuationTokenString)
}
}
if maxItemCount, ok := f.RequestHeaders[RntbdRequestHeaderPageSize]; ok {
if maxItemCountString, ok := maxItemCount.(uint64); ok {
req.Header.Set(headers.MaxItemCount, fmt.Sprintf("%d", maxItemCountString))
}
}
return req
}
func ToRntbdResponseFrame(responseWriter *httptest.ResponseRecorder) *RntbdResponseFrameBuilder {
builder := &RntbdResponseFrameBuilder{}
builder.SetStatusCode(uint16(responseWriter.Code))
if responseWriter.Header().Get(headers.ETag) != "" {
builder.AddHeader(uint16(RntbdResponseHeaderETag), RntbdTokenTypeString, responseWriter.Header().Get(headers.ETag))
}
if responseWriter.Header().Get(headers.ContinuationToken) != "" {
builder.AddHeader(uint16(RntbdResponseHeaderContinuationToken), RntbdTokenTypeString, responseWriter.Header().Get(headers.ContinuationToken))
}
if responseWriter.Header().Get(headers.ItemCount) != "" {
itemCount, err := strconv.ParseUint(responseWriter.Header().Get(headers.ItemCount), 10, 32)
if err != nil {
panic(err)
}
builder.AddHeader(uint16(RntbdResponseHeaderItemCount), RntbdTokenTypeULong, uint32(itemCount))
}
if responseWriter.Body.Len() > 0 {
builder.AddHeader(uint16(RntbdResponseHeaderPayloadPresent), RntbdTokenTypeByte, []byte{1})
builder.AddPayload(responseWriter.Body.Bytes())
} else {
builder.AddHeader(uint16(RntbdResponseHeaderPayloadPresent), RntbdTokenTypeByte, []byte{0})
}
return builder
}
func operationTypeToHttpMethod(operationType RntbdOperationType) string {
switch operationType {
case RntbdOperationTypeRead,
RntbdOperationTypeReadFeed:
return http.MethodGet
case RntbdOperationTypeCreate,
RntbdOperationTypeUpsert,
RntbdOperationTypeQuery,
RntbdOperationTypeSQLQuery:
return http.MethodPost
case RntbdOperationTypeUpdate,
RntbdOperationTypeReplace:
return http.MethodPut
case RntbdOperationTypeDelete:
return http.MethodDelete
}
panic(fmt.Sprintf("Unknown operation type: %d", operationType))
}
func frameToPath(frame *RntbdFrame) string {
databaseName, databaseOk := frame.RequestHeaders[RntbdRequestHeaderDatabaseName]
collectionName, collectionOk := frame.RequestHeaders[RntbdRequestHeaderCollectionName]
documentName, documentOk := frame.RequestHeaders[RntbdRequestHeaderDocumentName]
urlPath := ""
if databaseOk {
urlPath += fmt.Sprintf("/dbs/%s", databaseName)
} else if frame.ResourceType == RntbdResourceTypeDatabase {
urlPath += "/dbs"
}
if collectionOk {
urlPath += fmt.Sprintf("/colls/%s", collectionName)
} else if frame.ResourceType == RntbdResourceTypeCollection {
urlPath += "/colls"
}
if documentOk {
urlPath += fmt.Sprintf("/docs/%s", documentName)
} else if frame.ResourceType == RntbdResourceTypeDocument {
urlPath += "/docs"
}
return urlPath
}
+212
View File
@@ -0,0 +1,212 @@
package rntbd
import (
"bufio"
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"os"
"github.com/pikami/cosmium/internal/logger"
)
type RntbdFrame struct {
ResourceType RntbdResourceType
OperationType RntbdOperationType
ActivityId []byte
RequestHeaders map[RntbdRequestHeader]any
ResponseHeaders map[RntbdResponseHeaderType]any
ContextHeaders map[RntbdContextHeader]any
Payload []byte
}
func ReadFrame(reader *bufio.Reader) (*RntbdFrame, error) {
sizeBytes := readBytes(reader, 4)
size := binary.LittleEndian.Uint32(sizeBytes)
payload := readBytes(reader, int(size)-4)
frame, err := parseFrame_Int(payload, false)
if err != nil {
return nil, err
}
if payloadPresent, ok := frame.RequestHeaders[RntbdRequestHeaderPayloadPresent]; ok && payloadPresent.([]byte)[0] == 1 {
payloadSize := binary.LittleEndian.Uint32(readBytes(reader, 4))
payload := readBytes(reader, int(payloadSize))
frame.Payload = payload
}
if payloadPresent, ok := frame.ResponseHeaders[RntbdResponseHeaderPayloadPresent]; ok && payloadPresent.([]byte)[0] == 1 {
payloadSize := binary.LittleEndian.Uint32(readBytes(reader, 4))
payload := readBytes(reader, int(payloadSize))
frame.Payload = payload
}
return frame, nil
}
func ParseFrame(data []byte, isResponse bool) (*RntbdFrame, error) {
if len(data) < 4 {
return nil, fmt.Errorf("data too short")
}
reader := bufio.NewReader(bytes.NewReader(data))
sizeBytes := readBytes(reader, 4)
size := binary.LittleEndian.Uint32(sizeBytes)
payload := readBytes(reader, int(size)-4)
frame, err := parseFrame_Int(payload, isResponse)
if err != nil {
return nil, err
}
if payloadPresent, ok := frame.RequestHeaders[RntbdRequestHeaderPayloadPresent]; ok && payloadPresent.([]byte)[0] == 1 {
payloadSize := binary.LittleEndian.Uint32(readBytes(reader, 4))
payload := readBytes(reader, int(payloadSize))
frame.Payload = payload
}
if payloadPresent, ok := frame.ResponseHeaders[RntbdResponseHeaderPayloadPresent]; ok && payloadPresent.([]byte)[0] == 1 {
payloadSize := binary.LittleEndian.Uint32(readBytes(reader, 4))
payload := readBytes(reader, int(payloadSize))
frame.Payload = payload
}
leftOverBytes, err := io.ReadAll(reader)
if err != nil {
logger.ErrorLn("Error reading leftOverBytes:", err)
}
if len(leftOverBytes) > 0 {
logger.ErrorLn("Left over bytes:", hex.EncodeToString(leftOverBytes))
}
return frame, nil
}
func parseFrame_Int(data []byte, isResponse bool) (*RntbdFrame, error) {
payloadReader := bufio.NewReader(bytes.NewReader(data))
resourceTypeBytes := readBytes(payloadReader, 2)
resourceType := binary.LittleEndian.Uint16(resourceTypeBytes)
operationTypeBytes := readBytes(payloadReader, 2)
operationType := RntbdOperationType(binary.LittleEndian.Uint16(operationTypeBytes))
activityIdBytes := readBytes(payloadReader, 16)
requestHeaders := make(map[RntbdRequestHeader]any)
responseHeaders := make(map[RntbdResponseHeaderType]any)
contextHeaders := make(map[RntbdContextHeader]any)
for {
if _, err := payloadReader.Peek(1); err != nil {
break
}
headerIdBytes := readBytes(payloadReader, 2)
headerId := binary.LittleEndian.Uint16(headerIdBytes)
token, err := parseRntbdToken(payloadReader)
if err != nil {
return nil, err
}
if resourceType == uint16(RntbdResourceTypeConnection) {
contextHeaders[RntbdContextHeader(headerId)] = token
} else if isResponse {
responseHeaders[RntbdResponseHeaderType(headerId)] = token
} else {
requestHeaders[RntbdRequestHeader(headerId)] = token
}
}
return &RntbdFrame{
ResourceType: RntbdResourceType(resourceType),
OperationType: RntbdOperationType(operationType),
ActivityId: activityIdBytes,
RequestHeaders: requestHeaders,
ResponseHeaders: responseHeaders,
ContextHeaders: contextHeaders,
}, nil
}
func parseRntbdToken(reader *bufio.Reader) (any, error) {
tokenTypeBytes := readBytes(reader, 1)
tokenType := RntbdTokenType(tokenTypeBytes[0])
switch tokenType {
case RntbdTokenTypeByte:
token := readBytes(reader, 1)
return token, nil
case RntbdTokenTypeUShort:
token := binary.LittleEndian.Uint16(readBytes(reader, 2))
return token, nil
case RntbdTokenTypeULong:
token := binary.LittleEndian.Uint32(readBytes(reader, 4))
return token, nil
case RntbdTokenTypeLong:
token := int32(binary.LittleEndian.Uint32(readBytes(reader, 4)))
return token, nil
case RntbdTokenTypeULongLong:
token := binary.LittleEndian.Uint64(readBytes(reader, 8))
return token, nil
case RntbdTokenTypeLongLong:
token := int64(binary.LittleEndian.Uint64(readBytes(reader, 8)))
return token, nil
case RntbdTokenTypeGuid:
token := readBytes(reader, 16)
return token, nil
case RntbdTokenTypeSmallString:
lengthBytes := readBytes(reader, 1)
length := uint8(lengthBytes[0])
token := readBytes(reader, int(length))
return string(token), nil
case RntbdTokenTypeString:
length := binary.LittleEndian.Uint16(readBytes(reader, 2))
token := readBytes(reader, int(length))
return string(token), nil
case RntbdTokenTypeULongString:
length := binary.LittleEndian.Uint32(readBytes(reader, 4))
token := readBytes(reader, int(length))
return string(token), nil
case RntbdTokenTypeSmallBytes:
lengthBytes := readBytes(reader, 1)
length := uint8(lengthBytes[0])
token := readBytes(reader, int(length))
return token, nil
case RntbdTokenTypeBytes:
length := binary.LittleEndian.Uint16(readBytes(reader, 2))
token := readBytes(reader, int(length))
return token, nil
case RntbdTokenTypeULongBytes:
length := binary.LittleEndian.Uint32(readBytes(reader, 4))
token := readBytes(reader, int(length))
return token, nil
case RntbdTokenTypeFloat:
// I can't be bothered to implement this, let's just return a byte array
token := readBytes(reader, 4)
return token, nil
case RntbdTokenTypeDouble:
// I can't be bothered to implement this, let's just return a byte array
token := readBytes(reader, 8)
return token, nil
case RntbdTokenTypeInvalid:
return nil, fmt.Errorf("invalid token type")
}
return nil, fmt.Errorf("invalid token type")
}
func readBytes(reader *bufio.Reader, n int) []byte {
bytes := make([]byte, n)
_, err := io.ReadFull(reader, bytes)
if err != nil {
logger.ErrorLn("Error reading bytes:", err)
os.Exit(0)
}
return bytes
}
+120
View File
@@ -0,0 +1,120 @@
package rntbd
import (
"bufio"
"crypto/tls"
"fmt"
"net"
"net/http/httptest"
"github.com/pikami/cosmium/api"
"github.com/pikami/cosmium/internal/logger"
tlsprovider "github.com/pikami/cosmium/internal/tls_provider"
)
type RntbdServer struct {
port int
listener net.Listener
apiServer *api.ApiServer
}
func NewRntbdServer(port int, apiServer *api.ApiServer) *RntbdServer {
return &RntbdServer{port: port, apiServer: apiServer}
}
func (s *RntbdServer) Start() error {
tlsConfig := tlsprovider.GetDefaultTlsConfig()
listener, err := tls.Listen("tcp", fmt.Sprintf(":%d", s.port), tlsConfig)
if err != nil {
return fmt.Errorf("failed to listen on port %d: %w", s.port, err)
}
s.listener = listener
go func() {
for {
conn, err := s.listener.Accept()
if err != nil {
logger.ErrorLn("Failed to accept connection:", err)
continue
}
go s.handleConnection(conn)
}
}()
return nil
}
func (s *RntbdServer) Stop() error {
return s.listener.Close()
}
func (s *RntbdServer) handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
writer := bufio.NewWriter(conn)
for {
_, err := reader.Peek(4)
if err != nil {
return
}
frame, err := ReadFrame(reader)
if err != nil {
logger.ErrorLn("Failed to read frame:", err)
continue
}
if frame.ResourceType == RntbdResourceTypeConnection {
responseFrame := buildContextFrame(frame)
_, err := writer.Write(responseFrame)
writer.Flush()
if err != nil {
logger.ErrorLn("Failed to write response frame:", err)
continue
}
continue
} else if frame.ResourceType == RntbdResourceTypeDatabase ||
frame.ResourceType == RntbdResourceTypeCollection ||
frame.ResourceType == RntbdResourceTypeDocument {
responseFrameBytes := s.passToApiServer(frame)
_, err := writer.Write(responseFrameBytes)
writer.Flush()
if err != nil {
logger.ErrorLn("Failed to write response frame:", err)
continue
}
continue
} else {
logger.Errorf("Received Unhandled RNTBD request from: %s with resource type: %s\n", conn.RemoteAddr(), frame.ResourceType.String())
}
}
}
func (s *RntbdServer) passToApiServer(frame *RntbdFrame) []byte {
req := frame.ToHttpRequest()
responseWriter := httptest.NewRecorder()
s.apiServer.GetRouter().ServeHTTP(responseWriter, req)
responseFrameBuilder := ToRntbdResponseFrame(responseWriter)
responseFrameBuilder.SetActivityId(frame.ActivityId)
if transportRequestId, ok := frame.RequestHeaders[RntbdRequestHeaderTransportRequestID]; ok {
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderTransportRequestID), RntbdTokenTypeULong, transportRequestId)
}
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderItemLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderLocalLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderGlobalCommittedLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderItemLocalLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderQuorumAckedLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderQuorumAckedLocalLSN), RntbdTokenTypeLongLong, int64(420))
responseFrameBuilder.AddHeader(uint16(RntbdResponseHeaderCurrentReplicaSetSize), RntbdTokenTypeULong, uint32(1))
responseFrame := responseFrameBuilder.Build()
responseFrameBytes := responseFrame.ToBytes()
return responseFrameBytes
}
+1
View File
@@ -107,6 +107,7 @@ const (
FunctionCallContains FunctionCallType = "Contains" FunctionCallContains FunctionCallType = "Contains"
FunctionCallEndsWith FunctionCallType = "EndsWith" FunctionCallEndsWith FunctionCallType = "EndsWith"
FunctionCallStartsWith FunctionCallType = "StartsWith" FunctionCallStartsWith FunctionCallType = "StartsWith"
FunctionCallRegexMatch FunctionCallType = "RegexMatch"
FunctionCallIndexOf FunctionCallType = "IndexOf" FunctionCallIndexOf FunctionCallType = "IndexOf"
FunctionCallToString FunctionCallType = "ToString" FunctionCallToString FunctionCallType = "ToString"
FunctionCallUpper FunctionCallType = "Upper" FunctionCallUpper FunctionCallType = "Upper"
+28
View File
@@ -143,6 +143,34 @@ func Test_Parse(t *testing.T) {
) )
}) })
t.Run("Should parse NOT with parentheses", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id FROM c WHERE NOT (c.id IN ("123", "456"))`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}, Type: parsers.SelectItemTypeField},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall,
Invert: true,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallIn,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
},
},
},
},
)
})
t.Run("Should parse IN function with function call", func(t *testing.T) { t.Run("Should parse IN function with function call", func(t *testing.T) {
testQueryParse( testQueryParse(
t, t,
+1845 -1588
View File
File diff suppressed because it is too large Load Diff
+25 -6
View File
@@ -387,7 +387,11 @@ AsClause <- (ws As)? ws !ExcludedKeywords alias:Identifier {
return alias, nil return alias, nil
} }
ExcludedKeywords <- Select / Top / As / From / In / Join / Exists / Where / And / Or / Not / GroupBy / OrderBy / Offset ExcludedKeywords <- (
Select / Top / As / From / In / Join / Exists / Where / And / Or / Not / Offset /
("GROUP"i wss "BY"i) /
("ORDER"i wss "BY"i)
) ![a-zA-Z0-9_]
DotFieldAccess <- "." id:Identifier { DotFieldAccess <- "." id:Identifier {
return id, nil return id, nil
@@ -425,7 +429,15 @@ MulDivExpression <- left:SelectItemWithParentheses operations:(ws op:MultiplyOrD
return makeMathExpression(left, operations) return makeMathExpression(left, operations)
} }
SelectItemWithParentheses <- "(" ws ex:OrExpression ws ")" { return ex, nil } SelectItemWithParentheses <- inv:(Not ws)? "(" ws ex:OrExpression ws ")" {
if inv != nil {
if ex1, ok := ex.(parsers.SelectItem); ok {
ex1.Invert = true
return ex1, nil
}
}
return ex, nil
}
/ inv:(Not ws)? ex:SelectItem { / inv:(Not ws)? ex:SelectItem {
if inv != nil { if inv != nil {
ex1 := ex.(parsers.SelectItem) ex1 := ex.(parsers.SelectItem)
@@ -501,6 +513,8 @@ IntegerLiteral <- number:Integer {
} }
StringLiteral <- "\"" chars:StringCharacter* "\"" { StringLiteral <- "\"" chars:StringCharacter* "\"" {
return parsers.Constant{Type: parsers.ConstantTypeString,Value: joinStrings(chars.([]interface{}))}, nil return parsers.Constant{Type: parsers.ConstantTypeString,Value: joinStrings(chars.([]interface{}))}, nil
} / "'" chars:SingleQuotedStringCharacter* "'" {
return parsers.Constant{Type: parsers.ConstantTypeString,Value: joinStrings(chars.([]interface{}))}, nil
} }
FloatLiteral <- [0-9]+"."[0-9]+ { FloatLiteral <- [0-9]+"."[0-9]+ {
floatValue, _ := strconv.ParseFloat(string(c.text), 64) floatValue, _ := strconv.ParseFloat(string(c.text), 64)
@@ -673,6 +687,8 @@ ThreeArgumentStringFunctionExpression <- function:ThreeArgumentStringFunction ws
functionType = parsers.FunctionCallEndsWith functionType = parsers.FunctionCallEndsWith
case "STARTSWITH": case "STARTSWITH":
functionType = parsers.FunctionCallStartsWith functionType = parsers.FunctionCallStartsWith
case "REGEXMATCH":
functionType = parsers.FunctionCallRegexMatch
case "INDEX_OF": case "INDEX_OF":
functionType = parsers.FunctionCallIndexOf functionType = parsers.FunctionCallIndexOf
} }
@@ -680,7 +696,7 @@ ThreeArgumentStringFunctionExpression <- function:ThreeArgumentStringFunction ws
return createFunctionCall(functionType, []interface{}{ex1, ex2, ignoreCase}) return createFunctionCall(functionType, []interface{}{ex1, ex2, ignoreCase})
} }
ThreeArgumentStringFunction <- ("CONTAINS"i / "ENDSWITH"i / "STARTSWITH"i / "INDEX_OF"i) { ThreeArgumentStringFunction <- ("CONTAINS"i / "ENDSWITH"i / "STARTSWITH"i / "REGEXMATCH"i / "INDEX_OF"i) {
return string(c.text), nil return string(c.text), nil
} }
@@ -859,11 +875,14 @@ Integer <- [0-9]+ {
StringCharacter <- !('"' / "\\") . { return string(c.text), nil } StringCharacter <- !('"' / "\\") . { return string(c.text), nil }
/ "\\" seq:EscapeSequenceCharacter { return seq, nil } / "\\" seq:EscapeSequenceCharacter { return seq, nil }
SingleQuotedStringCharacter <- !("'" / "\\") . { return string(c.text), nil }
/ "\\" seq:EscapeSequenceCharacter { return seq, nil }
EscapeSequenceCharacter <- char:EscapeCharacter EscapeSequenceCharacter <- char:EscapeCharacter
EscapeCharacter <- "'" EscapeCharacter <- "'" { return "'", nil }
/ '"' / '"' { return "\"", nil }
/ "\\" / "\\" { return "\\", nil }
/ "b" { return "\b", nil } / "b" { return "\b", nil }
/ "f" { return "\f", nil } / "f" { return "\f", nil }
/ "n" { return "\n", nil } / "n" { return "\n", nil }
+20
View File
@@ -159,6 +159,26 @@ func Test_Parse_Select(t *testing.T) {
) )
}) })
t.Run("Should parse SELECT with orderByItems alias", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id, c["pk"] AS orderByItems FROM c ORDER BY c.id`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{Alias: "orderByItems", Path: []string{"c", "pk"}},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
OrderExpressions: []parsers.OrderExpression{
{
SelectItem: testutils.SelectItem_Path("c", "id"),
Direction: parsers.OrderDirectionAsc,
},
},
},
)
})
t.Run("Should parse SELECT object", func(t *testing.T) { t.Run("Should parse SELECT object", func(t *testing.T) {
testQueryParse( testQueryParse(
t, t,
+26
View File
@@ -168,6 +168,32 @@ func Test_Execute_StringFunctions(t *testing.T) {
) )
}) })
t.Run("Should parse function REGEXMATCH()", func(t *testing.T) {
testQueryParse(
t,
`SELECT REGEXMATCH(c.id, "aB c", "ix") FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallRegexMatch,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
testutils.SelectItem_Constant_String("aB c"),
testutils.SelectItem_Constant_String("ix"),
},
},
},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse function INDEX_OF()", func(t *testing.T) { t.Run("Should parse function INDEX_OF()", func(t *testing.T) {
testQueryParse( testQueryParse(
t, t,
+26
View File
@@ -149,6 +149,32 @@ func Test_Parse_Were(t *testing.T) {
) )
}) })
t.Run("Should correctly parse single-quoted string literals", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id FROM c WHERE c.id = '12345' AND c.string = 'it\'s'`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}}},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeAnd,
Expressions: []interface{}{
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: testutils.SelectItem_Constant_String("12345"),
},
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "string"}},
Right: testutils.SelectItem_Constant_String("it's"),
},
},
},
},
)
})
t.Run("Should correctly parse NOT conditions", func(t *testing.T) { t.Run("Should correctly parse NOT conditions", func(t *testing.T) {
testQueryParse( testQueryParse(
t, t,
@@ -25,7 +25,7 @@ func (r rowContext) array_Contains(arguments []interface{}) bool {
exprToSearch := r.resolveSelectItem(arguments[1].(parsers.SelectItem)) exprToSearch := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
partialSearch := false partialSearch := false
if len(arguments) > 2 { if len(arguments) > 2 && arguments[2] != nil {
boolExpr := r.resolveSelectItem(arguments[2].(parsers.SelectItem)) boolExpr := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if boolValue, ok := boolExpr.(bool); ok { if boolValue, ok := boolExpr.(bool); ok {
partialSearch = boolValue partialSearch = boolValue
@@ -162,6 +162,8 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
return r.strings_EndsWith(functionCall.Arguments) return r.strings_EndsWith(functionCall.Arguments)
case parsers.FunctionCallStartsWith: case parsers.FunctionCallStartsWith:
return r.strings_StartsWith(functionCall.Arguments) return r.strings_StartsWith(functionCall.Arguments)
case parsers.FunctionCallRegexMatch:
return r.strings_RegexMatch(functionCall.Arguments)
case parsers.FunctionCallConcat: case parsers.FunctionCallConcat:
return r.strings_Concat(functionCall.Arguments) return r.strings_Concat(functionCall.Arguments)
case parsers.FunctionCallIndexOf: case parsers.FunctionCallIndexOf:
@@ -5,18 +5,46 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func ExecuteQuery(query parsers.SelectStmt, documents rowTypeIterator) []RowType { type ExecuteQueryResult struct {
Rows []RowType
HasMorePages bool
}
func ExecuteQuery(
query parsers.SelectStmt,
documents rowTypeIterator,
offset int,
limit int,
) ExecuteQueryResult {
resultIter := executeQuery(query, &rowTypeToRowContextIterator{documents: documents, query: query}) resultIter := executeQuery(query, &rowTypeToRowContextIterator{documents: documents, query: query})
result := make([]RowType, 0)
for { result := &ExecuteQueryResult{
Rows: make([]RowType, 0),
HasMorePages: false,
}
for i := 0; i < offset; i++ {
_, status := resultIter.Next()
if status != datastore.StatusOk {
break
}
}
for i := 0; i < limit; i++ {
row, status := resultIter.Next() row, status := resultIter.Next()
if status != datastore.StatusOk { if status != datastore.StatusOk {
break break
} }
result = append(result, row) result.Rows = append(result.Rows, row)
} }
return result
_, status := resultIter.Next()
if status == datastore.StatusOk {
result.HasMorePages = true
}
return *result
} }
func executeQuery(query parsers.SelectStmt, documents rowIterator) rowTypeIterator { func executeQuery(query parsers.SelectStmt, documents rowIterator) rowTypeIterator {
+3 -3
View File
@@ -38,10 +38,10 @@ func testQueryExecute(
expectedData []memoryexecutor.RowType, expectedData []memoryexecutor.RowType,
) { ) {
iter := NewTestDocumentIterator(data) iter := NewTestDocumentIterator(data)
result := memoryexecutor.ExecuteQuery(query, iter) result := memoryexecutor.ExecuteQuery(query, iter, 0, 1000)
if !reflect.DeepEqual(result, expectedData) { if !reflect.DeepEqual(result.Rows, expectedData) {
t.Errorf("execution result does not match expected data.\nExpected: %+v\nGot: %+v", expectedData, result) t.Errorf("execution result does not match expected data.\nExpected: %+v\nGot: %+v", expectedData, result.Rows)
} }
} }
@@ -2,6 +2,7 @@ package memoryexecutor
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/pikami/cosmium/internal/logger" "github.com/pikami/cosmium/internal/logger"
@@ -75,6 +76,46 @@ func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
return strings.HasPrefix(str1, str2) return strings.HasPrefix(str1, str2)
} }
func (r rowContext) strings_RegexMatch(arguments []interface{}) bool {
value, valueOk := r.parseString(arguments[0])
pattern, patternOk := r.parseString(arguments[1])
if !valueOk || !patternOk {
return false
}
modifiers, ok := r.getStringFlag(arguments)
if !ok {
return false
}
regexPattern := pattern
if strings.Contains(modifiers, "x") {
regexPattern = stripRegexIgnoredWhitespace(regexPattern)
}
var flags strings.Builder
if strings.Contains(modifiers, "i") {
flags.WriteByte('i')
}
if strings.Contains(modifiers, "m") {
flags.WriteByte('m')
}
if strings.Contains(modifiers, "s") {
flags.WriteByte('s')
}
if flags.Len() > 0 {
regexPattern = "(?" + flags.String() + ")" + regexPattern
}
matched, err := regexp.MatchString(regexPattern, value)
if err != nil {
logger.Errorf("strings_RegexMatch - invalid pattern %q: %v", pattern, err)
return false
}
return matched
}
func (r rowContext) strings_Concat(arguments []interface{}) string { func (r rowContext) strings_Concat(arguments []interface{}) string {
result := "" result := ""
@@ -318,6 +359,20 @@ func (r rowContext) getBoolFlag(arguments []interface{}) bool {
return ignoreCase return ignoreCase
} }
func (r rowContext) getStringFlag(arguments []interface{}) (string, bool) {
if len(arguments) <= 2 || arguments[2] == nil {
return "", true
}
flagItem := arguments[2].(parsers.SelectItem)
if value, ok := r.resolveSelectItem(flagItem).(string); ok {
return value, true
}
logger.ErrorLn("getStringFlag - got parameters of wrong type")
return "", false
}
func (r rowContext) parseString(argument interface{}) (value string, ok bool) { func (r rowContext) parseString(argument interface{}) (value string, ok bool) {
exItem := argument.(parsers.SelectItem) exItem := argument.(parsers.SelectItem)
ex := r.resolveSelectItem(exItem) ex := r.resolveSelectItem(exItem)
@@ -329,6 +384,41 @@ func (r rowContext) parseString(argument interface{}) (value string, ok bool) {
return "", false return "", false
} }
func stripRegexIgnoredWhitespace(pattern string) string {
var result strings.Builder
inCharClass := false
escaped := false
for _, r := range pattern {
if escaped {
result.WriteRune(r)
escaped = false
continue
}
if r == '\\' {
result.WriteRune(r)
escaped = true
continue
}
switch r {
case '[':
inCharClass = true
case ']':
inCharClass = false
}
if !inCharClass && (r == ' ' || r == '\t' || r == '\n' || r == '\r' || r == '\f') {
continue
}
result.WriteRune(r)
}
return result.String()
}
func convertToString(value interface{}) string { func convertToString(value interface{}) string {
switch v := value.(type) { switch v := value.(type) {
case string: case string:
@@ -231,6 +231,42 @@ func Test_Execute_StringFunctions(t *testing.T) {
) )
}) })
t.Run("Should execute function REGEXMATCH()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "regexMatch",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallRegexMatch,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField,
},
testutils.SelectItem_Constant_String("COOL WORLD"),
testutils.SelectItem_Constant_String("i"),
},
},
},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "regexMatch": false},
map[string]interface{}{"id": "456", "regexMatch": false},
map[string]interface{}{"id": "789", "regexMatch": true},
},
)
})
t.Run("Should execute function INDEX_OF()", func(t *testing.T) { t.Run("Should execute function INDEX_OF()", func(t *testing.T) {
testQueryExecute( testQueryExecute(
t, t,