6 Commits

Author SHA1 Message Date
Pijus Kamandulis
84c33e3c8e Upgrade dependancies 2024-12-18 00:34:10 +02:00
Pijus Kamandulis
5e677431a3 Prepare for sharedlibrary builds 2024-12-18 00:28:59 +02:00
Pijus Kamandulis
a4659d90a9 Enable multi-platform docker builds 2024-12-08 18:55:20 +02:00
Pijus Kamandulis
503e6bb8ad Update compatibility matrix 2024-12-08 18:17:37 +02:00
Pijus Kamandulis
e5ddc143f0 Improved concurrency handling 2024-12-08 17:54:58 +02:00
Pijus Kamandulis
66ea859f34 Add support for subqueries 2024-12-07 22:29:26 +02:00
32 changed files with 3572 additions and 2391 deletions

View File

@@ -20,7 +20,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.21.6 go-version: 1.22.0
- name: Docker Login - name: Docker Login
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@@ -1,5 +1,6 @@
builds: builds:
- binary: cosmium - binary: cosmium
main: ./cmd/server
goos: goos:
- darwin - darwin
- linux - linux
@@ -27,11 +28,14 @@ brews:
email: git@pikami.org email: git@pikami.org
dockers: dockers:
- image_templates: - id: docker-linux-amd64
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}" goos: linux
- "ghcr.io/pikami/{{ .ProjectName }}:latest" goarch: amd64
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-amd64"
dockerfile: Dockerfile dockerfile: Dockerfile
use: docker use: buildx
build_flag_templates: build_flag_templates:
- "--platform=linux/amd64" - "--platform=linux/amd64"
- "--pull" - "--pull"
@@ -42,6 +46,38 @@ dockers:
- "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.version={{.Version}}"
- id: docker-linux-arm64
goos: linux
goarch: arm64
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-arm64v8"
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/arm64"
- "--pull"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.description=Lightweight Cosmos DB emulator"
- "--label=org.opencontainers.image.url=https://github.com/pikami/cosmium"
- "--label=org.opencontainers.image.source=https://github.com/pikami/cosmium"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
docker_manifests:
- name_template: 'ghcr.io/pikami/{{ .ProjectName }}:latest'
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:latest-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-arm64v8"
- name_template: 'ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}'
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8"
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'

View File

@@ -4,28 +4,44 @@ GOTEST=$(GOCMD) test
GOCLEAN=$(GOCMD) clean GOCLEAN=$(GOCMD) clean
BINARY_NAME=cosmium BINARY_NAME=cosmium
SERVER_LOCATION=./cmd/server
SHARED_LIB_LOCATION=./sharedlibrary
SHARED_LIB_OPT=-buildmode=c-shared
DIST_DIR=dist DIST_DIR=dist
all: test build-all all: test build-all
build-all: build-darwin-arm64 build-darwin-amd64 build-linux-amd64 build-windows-amd64 build-all: build-darwin-arm64 build-darwin-amd64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64
build-darwin-arm64: build-darwin-arm64:
@echo "Building macOS ARM binary..." @echo "Building macOS ARM binary..."
@GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-darwin-arm64 . @GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-darwin-arm64 $(SERVER_LOCATION)
build-darwin-amd64: build-darwin-amd64:
@echo "Building macOS x64 binary..." @echo "Building macOS x64 binary..."
@GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-darwin-amd64 . @GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-darwin-amd64 $(SERVER_LOCATION)
build-linux-amd64: build-linux-amd64:
@echo "Building Linux x64 binary..." @echo "Building Linux x64 binary..."
@GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-linux-amd64 . @GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-linux-amd64 $(SERVER_LOCATION)
build-linux-arm64:
@echo "Building Linux ARM binary..."
@GOOS=linux GOARCH=arm64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-linux-arm64 $(SERVER_LOCATION)
build-windows-amd64: build-windows-amd64:
@echo "Building Windows x64 binary..." @echo "Building Windows x64 binary..."
@GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-windows-amd64.exe . @GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-windows-amd64.exe $(SERVER_LOCATION)
build-windows-arm64:
@echo "Building Windows ARM binary..."
@GOOS=windows GOARCH=arm64 $(GOBUILD) -o $(DIST_DIR)/$(BINARY_NAME)-windows-arm64.exe $(SERVER_LOCATION)
build-sharedlib-linux-amd64:
@echo "Building shared library for Linux x64..."
@GOOS=linux GOARCH=amd64 $(GOBUILD) $(SHARED_LIB_OPT) -o $(DIST_DIR)/$(BINARY_NAME)-linux-amd64.so $(SHARED_LIB_LOCATION)
generate-parser-nosql: generate-parser-nosql:
pigeon -o ./parsers/nosql/nosql.go ./parsers/nosql/nosql.peg pigeon -o ./parsers/nosql/nosql.go ./parsers/nosql/nosql.peg

View File

@@ -26,9 +26,11 @@ You can download the latest version of Cosmium from the [GitHub Releases page](h
Cosmium is available for the following platforms: Cosmium is available for the following platforms:
- **Linux**: cosmium-linux-amd64 - **Linux**: cosmium-linux-amd64
- **Linux on ARM**: cosmium-linux-arm64
- **macOS**: cosmium-darwin-amd64 - **macOS**: cosmium-darwin-amd64
- **macOS on Apple Silicon**: cosmium-darwin-arm64 - **macOS on Apple Silicon**: cosmium-darwin-arm64
- **Windows**: cosmium-windows-amd64.exe - **Windows**: cosmium-windows-amd64.exe
- **Windows on ARM**: cosmium-windows-arm64.exe
### Running Cosmium ### Running Cosmium

View File

@@ -8,5 +8,11 @@ import (
) )
func CosmiumExport(c *gin.Context) { func CosmiumExport(c *gin.Context) {
c.IndentedJSON(http.StatusOK, repositories.GetState()) repositoryState, err := repositories.GetState()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Data(http.StatusOK, "application/json", []byte(repositoryState))
} }

View File

@@ -1,6 +1,7 @@
package api package api
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
@@ -12,6 +13,10 @@ import (
tlsprovider "github.com/pikami/cosmium/internal/tls_provider" tlsprovider "github.com/pikami/cosmium/internal/tls_provider"
) )
type Server struct {
StopServer chan interface{}
}
func CreateRouter() *gin.Engine { func CreateRouter() *gin.Engine {
router := gin.Default(func(e *gin.Engine) { router := gin.Default(func(e *gin.Engine) {
e.RedirectTrailingSlash = false e.RedirectTrailingSlash = false
@@ -57,42 +62,60 @@ func CreateRouter() *gin.Engine {
return router return router
} }
func StartAPI() { func StartAPI() *Server {
if !config.Config.Debug { if !config.Config.Debug {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
router := CreateRouter() router := CreateRouter()
listenAddress := fmt.Sprintf(":%d", config.Config.Port) listenAddress := fmt.Sprintf(":%d", config.Config.Port)
stopChan := make(chan interface{})
server := &http.Server{
Addr: listenAddress,
Handler: router.Handler(),
}
go func() {
<-stopChan
logger.Info("Shutting down server...")
err := server.Shutdown(context.TODO())
if err != nil {
logger.Error("Failed to shutdown server:", err)
}
}()
go func() {
if config.Config.DisableTls {
logger.Infof("Listening and serving HTTP on %s\n", server.Addr)
err := server.ListenAndServe()
if err != nil {
logger.Error("Failed to start HTTP server:", err)
}
return
}
if config.Config.TLS_CertificatePath != "" && config.Config.TLS_CertificateKey != "" { if config.Config.TLS_CertificatePath != "" && config.Config.TLS_CertificateKey != "" {
err := router.RunTLS( logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
listenAddress, err := server.ListenAndServeTLS(
config.Config.TLS_CertificatePath, config.Config.TLS_CertificatePath,
config.Config.TLS_CertificateKey) config.Config.TLS_CertificateKey)
if err != nil { if err != nil {
logger.Error("Failed to start HTTPS server:", err) logger.Error("Failed to start HTTPS server:", err)
} }
return return
} } else {
if config.Config.DisableTls {
router.Run(listenAddress)
}
tlsConfig := tlsprovider.GetDefaultTlsConfig() tlsConfig := tlsprovider.GetDefaultTlsConfig()
server := &http.Server{ server.TLSConfig = tlsConfig
Addr: listenAddress,
Handler: router.Handler(),
TLSConfig: tlsConfig,
}
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr) logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
err := server.ListenAndServeTLS("", "") err := server.ListenAndServeTLS("", "")
if err != nil { if err != nil {
logger.Error("Failed to start HTTPS server:", err) logger.Error("Failed to start HTTPS server:", err)
} }
return
router.Run() }
}()
return &Server{StopServer: stopChan}
} }

View File

@@ -8,7 +8,9 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"sync"
"testing" "testing"
"time"
"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"
@@ -162,6 +164,56 @@ func Test_Documents(t *testing.T) {
}, },
) )
}) })
t.Run("Should handle parallel writes", func(t *testing.T) {
var wg sync.WaitGroup
rutineCount := 100
results := make(chan error, rutineCount)
createCall := func(i int) {
defer wg.Done()
item := map[string]interface{}{
"id": fmt.Sprintf("id-%d", i),
"pk": fmt.Sprintf("pk-%d", i),
"val": i,
}
bytes, err := json.Marshal(item)
if err != nil {
results <- err
return
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_, err = collectionClient.CreateItem(
ctx,
azcosmos.PartitionKey{},
bytes,
&azcosmos.ItemOptions{
EnableContentResponseOnWrite: false,
},
)
results <- err
collectionClient.ReadItem(ctx, azcosmos.PartitionKey{}, fmt.Sprintf("id-%d", i), nil)
collectionClient.DeleteItem(ctx, azcosmos.PartitionKey{}, fmt.Sprintf("id-%d", i), nil)
}
for i := 0; i < rutineCount; i++ {
wg.Add(1)
go createCall(i)
}
wg.Wait()
close(results)
for err := range results {
if err != nil {
t.Errorf("Error creating item: %v", err)
}
}
})
} }
func Test_Documents_Patch(t *testing.T) { func Test_Documents_Patch(t *testing.T) {

View File

@@ -15,18 +15,21 @@ func main() {
repositories.InitializeRepository() repositories.InitializeRepository()
go api.StartAPI() server := api.StartAPI()
waitForExit() waitForExit(server)
} }
func waitForExit() { func waitForExit(server *api.Server) {
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// Block until a exit signal is received // Block until a exit signal is received
<-sigs <-sigs
// Stop the server
server.StopServer <- true
if config.Config.PersistDataFilePath != "" { if config.Config.PersistDataFilePath != "" {
repositories.SaveStateFS(config.Config.PersistDataFilePath) repositories.SaveStateFS(config.Config.PersistDataFilePath)
} }

View File

@@ -18,8 +18,8 @@ Cosmium strives to support the core features of Cosmos DB, including:
| Feature | Implemented | | Feature | Implemented |
| ----------------------------- | ----------- | | ----------------------------- | ----------- |
| Subqueries | No | | Subqueries | Yes |
| Joins | No | | Joins | Yes |
| Computed properties | No | | Computed properties | No |
| Coalesce operators | No | | Coalesce operators | No |
| Bitwise operators | No | | Bitwise operators | No |
@@ -63,9 +63,11 @@ Cosmium strives to support the core features of Cosmos DB, including:
### Array Functions ### Array Functions
| Function | Implemented | | Function | Implemented |
| -------------- | ----------- | | ------------------ | ----------- |
| ARRAY_CONCAT | Yes | | ARRAY_CONCAT | Yes |
| ARRAY_CONTAINS | No | | ARRAY_CONTAINS | No |
| ARRAY_CONTAINS_ANY | No |
| ARRAY_CONTAINS_ALL | No |
| ARRAY_LENGTH | Yes | | ARRAY_LENGTH | Yes |
| ARRAY_SLICE | Yes | | ARRAY_SLICE | Yes |
| CHOOSE | No | | CHOOSE | No |

30
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/pikami/cosmium module github.com/pikami/cosmium
go 1.21.6 go 1.22.0
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2
@@ -9,39 +9,39 @@ require (
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
) )
require ( require (
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
github.com/bytedance/sonic v1.11.8 // indirect github.com/bytedance/sonic v1.12.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // 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.21.0 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // 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.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.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.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.36.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

60
go.sum
View File

@@ -10,10 +10,11 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aM
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c= github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA= github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@@ -23,8 +24,8 @@ 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/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
@@ -35,10 +36,10 @@ 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.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -49,8 +50,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
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/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
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=
@@ -63,8 +64,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
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.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -74,40 +75,35 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
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=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -12,6 +12,9 @@ import (
) )
func GetAllCollections(databaseId string) ([]repositorymodels.Collection, repositorymodels.RepositoryStatus) { func GetAllCollections(databaseId string) ([]repositorymodels.Collection, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok { if _, ok := storeState.Databases[databaseId]; !ok {
return make([]repositorymodels.Collection, 0), repositorymodels.StatusNotFound return make([]repositorymodels.Collection, 0), repositorymodels.StatusNotFound
} }
@@ -20,6 +23,9 @@ func GetAllCollections(databaseId string) ([]repositorymodels.Collection, reposi
} }
func GetCollection(databaseId string, collectionId string) (repositorymodels.Collection, repositorymodels.RepositoryStatus) { func GetCollection(databaseId string, collectionId string) (repositorymodels.Collection, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok { if _, ok := storeState.Databases[databaseId]; !ok {
return repositorymodels.Collection{}, repositorymodels.StatusNotFound return repositorymodels.Collection{}, repositorymodels.StatusNotFound
} }
@@ -32,6 +38,9 @@ func GetCollection(databaseId string, collectionId string) (repositorymodels.Col
} }
func DeleteCollection(databaseId string, collectionId string) repositorymodels.RepositoryStatus { func DeleteCollection(databaseId string, collectionId string) repositorymodels.RepositoryStatus {
storeState.Lock()
defer storeState.Unlock()
if _, ok := storeState.Databases[databaseId]; !ok { if _, ok := storeState.Databases[databaseId]; !ok {
return repositorymodels.StatusNotFound return repositorymodels.StatusNotFound
} }
@@ -46,6 +55,9 @@ func DeleteCollection(databaseId string, collectionId string) repositorymodels.R
} }
func CreateCollection(databaseId string, newCollection repositorymodels.Collection) (repositorymodels.Collection, repositorymodels.RepositoryStatus) { func CreateCollection(databaseId string, newCollection repositorymodels.Collection) (repositorymodels.Collection, repositorymodels.RepositoryStatus) {
storeState.Lock()
defer storeState.Unlock()
var ok bool var ok bool
var database repositorymodels.Database var database repositorymodels.Database
if database, ok = storeState.Databases[databaseId]; !ok { if database, ok = storeState.Databases[databaseId]; !ok {

View File

@@ -11,10 +11,16 @@ import (
) )
func GetAllDatabases() ([]repositorymodels.Database, repositorymodels.RepositoryStatus) { func GetAllDatabases() ([]repositorymodels.Database, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
return maps.Values(storeState.Databases), repositorymodels.StatusOk return maps.Values(storeState.Databases), repositorymodels.StatusOk
} }
func GetDatabase(id string) (repositorymodels.Database, repositorymodels.RepositoryStatus) { func GetDatabase(id string) (repositorymodels.Database, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
if database, ok := storeState.Databases[id]; ok { if database, ok := storeState.Databases[id]; ok {
return database, repositorymodels.StatusOk return database, repositorymodels.StatusOk
} }
@@ -23,6 +29,9 @@ func GetDatabase(id string) (repositorymodels.Database, repositorymodels.Reposit
} }
func DeleteDatabase(id string) repositorymodels.RepositoryStatus { func DeleteDatabase(id string) repositorymodels.RepositoryStatus {
storeState.Lock()
defer storeState.Unlock()
if _, ok := storeState.Databases[id]; !ok { if _, ok := storeState.Databases[id]; !ok {
return repositorymodels.StatusNotFound return repositorymodels.StatusNotFound
} }
@@ -33,6 +42,9 @@ func DeleteDatabase(id string) repositorymodels.RepositoryStatus {
} }
func CreateDatabase(newDatabase repositorymodels.Database) (repositorymodels.Database, repositorymodels.RepositoryStatus) { func CreateDatabase(newDatabase repositorymodels.Database) (repositorymodels.Database, repositorymodels.RepositoryStatus) {
storeState.Lock()
defer storeState.Unlock()
if _, ok := storeState.Databases[newDatabase.ID]; ok { if _, ok := storeState.Databases[newDatabase.ID]; ok {
return repositorymodels.Database{}, repositorymodels.Conflict return repositorymodels.Database{}, repositorymodels.Conflict
} }

View File

@@ -15,6 +15,9 @@ import (
) )
func GetAllDocuments(databaseId string, collectionId string) ([]repositorymodels.Document, repositorymodels.RepositoryStatus) { func GetAllDocuments(databaseId string, collectionId string) ([]repositorymodels.Document, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok { if _, ok := storeState.Databases[databaseId]; !ok {
return make([]repositorymodels.Document, 0), repositorymodels.StatusNotFound return make([]repositorymodels.Document, 0), repositorymodels.StatusNotFound
} }
@@ -27,6 +30,9 @@ func GetAllDocuments(databaseId string, collectionId string) ([]repositorymodels
} }
func GetDocument(databaseId string, collectionId string, documentId string) (repositorymodels.Document, repositorymodels.RepositoryStatus) { func GetDocument(databaseId string, collectionId string, documentId string) (repositorymodels.Document, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok { if _, ok := storeState.Databases[databaseId]; !ok {
return repositorymodels.Document{}, repositorymodels.StatusNotFound return repositorymodels.Document{}, repositorymodels.StatusNotFound
} }
@@ -43,6 +49,9 @@ func GetDocument(databaseId string, collectionId string, documentId string) (rep
} }
func DeleteDocument(databaseId string, collectionId string, documentId string) repositorymodels.RepositoryStatus { func DeleteDocument(databaseId string, collectionId string, documentId string) repositorymodels.RepositoryStatus {
storeState.Lock()
defer storeState.Unlock()
if _, ok := storeState.Databases[databaseId]; !ok { if _, ok := storeState.Databases[databaseId]; !ok {
return repositorymodels.StatusNotFound return repositorymodels.StatusNotFound
} }
@@ -61,6 +70,9 @@ func DeleteDocument(databaseId string, collectionId string, documentId string) r
} }
func CreateDocument(databaseId string, collectionId string, document map[string]interface{}) (repositorymodels.Document, repositorymodels.RepositoryStatus) { func CreateDocument(databaseId string, collectionId string, document map[string]interface{}) (repositorymodels.Document, repositorymodels.RepositoryStatus) {
storeState.Lock()
defer storeState.Unlock()
var ok bool var ok bool
var documentId string var documentId string
var database repositorymodels.Database var database repositorymodels.Database
@@ -111,7 +123,7 @@ func ExecuteQueryDocuments(databaseId string, collectionId string, query string,
if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok { if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok {
typedQuery.Parameters = queryParameters typedQuery.Parameters = queryParameters
return memoryexecutor.Execute(typedQuery, covDocs), repositorymodels.StatusOk return memoryexecutor.ExecuteQuery(typedQuery, covDocs), repositorymodels.StatusOk
} }
return nil, repositorymodels.BadRequest return nil, repositorymodels.BadRequest

View File

@@ -10,6 +10,9 @@ import (
// I have no idea what this is tbh // I have no idea what this is tbh
func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repositorymodels.PartitionKeyRange, repositorymodels.RepositoryStatus) { func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repositorymodels.PartitionKeyRange, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
databaseRid := databaseId databaseRid := databaseId
collectionRid := collectionId collectionRid := collectionId
var timestamp int64 = 0 var timestamp int64 = 0

View File

@@ -66,6 +66,9 @@ func LoadStateFS(filePath string) {
} }
func SaveStateFS(filePath string) { func SaveStateFS(filePath string) {
storeState.RLock()
defer storeState.RUnlock()
data, err := json.MarshalIndent(storeState, "", "\t") data, err := json.MarshalIndent(storeState, "", "\t")
if err != nil { if err != nil {
logger.Errorf("Failed to save state: %v\n", err) logger.Errorf("Failed to save state: %v\n", err)
@@ -80,8 +83,17 @@ func SaveStateFS(filePath string) {
logger.Infof("Documents: %d\n", getLength(storeState.Documents)) logger.Infof("Documents: %d\n", getLength(storeState.Documents))
} }
func GetState() repositorymodels.State { func GetState() (string, error) {
return storeState storeState.RLock()
defer storeState.RUnlock()
data, err := json.MarshalIndent(storeState, "", "\t")
if err != nil {
logger.Errorf("Failed to serialize state: %v\n", err)
return "", err
}
return string(data), nil
} }
func getLength(v interface{}) int { func getLength(v interface{}) int {

View File

@@ -1,5 +1,7 @@
package repositorymodels package repositorymodels
import "sync"
type Database struct { type Database struct {
ID string `json:"id"` ID string `json:"id"`
TimeStamp int64 `json:"_ts"` TimeStamp int64 `json:"_ts"`
@@ -101,6 +103,8 @@ type PartitionKeyRange struct {
} }
type State struct { type State struct {
sync.RWMutex
// Map databaseId -> Database // Map databaseId -> Database
Databases map[string]Database `json:"databases"` Databases map[string]Database `json:"databases"`

View File

@@ -5,6 +5,7 @@ type SelectStmt struct {
Table Table Table Table
JoinItems []JoinItem JoinItems []JoinItem
Filters interface{} Filters interface{}
Exists bool
Distinct bool Distinct bool
Count int Count int
Offset int Offset int
@@ -15,6 +16,7 @@ type SelectStmt struct {
type Table struct { type Table struct {
Value string Value string
SelectItem SelectItem
} }
type JoinItem struct { type JoinItem struct {
@@ -30,6 +32,7 @@ const (
SelectItemTypeArray SelectItemTypeArray
SelectItemTypeConstant SelectItemTypeConstant
SelectItemTypeFunctionCall SelectItemTypeFunctionCall
SelectItemTypeSubQuery
) )
type SelectItem struct { type SelectItem struct {

View File

@@ -122,4 +122,25 @@ func Test_Parse(t *testing.T) {
}, },
) )
}) })
t.Run("Should parse IN selector", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id FROM c IN c.tags`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
)
})
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,17 @@ package nosql
import "github.com/pikami/cosmium/parsers" import "github.com/pikami/cosmium/parsers"
func makeSelectStmt( func makeSelectStmt(
columns, table, joinItems, columns, fromClause, joinItems,
whereClause interface{}, distinctClause interface{}, whereClause interface{}, distinctClause interface{},
count interface{}, groupByClause interface{}, orderList interface{}, count interface{}, groupByClause interface{}, orderList interface{},
offsetClause interface{}, offsetClause interface{},
) (parsers.SelectStmt, error) { ) (parsers.SelectStmt, error) {
selectStmt := parsers.SelectStmt{ selectStmt := parsers.SelectStmt{
SelectItems: columns.([]parsers.SelectItem), SelectItems: columns.([]parsers.SelectItem),
Table: table.(parsers.Table), }
if fromTable, ok := fromClause.(parsers.Table); ok {
selectStmt.Table = fromTable
} }
if joinItemsArray, ok := joinItems.([]interface{}); ok && len(joinItemsArray) > 0 { if joinItemsArray, ok := joinItems.([]interface{}); ok && len(joinItemsArray) > 0 {
@@ -56,10 +59,18 @@ func makeSelectStmt(
} }
func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) { func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) {
return parsers.JoinItem{ joinItem := parsers.JoinItem{}
Table: table.(parsers.Table),
SelectItem: column.(parsers.SelectItem), if selectItem, isSelectItem := column.(parsers.SelectItem); isSelectItem {
}, nil joinItem.SelectItem = selectItem
joinItem.Table.Value = selectItem.Alias
}
if tableTyped, isTable := table.(parsers.Table); isTable {
joinItem.Table = tableTyped
}
return joinItem, nil
} }
func makeSelectItem(name interface{}, path interface{}, selectItemType parsers.SelectItemType) (parsers.SelectItem, error) { func makeSelectItem(name interface{}, path interface{}, selectItemType parsers.SelectItemType) (parsers.SelectItem, error) {
@@ -177,13 +188,13 @@ SelectStmt <- Select ws
distinctClause:DistinctClause? ws distinctClause:DistinctClause? ws
topClause:TopClause? ws topClause:TopClause? ws
columns:Selection ws columns:Selection ws
From ws table:TableName ws fromClause:FromClause? ws
joinClauses:JoinClause* ws joinClauses:(ws join:JoinClause { return join, nil })* ws
whereClause:(ws Where ws condition:Condition { return condition, nil })? whereClause:(ws Where ws condition:Condition { return condition, nil })?
groupByClause:(ws GroupBy ws columns:ColumnList { return columns, nil })? groupByClause:(ws GroupBy ws columns:ColumnList { return columns, nil })?
orderByClause:OrderByClause? orderByClause:(ws order:OrderByClause { return order, nil })?
offsetClause:OffsetClause? { offsetClause:(ws offset:OffsetClause { return offset, nil })? {
return makeSelectStmt(columns, table, joinClauses, whereClause, return makeSelectStmt(columns, fromClause, joinClauses, whereClause,
distinctClause, topClause, groupByClause, orderByClause, offsetClause) distinctClause, topClause, groupByClause, orderByClause, offsetClause)
} }
@@ -193,8 +204,49 @@ TopClause <- Top ws count:Integer {
return count, nil return count, nil
} }
FromClause <- From ws table:TableName selectItem:(ws "IN"i ws column:SelectItem { return column, nil })? {
tableTyped := table.(parsers.Table)
if selectItem != nil {
tableTyped.SelectItem = selectItem.(parsers.SelectItem)
}
return tableTyped, nil
} / From ws subQuery:SubQuerySelectItem {
subQueryTyped := subQuery.(parsers.SelectItem)
table := parsers.Table{
Value: subQueryTyped.Alias,
SelectItem: subQueryTyped,
}
return table, nil
}
SubQuery <- exists:(exists:Exists ws { return exists, nil })? "(" ws selectStmt:SelectStmt ws ")" {
if selectStatement, isGoodValue := selectStmt.(parsers.SelectStmt); isGoodValue {
selectStatement.Exists = exists != nil
return selectStatement, nil
}
return selectStmt, nil
}
SubQuerySelectItem <- subQuery:SubQuery asClause:(ws alias:AsClause { return alias, nil })? {
selectItem := parsers.SelectItem{
Type: parsers.SelectItemTypeSubQuery,
Value: subQuery,
}
if tableName, isString := asClause.(string); isString {
selectItem.Alias = tableName
}
return selectItem, nil
}
JoinClause <- Join ws table:TableName ws "IN"i ws column:SelectItem { JoinClause <- Join ws table:TableName ws "IN"i ws column:SelectItem {
return makeJoin(table, column) return makeJoin(table, column)
} / Join ws subQuery:SubQuerySelectItem {
return makeJoin(nil, subQuery)
} }
OffsetClause <- "OFFSET"i ws offset:IntegerLiteral ws "LIMIT"i ws limit:IntegerLiteral { OffsetClause <- "OFFSET"i ws offset:IntegerLiteral ws "LIMIT"i ws limit:IntegerLiteral {
@@ -241,7 +293,7 @@ SelectProperty <- name:Identifier path:(DotFieldAccess / ArrayFieldAccess)* {
return makeSelectItem(name, path, parsers.SelectItemTypeField) return makeSelectItem(name, path, parsers.SelectItemTypeField)
} }
SelectItem <- selectItem:(Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? { SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
var itemResult parsers.SelectItem var itemResult parsers.SelectItem
switch typedValue := selectItem.(type) { switch typedValue := selectItem.(type) {
case parsers.SelectItem: case parsers.SelectItem:
@@ -322,11 +374,13 @@ From <- "FROM"i
Join <- "JOIN"i Join <- "JOIN"i
Exists <- "EXISTS"i
Where <- "WHERE"i Where <- "WHERE"i
And <- "AND"i And <- "AND"i
Or <- "OR"i Or <- "OR"i wss
GroupBy <- "GROUP"i ws "BY"i GroupBy <- "GROUP"i ws "BY"i
@@ -677,4 +731,6 @@ non_escape_character <- !(escape_character) char:.
ws <- [ \t\n\r]* ws <- [ \t\n\r]*
wss <- [ \t\n\r]+
EOF <- !. EOF <- !.

View File

@@ -0,0 +1,125 @@
package nosql_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
)
func Test_Parse_SubQuery(t *testing.T) {
t.Run("Should parse FROM subquery", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id FROM (SELECT VALUE cc["info"] FROM cc) AS c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Alias: "c",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
Table: parsers.Table{Value: "cc"},
SelectItems: []parsers.SelectItem{
{Path: []string{"cc", "info"}, IsTopLevel: true},
},
},
},
},
},
)
})
t.Run("Should parse JOIN subquery", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id, cc.name FROM c JOIN (SELECT tag.name FROM tag IN c.tags) AS cc`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{Path: []string{"cc", "name"}},
},
Table: parsers.Table{
Value: "c",
},
JoinItems: []parsers.JoinItem{
{
Table: parsers.Table{
Value: "cc",
},
SelectItem: parsers.SelectItem{
Alias: "cc",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}},
},
Table: parsers.Table{
Value: "tag",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
},
},
},
},
)
})
t.Run("Should parse JOIN EXISTS subquery", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id
FROM c
JOIN (
SELECT VALUE EXISTS(SELECT tag.name FROM tag IN c.tags)
) AS hasTags
WHERE hasTags`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
},
Table: parsers.Table{
Value: "c",
},
JoinItems: []parsers.JoinItem{
{
Table: parsers.Table{Value: "hasTags"},
SelectItem: parsers.SelectItem{
Alias: "hasTags",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
IsTopLevel: true,
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}},
},
Table: parsers.Table{
Value: "tag",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
Exists: true,
},
},
},
},
},
},
},
Filters: parsers.SelectItem{
Path: []string{"hasTags"},
},
},
)
})
}

View File

@@ -6,14 +6,13 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Avg(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
sum := 0.0 sum := 0.0
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
sum += numericValue sum += numericValue
count++ count++
@@ -22,7 +21,6 @@ func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return sum / float64(count) return sum / float64(count)
@@ -31,30 +29,27 @@ func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowTyp
} }
} }
func (c memoryExecutorContext) aggregate_Count(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Count(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if value != nil { if value != nil {
count++ count++
} }
} }
}
return count return count
} }
func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Max(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
max := 0.0 max := 0.0
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
if numericValue > max { if numericValue > max {
max = numericValue max = numericValue
@@ -67,7 +62,6 @@ func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return max return max
@@ -76,14 +70,13 @@ func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowTyp
} }
} }
func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Min(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
min := math.MaxFloat64 min := math.MaxFloat64
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
if numericValue < min { if numericValue < min {
min = numericValue min = numericValue
@@ -96,7 +89,6 @@ func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return min return min
@@ -105,14 +97,13 @@ func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowTyp
} }
} }
func (c memoryExecutorContext) aggregate_Sum(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Sum(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
sum := 0.0 sum := 0.0
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
sum += numericValue sum += numericValue
count++ count++
@@ -121,7 +112,6 @@ func (c memoryExecutorContext) aggregate_Sum(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return sum return sum

View File

@@ -7,17 +7,17 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) array_Concat(arguments []interface{}, row RowType) []interface{} { func (r rowContext) array_Concat(arguments []interface{}) []interface{} {
var result []interface{} var result []interface{}
for _, arg := range arguments { for _, arg := range arguments {
array := c.parseArray(arg, row) array := r.parseArray(arg)
result = append(result, array...) result = append(result, array...)
} }
return result return result
} }
func (c memoryExecutorContext) array_Length(arguments []interface{}, row RowType) int { func (r rowContext) array_Length(arguments []interface{}) int {
array := c.parseArray(arguments[0], row) array := r.parseArray(arguments[0])
if array == nil { if array == nil {
return 0 return 0
} }
@@ -25,15 +25,15 @@ func (c memoryExecutorContext) array_Length(arguments []interface{}, row RowType
return len(array) return len(array)
} }
func (c memoryExecutorContext) array_Slice(arguments []interface{}, row RowType) []interface{} { func (r rowContext) array_Slice(arguments []interface{}) []interface{} {
var ok bool var ok bool
var start int var start int
var length int var length int
array := c.parseArray(arguments[0], row) array := r.parseArray(arguments[0])
startEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) startEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if arguments[2] != nil { if arguments[2] != nil {
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("array_Slice - got length parameters of wrong type") logger.Error("array_Slice - got length parameters of wrong type")
@@ -65,9 +65,9 @@ func (c memoryExecutorContext) array_Slice(arguments []interface{}, row RowType)
return array[start:end] return array[start:end]
} }
func (c memoryExecutorContext) set_Intersect(arguments []interface{}, row RowType) []interface{} { func (r rowContext) set_Intersect(arguments []interface{}) []interface{} {
set1 := c.parseArray(arguments[0], row) set1 := r.parseArray(arguments[0])
set2 := c.parseArray(arguments[1], row) set2 := r.parseArray(arguments[1])
intersection := make(map[interface{}]struct{}) intersection := make(map[interface{}]struct{})
if set1 == nil || set2 == nil { if set1 == nil || set2 == nil {
@@ -88,9 +88,9 @@ func (c memoryExecutorContext) set_Intersect(arguments []interface{}, row RowTyp
return result return result
} }
func (c memoryExecutorContext) set_Union(arguments []interface{}, row RowType) []interface{} { func (r rowContext) set_Union(arguments []interface{}) []interface{} {
set1 := c.parseArray(arguments[0], row) set1 := r.parseArray(arguments[0])
set2 := c.parseArray(arguments[1], row) set2 := r.parseArray(arguments[1])
var result []interface{} var result []interface{}
union := make(map[interface{}]struct{}) union := make(map[interface{}]struct{})
@@ -111,9 +111,9 @@ func (c memoryExecutorContext) set_Union(arguments []interface{}, row RowType) [
return result return result
} }
func (c memoryExecutorContext) parseArray(argument interface{}, row RowType) []interface{} { func (r rowContext) parseArray(argument interface{}) []interface{} {
exItem := argument.(parsers.SelectItem) exItem := argument.(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
arrValue := reflect.ValueOf(ex) arrValue := reflect.ValueOf(ex)
if arrValue.Kind() != reflect.Slice { if arrValue.Kind() != reflect.Slice {

View File

@@ -8,9 +8,9 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) math_Abs(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Abs(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@@ -26,9 +26,9 @@ func (c memoryExecutorContext) math_Abs(arguments []interface{}, row RowType) in
} }
} }
func (c memoryExecutorContext) math_Acos(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Acos(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -44,9 +44,9 @@ func (c memoryExecutorContext) math_Acos(arguments []interface{}, row RowType) i
return math.Acos(val) * 180 / math.Pi return math.Acos(val) * 180 / math.Pi
} }
func (c memoryExecutorContext) math_Asin(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Asin(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -62,9 +62,9 @@ func (c memoryExecutorContext) math_Asin(arguments []interface{}, row RowType) i
return math.Asin(val) * 180 / math.Pi return math.Asin(val) * 180 / math.Pi
} }
func (c memoryExecutorContext) math_Atan(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Atan(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -75,9 +75,9 @@ func (c memoryExecutorContext) math_Atan(arguments []interface{}, row RowType) i
return math.Atan(val) * 180 / math.Pi return math.Atan(val) * 180 / math.Pi
} }
func (c memoryExecutorContext) math_Ceiling(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Ceiling(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@@ -90,9 +90,9 @@ func (c memoryExecutorContext) math_Ceiling(arguments []interface{}, row RowType
} }
} }
func (c memoryExecutorContext) math_Cos(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Cos(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -103,9 +103,9 @@ func (c memoryExecutorContext) math_Cos(arguments []interface{}, row RowType) in
return math.Cos(val) return math.Cos(val)
} }
func (c memoryExecutorContext) math_Cot(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Cot(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -121,9 +121,9 @@ func (c memoryExecutorContext) math_Cot(arguments []interface{}, row RowType) in
return 1 / math.Tan(val) return 1 / math.Tan(val)
} }
func (c memoryExecutorContext) math_Degrees(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Degrees(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -134,9 +134,9 @@ func (c memoryExecutorContext) math_Degrees(arguments []interface{}, row RowType
return val * (180 / math.Pi) return val * (180 / math.Pi)
} }
func (c memoryExecutorContext) math_Exp(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Exp(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -147,9 +147,9 @@ func (c memoryExecutorContext) math_Exp(arguments []interface{}, row RowType) in
return math.Exp(val) return math.Exp(val)
} }
func (c memoryExecutorContext) math_Floor(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Floor(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@@ -162,9 +162,9 @@ func (c memoryExecutorContext) math_Floor(arguments []interface{}, row RowType)
} }
} }
func (c memoryExecutorContext) math_IntBitNot(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitNot(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case int: case int:
@@ -175,9 +175,9 @@ func (c memoryExecutorContext) math_IntBitNot(arguments []interface{}, row RowTy
} }
} }
func (c memoryExecutorContext) math_Log10(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Log10(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -193,9 +193,9 @@ func (c memoryExecutorContext) math_Log10(arguments []interface{}, row RowType)
return math.Log10(val) return math.Log10(val)
} }
func (c memoryExecutorContext) math_Radians(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Radians(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -206,9 +206,9 @@ func (c memoryExecutorContext) math_Radians(arguments []interface{}, row RowType
return val * (math.Pi / 180.0) return val * (math.Pi / 180.0)
} }
func (c memoryExecutorContext) math_Round(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Round(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@@ -221,9 +221,9 @@ func (c memoryExecutorContext) math_Round(arguments []interface{}, row RowType)
} }
} }
func (c memoryExecutorContext) math_Sign(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Sign(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@@ -248,9 +248,9 @@ func (c memoryExecutorContext) math_Sign(arguments []interface{}, row RowType) i
} }
} }
func (c memoryExecutorContext) math_Sin(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Sin(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -261,9 +261,9 @@ func (c memoryExecutorContext) math_Sin(arguments []interface{}, row RowType) in
return math.Sin(val) return math.Sin(val)
} }
func (c memoryExecutorContext) math_Sqrt(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Sqrt(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -274,9 +274,9 @@ func (c memoryExecutorContext) math_Sqrt(arguments []interface{}, row RowType) i
return math.Sqrt(val) return math.Sqrt(val)
} }
func (c memoryExecutorContext) math_Square(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Square(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -287,9 +287,9 @@ func (c memoryExecutorContext) math_Square(arguments []interface{}, row RowType)
return math.Pow(val, 2) return math.Pow(val, 2)
} }
func (c memoryExecutorContext) math_Tan(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Tan(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@@ -300,9 +300,9 @@ func (c memoryExecutorContext) math_Tan(arguments []interface{}, row RowType) in
return math.Tan(val) return math.Tan(val)
} }
func (c memoryExecutorContext) math_Trunc(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Trunc(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@@ -315,11 +315,11 @@ func (c memoryExecutorContext) math_Trunc(arguments []interface{}, row RowType)
} }
} }
func (c memoryExecutorContext) math_Atn2(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Atn2(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
y, yIsNumber := numToFloat64(ex1) y, yIsNumber := numToFloat64(ex1)
x, xIsNumber := numToFloat64(ex2) x, xIsNumber := numToFloat64(ex2)
@@ -332,11 +332,11 @@ func (c memoryExecutorContext) math_Atn2(arguments []interface{}, row RowType) i
return math.Atan2(y, x) return math.Atan2(y, x)
} }
func (c memoryExecutorContext) math_IntAdd(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntAdd(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
ex1Number, ex1IsNumber := numToInt(ex1) ex1Number, ex1IsNumber := numToInt(ex1)
ex2Number, ex2IsNumber := numToInt(ex2) ex2Number, ex2IsNumber := numToInt(ex2)
@@ -349,11 +349,11 @@ func (c memoryExecutorContext) math_IntAdd(arguments []interface{}, row RowType)
return ex1Number + ex2Number return ex1Number + ex2Number
} }
func (c memoryExecutorContext) math_IntBitAnd(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitAnd(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
ex1Int, ex1IsInt := numToInt(ex1) ex1Int, ex1IsInt := numToInt(ex1)
ex2Int, ex2IsInt := numToInt(ex2) ex2Int, ex2IsInt := numToInt(ex2)
@@ -366,11 +366,11 @@ func (c memoryExecutorContext) math_IntBitAnd(arguments []interface{}, row RowTy
return ex1Int & ex2Int return ex1Int & ex2Int
} }
func (c memoryExecutorContext) math_IntBitLeftShift(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitLeftShift(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := numToInt(ex1) num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2) num2, num2IsInt := numToInt(ex2)
@@ -383,11 +383,11 @@ func (c memoryExecutorContext) math_IntBitLeftShift(arguments []interface{}, row
return num1 << uint(num2) return num1 << uint(num2)
} }
func (c memoryExecutorContext) math_IntBitOr(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitOr(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@@ -400,11 +400,11 @@ func (c memoryExecutorContext) math_IntBitOr(arguments []interface{}, row RowTyp
return num1 | num2 return num1 | num2
} }
func (c memoryExecutorContext) math_IntBitRightShift(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitRightShift(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := numToInt(ex1) num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2) num2, num2IsInt := numToInt(ex2)
@@ -417,11 +417,11 @@ func (c memoryExecutorContext) math_IntBitRightShift(arguments []interface{}, ro
return num1 >> uint(num2) return num1 >> uint(num2)
} }
func (c memoryExecutorContext) math_IntBitXor(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitXor(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@@ -434,11 +434,11 @@ func (c memoryExecutorContext) math_IntBitXor(arguments []interface{}, row RowTy
return num1 ^ num2 return num1 ^ num2
} }
func (c memoryExecutorContext) math_IntDiv(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntDiv(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@@ -451,11 +451,11 @@ func (c memoryExecutorContext) math_IntDiv(arguments []interface{}, row RowType)
return num1 / num2 return num1 / num2
} }
func (c memoryExecutorContext) math_IntMul(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntMul(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@@ -468,11 +468,11 @@ func (c memoryExecutorContext) math_IntMul(arguments []interface{}, row RowType)
return num1 * num2 return num1 * num2
} }
func (c memoryExecutorContext) math_IntSub(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntSub(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@@ -485,11 +485,11 @@ func (c memoryExecutorContext) math_IntSub(arguments []interface{}, row RowType)
return num1 - num2 return num1 - num2
} }
func (c memoryExecutorContext) math_IntMod(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntMod(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@@ -502,11 +502,11 @@ func (c memoryExecutorContext) math_IntMod(arguments []interface{}, row RowType)
return num1 % num2 return num1 % num2
} }
func (c memoryExecutorContext) math_Power(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Power(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
base, baseIsNumber := numToFloat64(ex1) base, baseIsNumber := numToFloat64(ex1)
exponent, exponentIsNumber := numToFloat64(ex2) exponent, exponentIsNumber := numToFloat64(ex2)
@@ -519,14 +519,14 @@ func (c memoryExecutorContext) math_Power(arguments []interface{}, row RowType)
return math.Pow(base, exponent) return math.Pow(base, exponent)
} }
func (c memoryExecutorContext) math_Log(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Log(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem1, row) ex := r.resolveSelectItem(exItem1)
var base float64 = math.E var base float64 = math.E
if len(arguments) > 1 { if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
baseValueObject := c.getFieldValue(exItem2, row) baseValueObject := r.resolveSelectItem(exItem2)
baseValue, baseValueIsNumber := numToFloat64(baseValueObject) baseValue, baseValueIsNumber := numToFloat64(baseValueObject)
if !baseValueIsNumber { if !baseValueIsNumber {
@@ -551,15 +551,15 @@ func (c memoryExecutorContext) math_Log(arguments []interface{}, row RowType) in
return math.Log(num) / math.Log(base) return math.Log(num) / math.Log(base)
} }
func (c memoryExecutorContext) math_NumberBin(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_NumberBin(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem1, row) ex := r.resolveSelectItem(exItem1)
binSize := 1.0 binSize := 1.0
if len(arguments) > 1 { if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
binSizeValueObject := c.getFieldValue(exItem2, row) binSizeValueObject := r.resolveSelectItem(exItem2)
binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject) binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject)
if !binSizeValueIsNumber { if !binSizeValueIsNumber {
@@ -584,11 +584,11 @@ func (c memoryExecutorContext) math_NumberBin(arguments []interface{}, row RowTy
return math.Floor(num/binSize) * binSize return math.Floor(num/binSize) * binSize
} }
func (c memoryExecutorContext) math_Pi() interface{} { func (r rowContext) math_Pi() interface{} {
return math.Pi return math.Pi
} }
func (c memoryExecutorContext) math_Rand() interface{} { func (r rowContext) math_Rand() interface{} {
return rand.Float64() return rand.Float64()
} }

View File

@@ -13,137 +13,174 @@ import (
) )
type RowType interface{} type RowType interface{}
type RowWithJoins map[string]RowType type rowContext struct {
type ExpressionType interface{} tables map[string]RowType
type memoryExecutorContext struct {
parameters map[string]interface{} parameters map[string]interface{}
grouppedRows []rowContext
} }
func Execute(query parsers.SelectStmt, data []RowType) []RowType { func ExecuteQuery(query parsers.SelectStmt, documents []RowType) []RowType {
ctx := memoryExecutorContext{ currentDocuments := make([]rowContext, 0)
parameters: query.Parameters, for _, doc := range documents {
currentDocuments = append(currentDocuments, resolveFrom(query, doc)...)
} }
joinedRows := make([]RowWithJoins, 0) // Handle JOINS
for _, row := range data { nextDocuments := make([]rowContext, 0)
// Perform joins for _, currentDocument := range currentDocuments {
dataTables := map[string][]RowType{} rowContexts := currentDocument.handleJoin(query)
nextDocuments = append(nextDocuments, rowContexts...)
for _, join := range query.JoinItems {
joinedData := ctx.getFieldValue(join.SelectItem, row)
if joinedDataArray, isArray := joinedData.([]map[string]interface{}); isArray {
var rows []RowType
for _, m := range joinedDataArray {
rows = append(rows, RowType(m))
}
dataTables[join.Table.Value] = rows
}
}
// Generate flat rows
flatRows := []RowWithJoins{
{query.Table.Value: row},
}
for joinedTableName, joinedTable := range dataTables {
flatRows = zipRows(flatRows, joinedTableName, joinedTable)
} }
currentDocuments = nextDocuments
// Apply filters // Apply filters
filteredRows := []RowWithJoins{} nextDocuments = make([]rowContext, 0)
for _, rowWithJoins := range flatRows { for _, currentDocument := range currentDocuments {
if ctx.evaluateFilters(query.Filters, rowWithJoins) { if currentDocument.applyFilters(query.Filters) {
filteredRows = append(filteredRows, rowWithJoins) nextDocuments = append(nextDocuments, currentDocument)
} }
} }
currentDocuments = nextDocuments
joinedRows = append(joinedRows, filteredRows...)
}
// Apply order // Apply order
if query.OrderExpressions != nil && len(query.OrderExpressions) > 0 { if len(query.OrderExpressions) > 0 {
ctx.orderBy(query.OrderExpressions, joinedRows) applyOrder(currentDocuments, query.OrderExpressions)
} }
result := make([]RowType, 0) // Apply group by
if len(query.GroupBy) > 0 {
// Apply group currentDocuments = applyGroupBy(currentDocuments, query.GroupBy)
isGroupSelect := query.GroupBy != nil && len(query.GroupBy) > 0
if isGroupSelect {
result = ctx.groupBy(query, joinedRows)
} }
// Apply select // Apply select
if !isGroupSelect { projectedDocuments := applyProjection(currentDocuments, query.SelectItems, query.GroupBy)
selectedData := make([]RowType, 0)
if hasAggregateFunctions(query.SelectItems) {
// When can have aggregate functions without GROUP BY clause,
// we should aggregate all rows in that case
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, joinedRows))
} else {
for _, row := range joinedRows {
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, row))
}
}
result = selectedData
}
// Apply distinct // Apply distinct
if query.Distinct { if query.Distinct {
result = deduplicate(result) projectedDocuments = deduplicate(projectedDocuments)
} }
// Apply result limit // Apply result limit
if query.Count > 0 { if query.Count > 0 && len(projectedDocuments) > query.Count {
count := func() int { projectedDocuments = projectedDocuments[:query.Count]
if len(result) < query.Count {
return len(result)
}
return query.Count
}()
result = result[:count]
} }
return result return projectedDocuments
} }
func (c memoryExecutorContext) selectRow(selectItems []parsers.SelectItem, row interface{}) interface{} { func resolveFrom(query parsers.SelectStmt, doc RowType) []rowContext {
// When the first value is top level, select it instead initialRow, gotParentContext := doc.(rowContext)
if len(selectItems) > 0 && selectItems[0].IsTopLevel { if !gotParentContext {
return c.getFieldValue(selectItems[0], row) var initialTableName string
if query.Table.SelectItem.Type == parsers.SelectItemTypeSubQuery {
initialTableName = query.Table.SelectItem.Value.(parsers.SelectStmt).Table.Value
} }
// Construct a new row based on the selected columns if initialTableName == "" {
newRow := make(map[string]interface{}) initialTableName = query.Table.Value
for index, column := range selectItems { }
destinationName := column.Alias
if destinationName == "" { initialRow = rowContext{
if len(column.Path) > 0 { parameters: query.Parameters,
destinationName = column.Path[len(column.Path)-1] tables: map[string]RowType{
} else { initialTableName: doc,
destinationName = fmt.Sprintf("$%d", index+1) },
} }
} }
newRow[destinationName] = c.getFieldValue(column, row) if query.Table.SelectItem.Path != nil || query.Table.SelectItem.Type == parsers.SelectItemTypeSubQuery {
destinationTableName := query.Table.SelectItem.Alias
if destinationTableName == "" {
destinationTableName = query.Table.Value
} }
return newRow selectValue := initialRow.parseArray(query.Table.SelectItem)
rowContexts := make([]rowContext, len(selectValue))
for i, newRowData := range selectValue {
rowContexts[i].parameters = initialRow.parameters
rowContexts[i].tables = copyMap(initialRow.tables)
rowContexts[i].tables[destinationTableName] = newRowData
}
return rowContexts
} }
func (c memoryExecutorContext) evaluateFilters(expr ExpressionType, row RowWithJoins) bool { return []rowContext{initialRow}
if expr == nil { }
func (r rowContext) handleJoin(query parsers.SelectStmt) []rowContext {
currentDocuments := []rowContext{r}
for _, joinItem := range query.JoinItems {
nextDocuments := make([]rowContext, 0)
for _, currentDocument := range currentDocuments {
joinedItems := currentDocument.resolveJoinItemSelect(joinItem.SelectItem)
for _, joinedItem := range joinedItems {
tablesCopy := copyMap(currentDocument.tables)
tablesCopy[joinItem.Table.Value] = joinedItem
nextDocuments = append(nextDocuments, rowContext{
parameters: currentDocument.parameters,
tables: tablesCopy,
})
}
}
currentDocuments = nextDocuments
}
return currentDocuments
}
func (r rowContext) resolveJoinItemSelect(selectItem parsers.SelectItem) []RowType {
if selectItem.Path != nil || selectItem.Type == parsers.SelectItemTypeSubQuery {
selectValue := r.parseArray(selectItem)
documents := make([]RowType, len(selectValue))
for i, newRowData := range selectValue {
documents[i] = newRowData
}
return documents
}
return []RowType{}
}
func (r rowContext) applyFilters(filters interface{}) bool {
if filters == nil {
return true return true
} }
switch typedValue := expr.(type) { switch typedFilters := filters.(type) {
case parsers.ComparisonExpression: case parsers.ComparisonExpression:
leftValue := c.getExpressionParameterValue(typedValue.Left, row) return r.filters_ComparisonExpression(typedFilters)
rightValue := c.getExpressionParameterValue(typedValue.Right, row) case parsers.LogicalExpression:
return r.filters_LogicalExpression(typedFilters)
case parsers.Constant:
if value, ok := typedFilters.Value.(bool); ok {
return value
}
return false
case parsers.SelectItem:
resolvedValue := r.resolveSelectItem(typedFilters)
if value, ok := resolvedValue.(bool); ok {
return value
}
}
return false
}
func (r rowContext) filters_ComparisonExpression(expression parsers.ComparisonExpression) bool {
leftExpression, leftExpressionOk := expression.Left.(parsers.SelectItem)
rightExpression, rightExpressionOk := expression.Right.(parsers.SelectItem)
if !leftExpressionOk || !rightExpressionOk {
logger.Error("ComparisonExpression has incorrect Left or Right type")
return false
}
leftValue := r.resolveSelectItem(leftExpression)
rightValue := r.resolveSelectItem(rightExpression)
cmp := compareValues(leftValue, rightValue) cmp := compareValues(leftValue, rightValue)
switch typedValue.Operation { switch expression.Operation {
case "=": case "=":
return cmp == 0 return cmp == 0
case "!=": case "!=":
@@ -157,15 +194,19 @@ func (c memoryExecutorContext) evaluateFilters(expr ExpressionType, row RowWithJ
case ">=": case ">=":
return cmp >= 0 return cmp >= 0
} }
case parsers.LogicalExpression:
return false
}
func (r rowContext) filters_LogicalExpression(expression parsers.LogicalExpression) bool {
var result bool var result bool
for i, expression := range typedValue.Expressions { for i, subExpression := range expression.Expressions {
expressionResult := c.evaluateFilters(expression, row) expressionResult := r.applyFilters(subExpression)
if i == 0 { if i == 0 {
result = expressionResult result = expressionResult
} }
switch typedValue.Operation { switch expression.Operation {
case parsers.LogicalExpressionTypeAnd: case parsers.LogicalExpressionTypeAnd:
result = result && expressionResult result = result && expressionResult
if !result { if !result {
@@ -179,242 +220,363 @@ func (c memoryExecutorContext) evaluateFilters(expr ExpressionType, row RowWithJ
} }
} }
return result return result
case parsers.Constant:
if value, ok := typedValue.Value.(bool); ok {
return value
}
return false
case parsers.SelectItem:
resolvedValue := c.getFieldValue(typedValue, row)
if value, ok := resolvedValue.(bool); ok {
return value
}
}
return false
} }
func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row interface{}) interface{} { func applyOrder(documents []rowContext, orderExpressions []parsers.OrderExpression) {
if field.Type == parsers.SelectItemTypeArray { less := func(i, j int) bool {
for _, order := range orderExpressions {
val1 := documents[i].resolveSelectItem(order.SelectItem)
val2 := documents[j].resolveSelectItem(order.SelectItem)
cmp := compareValues(val1, val2)
if cmp != 0 {
if order.Direction == parsers.OrderDirectionDesc {
return cmp > 0
}
return cmp < 0
}
}
return i < j
}
sort.SliceStable(documents, less)
}
func applyGroupBy(documents []rowContext, groupBy []parsers.SelectItem) []rowContext {
groupedRows := make(map[string][]rowContext)
groupedKeys := make([]string, 0)
for _, row := range documents {
key := row.generateGroupByKey(groupBy)
if _, ok := groupedRows[key]; !ok {
groupedKeys = append(groupedKeys, key)
}
groupedRows[key] = append(groupedRows[key], row)
}
grouppedRows := make([]rowContext, 0)
for _, key := range groupedKeys {
grouppedRowContext := rowContext{
tables: groupedRows[key][0].tables,
parameters: groupedRows[key][0].parameters,
grouppedRows: groupedRows[key],
}
grouppedRows = append(grouppedRows, grouppedRowContext)
}
return grouppedRows
}
func (r rowContext) generateGroupByKey(groupBy []parsers.SelectItem) string {
var keyBuilder strings.Builder
for _, selectItem := range groupBy {
value := r.resolveSelectItem(selectItem)
keyBuilder.WriteString(fmt.Sprintf("%v", value))
keyBuilder.WriteString(":")
}
return keyBuilder.String()
}
func applyProjection(documents []rowContext, selectItems []parsers.SelectItem, groupBy []parsers.SelectItem) []RowType {
if len(documents) == 0 {
return []RowType{}
}
if hasAggregateFunctions(selectItems) && len(groupBy) == 0 {
// When can have aggregate functions without GROUP BY clause,
// we should aggregate all rows in that case
rowContext := rowContext{
tables: documents[0].tables,
parameters: documents[0].parameters,
grouppedRows: documents,
}
return []RowType{rowContext.applyProjection(selectItems)}
}
projectedDocuments := make([]RowType, len(documents))
for index, row := range documents {
projectedDocuments[index] = row.applyProjection(selectItems)
}
return projectedDocuments
}
func (r rowContext) applyProjection(selectItems []parsers.SelectItem) RowType {
// When the first value is top level, select it instead
if len(selectItems) > 0 && selectItems[0].IsTopLevel {
return r.resolveSelectItem(selectItems[0])
}
// Construct a new row based on the selected columns
row := make(map[string]interface{})
for index, selectItem := range selectItems {
destinationName := selectItem.Alias
if destinationName == "" {
if len(selectItem.Path) > 0 {
destinationName = selectItem.Path[len(selectItem.Path)-1]
} else {
destinationName = fmt.Sprintf("$%d", index+1)
}
}
row[destinationName] = r.resolveSelectItem(selectItem)
}
return row
}
func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{} {
if selectItem.Type == parsers.SelectItemTypeArray {
return r.selectItem_SelectItemTypeArray(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeObject {
return r.selectItem_SelectItemTypeObject(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeConstant {
return r.selectItem_SelectItemTypeConstant(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeSubQuery {
return r.selectItem_SelectItemTypeSubQuery(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeFunctionCall {
if typedFunctionCall, ok := selectItem.Value.(parsers.FunctionCall); ok {
return r.selectItem_SelectItemTypeFunctionCall(typedFunctionCall)
}
logger.Error("parsers.SelectItem has incorrect Value type (expected parsers.FunctionCall)")
return nil
}
return r.selectItem_SelectItemTypeField(selectItem)
}
func (r rowContext) selectItem_SelectItemTypeArray(selectItem parsers.SelectItem) interface{} {
arrayValue := make([]interface{}, 0) arrayValue := make([]interface{}, 0)
for _, selectItem := range field.SelectItems { for _, subSelectItem := range selectItem.SelectItems {
arrayValue = append(arrayValue, c.getFieldValue(selectItem, row)) arrayValue = append(arrayValue, r.resolveSelectItem(subSelectItem))
} }
return arrayValue return arrayValue
} }
if field.Type == parsers.SelectItemTypeObject { func (r rowContext) selectItem_SelectItemTypeObject(selectItem parsers.SelectItem) interface{} {
objectValue := make(map[string]interface{}) objectValue := make(map[string]interface{})
for _, selectItem := range field.SelectItems { for _, subSelectItem := range selectItem.SelectItems {
objectValue[selectItem.Alias] = c.getFieldValue(selectItem, row) objectValue[subSelectItem.Alias] = r.resolveSelectItem(subSelectItem)
} }
return objectValue return objectValue
} }
if field.Type == parsers.SelectItemTypeConstant { func (r rowContext) selectItem_SelectItemTypeConstant(selectItem parsers.SelectItem) interface{} {
var typedValue parsers.Constant var typedValue parsers.Constant
var ok bool var ok bool
if typedValue, ok = field.Value.(parsers.Constant); !ok { if typedValue, ok = selectItem.Value.(parsers.Constant); !ok {
// TODO: Handle error // TODO: Handle error
logger.Error("parsers.Constant has incorrect Value type") logger.Error("parsers.Constant has incorrect Value type")
} }
if typedValue.Type == parsers.ConstantTypeParameterConstant && if typedValue.Type == parsers.ConstantTypeParameterConstant &&
c.parameters != nil { r.parameters != nil {
if key, ok := typedValue.Value.(string); ok { if key, ok := typedValue.Value.(string); ok {
return c.parameters[key] return r.parameters[key]
} }
} }
return typedValue.Value return typedValue.Value
} }
rowValue := row func (r rowContext) selectItem_SelectItemTypeSubQuery(selectItem parsers.SelectItem) interface{} {
// Used for aggregates subQuery := selectItem.Value.(parsers.SelectStmt)
if array, isArray := row.([]RowWithJoins); isArray { subQueryResult := ExecuteQuery(
rowValue = array[0] subQuery,
[]RowType{r},
)
if subQuery.Exists {
return len(subQueryResult) > 0
} }
if field.Type == parsers.SelectItemTypeFunctionCall { return subQueryResult
var typedValue parsers.FunctionCall
var ok bool
if typedValue, ok = field.Value.(parsers.FunctionCall); !ok {
// TODO: Handle error
logger.Error("parsers.Constant has incorrect Value type")
} }
switch typedValue.Type { func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.FunctionCall) interface{} {
switch functionCall.Type {
case parsers.FunctionCallStringEquals: case parsers.FunctionCallStringEquals:
return c.strings_StringEquals(typedValue.Arguments, rowValue) return r.strings_StringEquals(functionCall.Arguments)
case parsers.FunctionCallContains: case parsers.FunctionCallContains:
return c.strings_Contains(typedValue.Arguments, rowValue) return r.strings_Contains(functionCall.Arguments)
case parsers.FunctionCallEndsWith: case parsers.FunctionCallEndsWith:
return c.strings_EndsWith(typedValue.Arguments, rowValue) return r.strings_EndsWith(functionCall.Arguments)
case parsers.FunctionCallStartsWith: case parsers.FunctionCallStartsWith:
return c.strings_StartsWith(typedValue.Arguments, rowValue) return r.strings_StartsWith(functionCall.Arguments)
case parsers.FunctionCallConcat: case parsers.FunctionCallConcat:
return c.strings_Concat(typedValue.Arguments, rowValue) return r.strings_Concat(functionCall.Arguments)
case parsers.FunctionCallIndexOf: case parsers.FunctionCallIndexOf:
return c.strings_IndexOf(typedValue.Arguments, rowValue) return r.strings_IndexOf(functionCall.Arguments)
case parsers.FunctionCallToString: case parsers.FunctionCallToString:
return c.strings_ToString(typedValue.Arguments, rowValue) return r.strings_ToString(functionCall.Arguments)
case parsers.FunctionCallUpper: case parsers.FunctionCallUpper:
return c.strings_Upper(typedValue.Arguments, rowValue) return r.strings_Upper(functionCall.Arguments)
case parsers.FunctionCallLower: case parsers.FunctionCallLower:
return c.strings_Lower(typedValue.Arguments, rowValue) return r.strings_Lower(functionCall.Arguments)
case parsers.FunctionCallLeft: case parsers.FunctionCallLeft:
return c.strings_Left(typedValue.Arguments, rowValue) return r.strings_Left(functionCall.Arguments)
case parsers.FunctionCallLength: case parsers.FunctionCallLength:
return c.strings_Length(typedValue.Arguments, rowValue) return r.strings_Length(functionCall.Arguments)
case parsers.FunctionCallLTrim: case parsers.FunctionCallLTrim:
return c.strings_LTrim(typedValue.Arguments, rowValue) return r.strings_LTrim(functionCall.Arguments)
case parsers.FunctionCallReplace: case parsers.FunctionCallReplace:
return c.strings_Replace(typedValue.Arguments, rowValue) return r.strings_Replace(functionCall.Arguments)
case parsers.FunctionCallReplicate: case parsers.FunctionCallReplicate:
return c.strings_Replicate(typedValue.Arguments, rowValue) return r.strings_Replicate(functionCall.Arguments)
case parsers.FunctionCallReverse: case parsers.FunctionCallReverse:
return c.strings_Reverse(typedValue.Arguments, rowValue) return r.strings_Reverse(functionCall.Arguments)
case parsers.FunctionCallRight: case parsers.FunctionCallRight:
return c.strings_Right(typedValue.Arguments, rowValue) return r.strings_Right(functionCall.Arguments)
case parsers.FunctionCallRTrim: case parsers.FunctionCallRTrim:
return c.strings_RTrim(typedValue.Arguments, rowValue) return r.strings_RTrim(functionCall.Arguments)
case parsers.FunctionCallSubstring: case parsers.FunctionCallSubstring:
return c.strings_Substring(typedValue.Arguments, rowValue) return r.strings_Substring(functionCall.Arguments)
case parsers.FunctionCallTrim: case parsers.FunctionCallTrim:
return c.strings_Trim(typedValue.Arguments, rowValue) return r.strings_Trim(functionCall.Arguments)
case parsers.FunctionCallIsDefined: case parsers.FunctionCallIsDefined:
return c.typeChecking_IsDefined(typedValue.Arguments, rowValue) return r.typeChecking_IsDefined(functionCall.Arguments)
case parsers.FunctionCallIsArray: case parsers.FunctionCallIsArray:
return c.typeChecking_IsArray(typedValue.Arguments, rowValue) return r.typeChecking_IsArray(functionCall.Arguments)
case parsers.FunctionCallIsBool: case parsers.FunctionCallIsBool:
return c.typeChecking_IsBool(typedValue.Arguments, rowValue) return r.typeChecking_IsBool(functionCall.Arguments)
case parsers.FunctionCallIsFiniteNumber: case parsers.FunctionCallIsFiniteNumber:
return c.typeChecking_IsFiniteNumber(typedValue.Arguments, rowValue) return r.typeChecking_IsFiniteNumber(functionCall.Arguments)
case parsers.FunctionCallIsInteger: case parsers.FunctionCallIsInteger:
return c.typeChecking_IsInteger(typedValue.Arguments, rowValue) return r.typeChecking_IsInteger(functionCall.Arguments)
case parsers.FunctionCallIsNull: case parsers.FunctionCallIsNull:
return c.typeChecking_IsNull(typedValue.Arguments, rowValue) return r.typeChecking_IsNull(functionCall.Arguments)
case parsers.FunctionCallIsNumber: case parsers.FunctionCallIsNumber:
return c.typeChecking_IsNumber(typedValue.Arguments, rowValue) return r.typeChecking_IsNumber(functionCall.Arguments)
case parsers.FunctionCallIsObject: case parsers.FunctionCallIsObject:
return c.typeChecking_IsObject(typedValue.Arguments, rowValue) return r.typeChecking_IsObject(functionCall.Arguments)
case parsers.FunctionCallIsPrimitive: case parsers.FunctionCallIsPrimitive:
return c.typeChecking_IsPrimitive(typedValue.Arguments, rowValue) return r.typeChecking_IsPrimitive(functionCall.Arguments)
case parsers.FunctionCallIsString: case parsers.FunctionCallIsString:
return c.typeChecking_IsString(typedValue.Arguments, rowValue) return r.typeChecking_IsString(functionCall.Arguments)
case parsers.FunctionCallArrayConcat: case parsers.FunctionCallArrayConcat:
return c.array_Concat(typedValue.Arguments, rowValue) return r.array_Concat(functionCall.Arguments)
case parsers.FunctionCallArrayLength: case parsers.FunctionCallArrayLength:
return c.array_Length(typedValue.Arguments, rowValue) return r.array_Length(functionCall.Arguments)
case parsers.FunctionCallArraySlice: case parsers.FunctionCallArraySlice:
return c.array_Slice(typedValue.Arguments, rowValue) return r.array_Slice(functionCall.Arguments)
case parsers.FunctionCallSetIntersect: case parsers.FunctionCallSetIntersect:
return c.set_Intersect(typedValue.Arguments, rowValue) return r.set_Intersect(functionCall.Arguments)
case parsers.FunctionCallSetUnion: case parsers.FunctionCallSetUnion:
return c.set_Union(typedValue.Arguments, rowValue) return r.set_Union(functionCall.Arguments)
case parsers.FunctionCallMathAbs: case parsers.FunctionCallMathAbs:
return c.math_Abs(typedValue.Arguments, rowValue) return r.math_Abs(functionCall.Arguments)
case parsers.FunctionCallMathAcos: case parsers.FunctionCallMathAcos:
return c.math_Acos(typedValue.Arguments, rowValue) return r.math_Acos(functionCall.Arguments)
case parsers.FunctionCallMathAsin: case parsers.FunctionCallMathAsin:
return c.math_Asin(typedValue.Arguments, rowValue) return r.math_Asin(functionCall.Arguments)
case parsers.FunctionCallMathAtan: case parsers.FunctionCallMathAtan:
return c.math_Atan(typedValue.Arguments, rowValue) return r.math_Atan(functionCall.Arguments)
case parsers.FunctionCallMathCeiling: case parsers.FunctionCallMathCeiling:
return c.math_Ceiling(typedValue.Arguments, rowValue) return r.math_Ceiling(functionCall.Arguments)
case parsers.FunctionCallMathCos: case parsers.FunctionCallMathCos:
return c.math_Cos(typedValue.Arguments, rowValue) return r.math_Cos(functionCall.Arguments)
case parsers.FunctionCallMathCot: case parsers.FunctionCallMathCot:
return c.math_Cot(typedValue.Arguments, rowValue) return r.math_Cot(functionCall.Arguments)
case parsers.FunctionCallMathDegrees: case parsers.FunctionCallMathDegrees:
return c.math_Degrees(typedValue.Arguments, rowValue) return r.math_Degrees(functionCall.Arguments)
case parsers.FunctionCallMathExp: case parsers.FunctionCallMathExp:
return c.math_Exp(typedValue.Arguments, rowValue) return r.math_Exp(functionCall.Arguments)
case parsers.FunctionCallMathFloor: case parsers.FunctionCallMathFloor:
return c.math_Floor(typedValue.Arguments, rowValue) return r.math_Floor(functionCall.Arguments)
case parsers.FunctionCallMathIntBitNot: case parsers.FunctionCallMathIntBitNot:
return c.math_IntBitNot(typedValue.Arguments, rowValue) return r.math_IntBitNot(functionCall.Arguments)
case parsers.FunctionCallMathLog10: case parsers.FunctionCallMathLog10:
return c.math_Log10(typedValue.Arguments, rowValue) return r.math_Log10(functionCall.Arguments)
case parsers.FunctionCallMathRadians: case parsers.FunctionCallMathRadians:
return c.math_Radians(typedValue.Arguments, rowValue) return r.math_Radians(functionCall.Arguments)
case parsers.FunctionCallMathRound: case parsers.FunctionCallMathRound:
return c.math_Round(typedValue.Arguments, rowValue) return r.math_Round(functionCall.Arguments)
case parsers.FunctionCallMathSign: case parsers.FunctionCallMathSign:
return c.math_Sign(typedValue.Arguments, rowValue) return r.math_Sign(functionCall.Arguments)
case parsers.FunctionCallMathSin: case parsers.FunctionCallMathSin:
return c.math_Sin(typedValue.Arguments, rowValue) return r.math_Sin(functionCall.Arguments)
case parsers.FunctionCallMathSqrt: case parsers.FunctionCallMathSqrt:
return c.math_Sqrt(typedValue.Arguments, rowValue) return r.math_Sqrt(functionCall.Arguments)
case parsers.FunctionCallMathSquare: case parsers.FunctionCallMathSquare:
return c.math_Square(typedValue.Arguments, rowValue) return r.math_Square(functionCall.Arguments)
case parsers.FunctionCallMathTan: case parsers.FunctionCallMathTan:
return c.math_Tan(typedValue.Arguments, rowValue) return r.math_Tan(functionCall.Arguments)
case parsers.FunctionCallMathTrunc: case parsers.FunctionCallMathTrunc:
return c.math_Trunc(typedValue.Arguments, rowValue) return r.math_Trunc(functionCall.Arguments)
case parsers.FunctionCallMathAtn2: case parsers.FunctionCallMathAtn2:
return c.math_Atn2(typedValue.Arguments, rowValue) return r.math_Atn2(functionCall.Arguments)
case parsers.FunctionCallMathIntAdd: case parsers.FunctionCallMathIntAdd:
return c.math_IntAdd(typedValue.Arguments, rowValue) return r.math_IntAdd(functionCall.Arguments)
case parsers.FunctionCallMathIntBitAnd: case parsers.FunctionCallMathIntBitAnd:
return c.math_IntBitAnd(typedValue.Arguments, rowValue) return r.math_IntBitAnd(functionCall.Arguments)
case parsers.FunctionCallMathIntBitLeftShift: case parsers.FunctionCallMathIntBitLeftShift:
return c.math_IntBitLeftShift(typedValue.Arguments, rowValue) return r.math_IntBitLeftShift(functionCall.Arguments)
case parsers.FunctionCallMathIntBitOr: case parsers.FunctionCallMathIntBitOr:
return c.math_IntBitOr(typedValue.Arguments, rowValue) return r.math_IntBitOr(functionCall.Arguments)
case parsers.FunctionCallMathIntBitRightShift: case parsers.FunctionCallMathIntBitRightShift:
return c.math_IntBitRightShift(typedValue.Arguments, rowValue) return r.math_IntBitRightShift(functionCall.Arguments)
case parsers.FunctionCallMathIntBitXor: case parsers.FunctionCallMathIntBitXor:
return c.math_IntBitXor(typedValue.Arguments, rowValue) return r.math_IntBitXor(functionCall.Arguments)
case parsers.FunctionCallMathIntDiv: case parsers.FunctionCallMathIntDiv:
return c.math_IntDiv(typedValue.Arguments, rowValue) return r.math_IntDiv(functionCall.Arguments)
case parsers.FunctionCallMathIntMod: case parsers.FunctionCallMathIntMod:
return c.math_IntMod(typedValue.Arguments, rowValue) return r.math_IntMod(functionCall.Arguments)
case parsers.FunctionCallMathIntMul: case parsers.FunctionCallMathIntMul:
return c.math_IntMul(typedValue.Arguments, rowValue) return r.math_IntMul(functionCall.Arguments)
case parsers.FunctionCallMathIntSub: case parsers.FunctionCallMathIntSub:
return c.math_IntSub(typedValue.Arguments, rowValue) return r.math_IntSub(functionCall.Arguments)
case parsers.FunctionCallMathPower: case parsers.FunctionCallMathPower:
return c.math_Power(typedValue.Arguments, rowValue) return r.math_Power(functionCall.Arguments)
case parsers.FunctionCallMathLog: case parsers.FunctionCallMathLog:
return c.math_Log(typedValue.Arguments, rowValue) return r.math_Log(functionCall.Arguments)
case parsers.FunctionCallMathNumberBin: case parsers.FunctionCallMathNumberBin:
return c.math_NumberBin(typedValue.Arguments, rowValue) return r.math_NumberBin(functionCall.Arguments)
case parsers.FunctionCallMathPi: case parsers.FunctionCallMathPi:
return c.math_Pi() return r.math_Pi()
case parsers.FunctionCallMathRand: case parsers.FunctionCallMathRand:
return c.math_Rand() return r.math_Rand()
case parsers.FunctionCallAggregateAvg: case parsers.FunctionCallAggregateAvg:
return c.aggregate_Avg(typedValue.Arguments, row) return r.aggregate_Avg(functionCall.Arguments)
case parsers.FunctionCallAggregateCount: case parsers.FunctionCallAggregateCount:
return c.aggregate_Count(typedValue.Arguments, row) return r.aggregate_Count(functionCall.Arguments)
case parsers.FunctionCallAggregateMax: case parsers.FunctionCallAggregateMax:
return c.aggregate_Max(typedValue.Arguments, row) return r.aggregate_Max(functionCall.Arguments)
case parsers.FunctionCallAggregateMin: case parsers.FunctionCallAggregateMin:
return c.aggregate_Min(typedValue.Arguments, row) return r.aggregate_Min(functionCall.Arguments)
case parsers.FunctionCallAggregateSum: case parsers.FunctionCallAggregateSum:
return c.aggregate_Sum(typedValue.Arguments, row) return r.aggregate_Sum(functionCall.Arguments)
case parsers.FunctionCallIn: case parsers.FunctionCallIn:
return c.misc_In(typedValue.Arguments, rowValue) return r.misc_In(functionCall.Arguments)
}
} }
value := rowValue logger.Errorf("Unknown function call type: %v", functionCall.Type)
if joinedRow, isRowWithJoins := value.(RowWithJoins); isRowWithJoins { return nil
value = joinedRow[field.Path[0]]
} }
if len(field.Path) > 1 { func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem) interface{} {
for _, pathSegment := range field.Path[1:] { value := r.tables[selectItem.Path[0]]
if len(selectItem.Path) > 1 {
for _, pathSegment := range selectItem.Path[1:] {
switch nestedValue := value.(type) { switch nestedValue := value.(type) {
case map[string]interface{}: case map[string]interface{}:
value = nestedValue[pathSegment] value = nestedValue[pathSegment]
case RowWithJoins: case map[string]RowType:
value = nestedValue[pathSegment] value = nestedValue[pathSegment]
case []int, []string, []interface{}: case []int, []string, []interface{}:
slice := reflect.ValueOf(nestedValue) slice := reflect.ValueOf(nestedValue)
@@ -428,82 +590,28 @@ func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row inter
} }
} }
} }
return value return value
} }
func (c memoryExecutorContext) getExpressionParameterValue( func hasAggregateFunctions(selectItems []parsers.SelectItem) bool {
parameter interface{}, if selectItems == nil {
row RowWithJoins, return false
) interface{} {
switch typedParameter := parameter.(type) {
case parsers.SelectItem:
return c.getFieldValue(typedParameter, row)
} }
logger.Error("getExpressionParameterValue - got incorrect parameter type") for _, selectItem := range selectItems {
if selectItem.Type == parsers.SelectItemTypeFunctionCall {
return nil if typedValue, ok := selectItem.Value.(parsers.FunctionCall); ok && slices.Contains[[]parsers.FunctionCallType](parsers.AggregateFunctions, typedValue.Type) {
} return true
func (c memoryExecutorContext) orderBy(orderBy []parsers.OrderExpression, data []RowWithJoins) {
less := func(i, j int) bool {
for _, order := range orderBy {
val1 := c.getFieldValue(order.SelectItem, data[i])
val2 := c.getFieldValue(order.SelectItem, data[j])
cmp := compareValues(val1, val2)
if cmp != 0 {
if order.Direction == parsers.OrderDirectionDesc {
return cmp > 0
}
return cmp < 0
} }
} }
return i < j
if hasAggregateFunctions(selectItem.SelectItems) {
return true
}
} }
sort.SliceStable(data, less) return false
}
func (c memoryExecutorContext) groupBy(selectStmt parsers.SelectStmt, data []RowWithJoins) []RowType {
groupedRows := make(map[string][]RowWithJoins)
groupedKeys := make([]string, 0)
// Group rows by group by columns
for _, row := range data {
key := c.generateGroupKey(selectStmt.GroupBy, row)
if _, ok := groupedRows[key]; !ok {
groupedKeys = append(groupedKeys, key)
}
groupedRows[key] = append(groupedRows[key], row)
}
// Aggregate each group
aggregatedRows := make([]RowType, 0)
for _, key := range groupedKeys {
groupRows := groupedRows[key]
aggregatedRow := c.aggregateGroup(selectStmt, groupRows)
aggregatedRows = append(aggregatedRows, aggregatedRow)
}
return aggregatedRows
}
func (c memoryExecutorContext) generateGroupKey(groupByFields []parsers.SelectItem, row RowWithJoins) string {
var keyBuilder strings.Builder
for _, column := range groupByFields {
fieldValue := c.getFieldValue(column, row)
keyBuilder.WriteString(fmt.Sprintf("%v", fieldValue))
keyBuilder.WriteString(":")
}
return keyBuilder.String()
}
func (c memoryExecutorContext) aggregateGroup(selectStmt parsers.SelectStmt, groupRows []RowWithJoins) RowType {
aggregatedRow := c.selectRow(selectStmt.SelectItems, groupRows)
return aggregatedRow
} }
func compareValues(val1, val2 interface{}) int { func compareValues(val1, val2 interface{}) int {
@@ -549,8 +657,9 @@ func compareValues(val1, val2 interface{}) int {
} }
} }
func deduplicate(slice []RowType) []RowType { func deduplicate[T RowType | interface{}](slice []T) []T {
var result []RowType var result []T
result = make([]T, 0)
for i := 0; i < len(slice); i++ { for i := 0; i < len(slice); i++ {
unique := true unique := true
@@ -569,42 +678,8 @@ func deduplicate(slice []RowType) []RowType {
return result return result
} }
func hasAggregateFunctions(selectItems []parsers.SelectItem) bool { func copyMap[T RowType | []RowType](originalMap map[string]T) map[string]T {
if selectItems == nil { targetMap := make(map[string]T)
return false
}
for _, selectItem := range selectItems {
if selectItem.Type == parsers.SelectItemTypeFunctionCall {
if typedValue, ok := selectItem.Value.(parsers.FunctionCall); ok && slices.Contains[[]parsers.FunctionCallType](parsers.AggregateFunctions, typedValue.Type) {
return true
}
}
if hasAggregateFunctions(selectItem.SelectItems) {
return true
}
}
return false
}
func zipRows(current []RowWithJoins, joinedTableName string, rowsToZip []RowType) []RowWithJoins {
resultMap := make([]RowWithJoins, 0)
for _, currentRow := range current {
for _, rowToZip := range rowsToZip {
newRow := copyMap(currentRow)
newRow[joinedTableName] = rowToZip
resultMap = append(resultMap, newRow)
}
}
return resultMap
}
func copyMap(originalMap map[string]RowType) map[string]RowType {
targetMap := make(map[string]RowType)
for k, v := range originalMap { for k, v := range originalMap {
targetMap[k] = v targetMap[k] = v

View File

@@ -4,11 +4,11 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) misc_In(arguments []interface{}, row RowType) bool { func (r rowContext) misc_In(arguments []interface{}) bool {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
for i := 1; i < len(arguments); i++ { for i := 1; i < len(arguments); i++ {
compareValue := c.getFieldValue(arguments[i].(parsers.SelectItem), row) compareValue := r.resolveSelectItem(arguments[i].(parsers.SelectItem))
if compareValues(value, compareValue) == 0 { if compareValues(value, compareValue) == 0 {
return true return true
} }

View File

@@ -14,7 +14,7 @@ func testQueryExecute(
data []memoryexecutor.RowType, data []memoryexecutor.RowType,
expectedData []memoryexecutor.RowType, expectedData []memoryexecutor.RowType,
) { ) {
result := memoryexecutor.Execute(query, data) result := memoryexecutor.ExecuteQuery(query, data)
if !reflect.DeepEqual(result, expectedData) { if !reflect.DeepEqual(result, 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)
@@ -25,8 +25,20 @@ func Test_Execute(t *testing.T) {
mockData := []memoryexecutor.RowType{ mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "12345", "pk": 123, "_self": "self1", "_rid": "rid1", "_ts": 123456, "isCool": false}, map[string]interface{}{"id": "12345", "pk": 123, "_self": "self1", "_rid": "rid1", "_ts": 123456, "isCool": false},
map[string]interface{}{"id": "67890", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true}, map[string]interface{}{"id": "67890", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true},
map[string]interface{}{"id": "456", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true}, map[string]interface{}{
map[string]interface{}{"id": "123", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true}, "id": "456", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true,
"tags": []map[string]interface{}{
{"name": "tag-a"},
{"name": "tag-b"},
},
},
map[string]interface{}{
"id": "123", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true,
"tags": []map[string]interface{}{
{"name": "tag-b"},
{"name": "tag-c"},
},
},
} }
t.Run("Should execute SELECT with ORDER BY", func(t *testing.T) { t.Run("Should execute SELECT with ORDER BY", func(t *testing.T) {
@@ -124,4 +136,31 @@ func Test_Execute(t *testing.T) {
}, },
) )
}) })
t.Run("Should execute IN selector", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "name"},
Type: parsers.SelectItemTypeField,
},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"name": "tag-a"},
map[string]interface{}{"name": "tag-b"},
map[string]interface{}{"name": "tag-b"},
map[string]interface{}{"name": "tag-c"},
},
)
})
} }

View File

@@ -8,10 +8,10 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) strings_StringEquals(arguments []interface{}, row RowType) bool { func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
return strings.EqualFold(str1, str2) return strings.EqualFold(str1, str2)
@@ -20,10 +20,10 @@ func (c memoryExecutorContext) strings_StringEquals(arguments []interface{}, row
return str1 == str2 return str1 == str2
} }
func (c memoryExecutorContext) strings_Contains(arguments []interface{}, row RowType) bool { func (r rowContext) strings_Contains(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
@@ -33,10 +33,10 @@ func (c memoryExecutorContext) strings_Contains(arguments []interface{}, row Row
return strings.Contains(str1, str2) return strings.Contains(str1, str2)
} }
func (c memoryExecutorContext) strings_EndsWith(arguments []interface{}, row RowType) bool { func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
@@ -46,10 +46,10 @@ func (c memoryExecutorContext) strings_EndsWith(arguments []interface{}, row Row
return strings.HasSuffix(str1, str2) return strings.HasSuffix(str1, str2)
} }
func (c memoryExecutorContext) strings_StartsWith(arguments []interface{}, row RowType) bool { func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
@@ -59,12 +59,12 @@ func (c memoryExecutorContext) strings_StartsWith(arguments []interface{}, row R
return strings.HasPrefix(str1, str2) return strings.HasPrefix(str1, str2)
} }
func (c memoryExecutorContext) strings_Concat(arguments []interface{}, row RowType) string { func (r rowContext) strings_Concat(arguments []interface{}) string {
result := "" result := ""
for _, arg := range arguments { for _, arg := range arguments {
if selectItem, ok := arg.(parsers.SelectItem); ok { if selectItem, ok := arg.(parsers.SelectItem); ok {
value := c.getFieldValue(selectItem, row) value := r.resolveSelectItem(selectItem)
result += convertToString(value) result += convertToString(value)
} }
} }
@@ -72,13 +72,13 @@ func (c memoryExecutorContext) strings_Concat(arguments []interface{}, row RowTy
return result return result
} }
func (c memoryExecutorContext) strings_IndexOf(arguments []interface{}, row RowType) int { func (r rowContext) strings_IndexOf(arguments []interface{}) int {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
start := 0 start := 0
if len(arguments) > 2 && arguments[2] != nil { if len(arguments) > 2 && arguments[2] != nil {
if startPos, ok := c.getFieldValue(arguments[2].(parsers.SelectItem), row).(int); ok { if startPos, ok := r.resolveSelectItem(arguments[2].(parsers.SelectItem)).(int); ok {
start = startPos start = startPos
} }
} }
@@ -97,26 +97,26 @@ func (c memoryExecutorContext) strings_IndexOf(arguments []interface{}, row RowT
} }
} }
func (c memoryExecutorContext) strings_ToString(arguments []interface{}, row RowType) string { func (r rowContext) strings_ToString(arguments []interface{}) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return convertToString(value) return convertToString(value)
} }
func (c memoryExecutorContext) strings_Upper(arguments []interface{}, row RowType) string { func (r rowContext) strings_Upper(arguments []interface{}) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return strings.ToUpper(convertToString(value)) return strings.ToUpper(convertToString(value))
} }
func (c memoryExecutorContext) strings_Lower(arguments []interface{}, row RowType) string { func (r rowContext) strings_Lower(arguments []interface{}) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return strings.ToLower(convertToString(value)) return strings.ToLower(convertToString(value))
} }
func (c memoryExecutorContext) strings_Left(arguments []interface{}, row RowType) string { func (r rowContext) strings_Left(arguments []interface{}) string {
var ok bool var ok bool
var length int var length int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Left - got parameters of wrong type") logger.Error("strings_Left - got parameters of wrong type")
@@ -134,28 +134,28 @@ func (c memoryExecutorContext) strings_Left(arguments []interface{}, row RowType
return str[:length] return str[:length]
} }
func (c memoryExecutorContext) strings_Length(arguments []interface{}, row RowType) int { func (r rowContext) strings_Length(arguments []interface{}) int {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return len(str) return len(str)
} }
func (c memoryExecutorContext) strings_LTrim(arguments []interface{}, row RowType) string { func (r rowContext) strings_LTrim(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return strings.TrimLeft(str, " ") return strings.TrimLeft(str, " ")
} }
func (c memoryExecutorContext) strings_Replace(arguments []interface{}, row RowType) string { func (r rowContext) strings_Replace(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
oldStr := c.parseString(arguments[1], row) oldStr := r.parseString(arguments[1])
newStr := c.parseString(arguments[2], row) newStr := r.parseString(arguments[2])
return strings.Replace(str, oldStr, newStr, -1) return strings.Replace(str, oldStr, newStr, -1)
} }
func (c memoryExecutorContext) strings_Replicate(arguments []interface{}, row RowType) string { func (r rowContext) strings_Replicate(arguments []interface{}) string {
var ok bool var ok bool
var times int var times int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
timesEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if times, ok = timesEx.(int); !ok { if times, ok = timesEx.(int); !ok {
logger.Error("strings_Replicate - got parameters of wrong type") logger.Error("strings_Replicate - got parameters of wrong type")
@@ -173,8 +173,8 @@ func (c memoryExecutorContext) strings_Replicate(arguments []interface{}, row Ro
return strings.Repeat(str, times) return strings.Repeat(str, times)
} }
func (c memoryExecutorContext) strings_Reverse(arguments []interface{}, row RowType) string { func (r rowContext) strings_Reverse(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
runes := []rune(str) runes := []rune(str)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
@@ -184,11 +184,11 @@ func (c memoryExecutorContext) strings_Reverse(arguments []interface{}, row RowT
return string(runes) return string(runes)
} }
func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowType) string { func (r rowContext) strings_Right(arguments []interface{}) string {
var ok bool var ok bool
var length int var length int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Right - got parameters of wrong type") logger.Error("strings_Right - got parameters of wrong type")
@@ -206,18 +206,18 @@ func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowTyp
return str[len(str)-length:] return str[len(str)-length:]
} }
func (c memoryExecutorContext) strings_RTrim(arguments []interface{}, row RowType) string { func (r rowContext) strings_RTrim(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return strings.TrimRight(str, " ") return strings.TrimRight(str, " ")
} }
func (c memoryExecutorContext) strings_Substring(arguments []interface{}, row RowType) string { func (r rowContext) strings_Substring(arguments []interface{}) string {
var ok bool var ok bool
var startPos int var startPos int
var length int var length int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
startPosEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) startPosEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if startPos, ok = startPosEx.(int); !ok { if startPos, ok = startPosEx.(int); !ok {
logger.Error("strings_Substring - got start parameters of wrong type") logger.Error("strings_Substring - got start parameters of wrong type")
@@ -240,16 +240,16 @@ func (c memoryExecutorContext) strings_Substring(arguments []interface{}, row Ro
return str[startPos:endPos] return str[startPos:endPos]
} }
func (c memoryExecutorContext) strings_Trim(arguments []interface{}, row RowType) string { func (r rowContext) strings_Trim(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return strings.TrimSpace(str) return strings.TrimSpace(str)
} }
func (c memoryExecutorContext) getBoolFlag(arguments []interface{}, row RowType) bool { func (r rowContext) getBoolFlag(arguments []interface{}) bool {
ignoreCase := false ignoreCase := false
if len(arguments) > 2 && arguments[2] != nil { if len(arguments) > 2 && arguments[2] != nil {
ignoreCaseItem := arguments[2].(parsers.SelectItem) ignoreCaseItem := arguments[2].(parsers.SelectItem)
if value, ok := c.getFieldValue(ignoreCaseItem, row).(bool); ok { if value, ok := r.resolveSelectItem(ignoreCaseItem).(bool); ok {
ignoreCase = value ignoreCase = value
} }
} }
@@ -257,9 +257,9 @@ func (c memoryExecutorContext) getBoolFlag(arguments []interface{}, row RowType)
return ignoreCase return ignoreCase
} }
func (c memoryExecutorContext) parseString(argument interface{}, row RowType) string { func (r rowContext) parseString(argument interface{}) string {
exItem := argument.(parsers.SelectItem) exItem := argument.(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
if str1, ok := ex.(string); ok { if str1, ok := ex.(string); ok {
return str1 return str1
} }

View File

@@ -0,0 +1,155 @@
package memoryexecutor_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
)
func Test_Execute_SubQuery(t *testing.T) {
mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "123", "info": map[string]interface{}{"name": "row-1"}},
map[string]interface{}{
"id": "456",
"info": map[string]interface{}{"name": "row-2"},
"tags": []map[string]interface{}{
{"name": "tag-a"},
{"name": "tag-b"},
},
},
map[string]interface{}{
"id": "789",
"info": map[string]interface{}{"name": "row-3"},
"tags": []map[string]interface{}{
{"name": "tag-b"},
{"name": "tag-c"},
},
},
}
t.Run("Should execute FROM subquery", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "name"}},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Alias: "c",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
Table: parsers.Table{Value: "cc"},
SelectItems: []parsers.SelectItem{
{Path: []string{"cc", "info"}, IsTopLevel: true},
},
},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"name": "row-1"},
map[string]interface{}{"name": "row-2"},
map[string]interface{}{"name": "row-3"},
},
)
})
t.Run("Should execute JOIN subquery", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{Path: []string{"cc", "name"}},
},
Table: parsers.Table{
Value: "c",
},
JoinItems: []parsers.JoinItem{
{
Table: parsers.Table{
Value: "cc",
},
SelectItem: parsers.SelectItem{
Alias: "cc",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}},
},
Table: parsers.Table{
Value: "tag",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "456", "name": "tag-a"},
map[string]interface{}{"id": "456", "name": "tag-b"},
map[string]interface{}{"id": "789", "name": "tag-b"},
map[string]interface{}{"id": "789", "name": "tag-c"},
},
)
})
t.Run("Should execute JOIN EXISTS subquery", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
},
Table: parsers.Table{
Value: "c",
},
JoinItems: []parsers.JoinItem{
{
Table: parsers.Table{Value: "hasTags"},
SelectItem: parsers.SelectItem{
Alias: "hasTags",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
IsTopLevel: true,
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}},
},
Table: parsers.Table{
Value: "tag",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
Exists: true,
},
},
},
},
},
},
},
Filters: parsers.SelectItem{
Path: []string{"hasTags"},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "456"},
map[string]interface{}{"id": "789"},
},
)
})
}

View File

@@ -6,32 +6,32 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) typeChecking_IsDefined(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsDefined(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
return ex != nil return ex != nil
} }
func (c memoryExecutorContext) typeChecking_IsArray(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsArray(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isArray := ex.([]interface{}) _, isArray := ex.([]interface{})
return isArray return isArray
} }
func (c memoryExecutorContext) typeChecking_IsBool(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsBool(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isBool := ex.(bool) _, isBool := ex.(bool)
return isBool return isBool
} }
func (c memoryExecutorContext) typeChecking_IsFiniteNumber(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsFiniteNumber(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch num := ex.(type) { switch num := ex.(type) {
case int: case int:
@@ -43,41 +43,41 @@ func (c memoryExecutorContext) typeChecking_IsFiniteNumber(arguments []interface
} }
} }
func (c memoryExecutorContext) typeChecking_IsInteger(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsInteger(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isInt := ex.(int) _, isInt := ex.(int)
return isInt return isInt
} }
func (c memoryExecutorContext) typeChecking_IsNull(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsNull(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
return ex == nil return ex == nil
} }
func (c memoryExecutorContext) typeChecking_IsNumber(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsNumber(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isFloat := ex.(float64) _, isFloat := ex.(float64)
_, isInt := ex.(int) _, isInt := ex.(int)
return isFloat || isInt return isFloat || isInt
} }
func (c memoryExecutorContext) typeChecking_IsObject(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsObject(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isObject := ex.(map[string]interface{}) _, isObject := ex.(map[string]interface{})
return isObject return isObject
} }
func (c memoryExecutorContext) typeChecking_IsPrimitive(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsPrimitive(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch ex.(type) { switch ex.(type) {
case bool, string, float64, int, nil: case bool, string, float64, int, nil:
@@ -87,9 +87,9 @@ func (c memoryExecutorContext) typeChecking_IsPrimitive(arguments []interface{},
} }
} }
func (c memoryExecutorContext) typeChecking_IsString(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsString(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isStr := ex.(string) _, isStr := ex.(string)
return isStr return isStr

View File

@@ -0,0 +1,52 @@
package main
import "C"
import (
"encoding/json"
"github.com/pikami/cosmium/api"
"github.com/pikami/cosmium/api/config"
"github.com/pikami/cosmium/internal/repositories"
)
var currentServer *api.Server
//export Configure
func Configure(configurationJSON *C.char) bool {
var configuration config.ServerConfig
err := json.Unmarshal([]byte(C.GoString(configurationJSON)), &configuration)
if err != nil {
return false
}
config.Config = configuration
return true
}
//export InitializeRepository
func InitializeRepository() {
repositories.InitializeRepository()
}
//export StartAPI
func StartAPI() {
currentServer = api.StartAPI()
}
//export StopAPI
func StopAPI() {
if currentServer == nil {
currentServer.StopServer <- true
currentServer = nil
}
}
//export GetState
func GetState() *C.char {
stateJSON, err := repositories.GetState()
if err != nil {
return nil
}
return C.CString(stateJSON)
}
func main() {}