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
uses: actions/setup-go@v5
with:
go-version: 1.21.6
go-version: 1.22.0
- name: Docker Login
uses: docker/login-action@v3
with:

View File

@@ -1,5 +1,6 @@
builds:
- binary: cosmium
main: ./cmd/server
goos:
- darwin
- linux
@@ -27,11 +28,14 @@ brews:
email: git@pikami.org
dockers:
- image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}"
- "ghcr.io/pikami/{{ .ProjectName }}:latest"
- id: docker-linux-amd64
goos: linux
goarch: amd64
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-amd64"
dockerfile: Dockerfile
use: docker
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--pull"
@@ -42,6 +46,38 @@ dockers:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--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:
name_template: 'checksums.txt'

View File

@@ -4,28 +4,44 @@ GOTEST=$(GOCMD) test
GOCLEAN=$(GOCMD) clean
BINARY_NAME=cosmium
SERVER_LOCATION=./cmd/server
SHARED_LIB_LOCATION=./sharedlibrary
SHARED_LIB_OPT=-buildmode=c-shared
DIST_DIR=dist
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:
@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:
@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:
@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:
@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:
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:
- **Linux**: cosmium-linux-amd64
- **Linux on ARM**: cosmium-linux-arm64
- **macOS**: cosmium-darwin-amd64
- **macOS on Apple Silicon**: cosmium-darwin-arm64
- **Windows**: cosmium-windows-amd64.exe
- **Windows on ARM**: cosmium-windows-arm64.exe
### Running Cosmium

View File

@@ -8,5 +8,11 @@ import (
)
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
import (
"context"
"fmt"
"net/http"
@@ -12,6 +13,10 @@ import (
tlsprovider "github.com/pikami/cosmium/internal/tls_provider"
)
type Server struct {
StopServer chan interface{}
}
func CreateRouter() *gin.Engine {
router := gin.Default(func(e *gin.Engine) {
e.RedirectTrailingSlash = false
@@ -57,42 +62,60 @@ func CreateRouter() *gin.Engine {
return router
}
func StartAPI() {
func StartAPI() *Server {
if !config.Config.Debug {
gin.SetMode(gin.ReleaseMode)
}
router := CreateRouter()
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 != "" {
err := router.RunTLS(
listenAddress,
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
err := server.ListenAndServeTLS(
config.Config.TLS_CertificatePath,
config.Config.TLS_CertificateKey)
if err != nil {
logger.Error("Failed to start HTTPS server:", err)
}
return
}
if config.Config.DisableTls {
router.Run(listenAddress)
}
} else {
tlsConfig := tlsprovider.GetDefaultTlsConfig()
server := &http.Server{
Addr: listenAddress,
Handler: router.Handler(),
TLSConfig: tlsConfig,
}
server.TLSConfig = tlsConfig
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
err := server.ListenAndServeTLS("", "")
if err != nil {
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/httptest"
"reflect"
"sync"
"testing"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"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) {

View File

@@ -15,18 +15,21 @@ func main() {
repositories.InitializeRepository()
go api.StartAPI()
server := api.StartAPI()
waitForExit()
waitForExit(server)
}
func waitForExit() {
func waitForExit(server *api.Server) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// Block until a exit signal is received
<-sigs
// Stop the server
server.StopServer <- true
if 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 |
| ----------------------------- | ----------- |
| Subqueries | No |
| Joins | No |
| Subqueries | Yes |
| Joins | Yes |
| Computed properties | No |
| Coalesce operators | No |
| Bitwise operators | No |
@@ -63,9 +63,11 @@ Cosmium strives to support the core features of Cosmos DB, including:
### Array Functions
| Function | Implemented |
| -------------- | ----------- |
| ------------------ | ----------- |
| ARRAY_CONCAT | Yes |
| ARRAY_CONTAINS | No |
| ARRAY_CONTAINS_ANY | No |
| ARRAY_CONTAINS_ALL | No |
| ARRAY_LENGTH | Yes |
| ARRAY_SLICE | Yes |
| CHOOSE | No |

30
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/pikami/cosmium
go 1.21.6
go 1.22.0
require (
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/google/uuid v1.6.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 (
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/bytedance/sonic v1.11.8 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/bytedance/sonic v1.12.6 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // 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/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.21.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/goccy/go-json v0.10.4 // 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/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.36.0 // 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/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/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA=
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
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/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
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/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/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
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/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
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/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/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0=
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
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/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
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/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.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
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.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
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/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
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.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.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.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.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.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/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/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/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
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/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
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.20.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.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
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/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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=
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) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok {
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) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok {
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 {
storeState.Lock()
defer storeState.Unlock()
if _, ok := storeState.Databases[databaseId]; !ok {
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) {
storeState.Lock()
defer storeState.Unlock()
var ok bool
var database repositorymodels.Database
if database, ok = storeState.Databases[databaseId]; !ok {

View File

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

View File

@@ -15,6 +15,9 @@ import (
)
func GetAllDocuments(databaseId string, collectionId string) ([]repositorymodels.Document, repositorymodels.RepositoryStatus) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok {
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) {
storeState.RLock()
defer storeState.RUnlock()
if _, ok := storeState.Databases[databaseId]; !ok {
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 {
storeState.Lock()
defer storeState.Unlock()
if _, ok := storeState.Databases[databaseId]; !ok {
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) {
storeState.Lock()
defer storeState.Unlock()
var ok bool
var documentId string
var database repositorymodels.Database
@@ -111,7 +123,7 @@ func ExecuteQueryDocuments(databaseId string, collectionId string, query string,
if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok {
typedQuery.Parameters = queryParameters
return memoryexecutor.Execute(typedQuery, covDocs), repositorymodels.StatusOk
return memoryexecutor.ExecuteQuery(typedQuery, covDocs), repositorymodels.StatusOk
}
return nil, repositorymodels.BadRequest

View File

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

View File

@@ -66,6 +66,9 @@ func LoadStateFS(filePath string) {
}
func SaveStateFS(filePath string) {
storeState.RLock()
defer storeState.RUnlock()
data, err := json.MarshalIndent(storeState, "", "\t")
if err != nil {
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))
}
func GetState() repositorymodels.State {
return storeState
func GetState() (string, error) {
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 {

View File

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

View File

@@ -5,6 +5,7 @@ type SelectStmt struct {
Table Table
JoinItems []JoinItem
Filters interface{}
Exists bool
Distinct bool
Count int
Offset int
@@ -15,6 +16,7 @@ type SelectStmt struct {
type Table struct {
Value string
SelectItem SelectItem
}
type JoinItem struct {
@@ -30,6 +32,7 @@ const (
SelectItemTypeArray
SelectItemTypeConstant
SelectItemTypeFunctionCall
SelectItemTypeSubQuery
)
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"
func makeSelectStmt(
columns, table, joinItems,
columns, fromClause, joinItems,
whereClause interface{}, distinctClause interface{},
count interface{}, groupByClause interface{}, orderList interface{},
offsetClause interface{},
) (parsers.SelectStmt, error) {
selectStmt := parsers.SelectStmt{
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 {
@@ -56,10 +59,18 @@ func makeSelectStmt(
}
func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) {
return parsers.JoinItem{
Table: table.(parsers.Table),
SelectItem: column.(parsers.SelectItem),
}, nil
joinItem := parsers.JoinItem{}
if selectItem, isSelectItem := column.(parsers.SelectItem); isSelectItem {
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) {
@@ -177,13 +188,13 @@ SelectStmt <- Select ws
distinctClause:DistinctClause? ws
topClause:TopClause? ws
columns:Selection ws
From ws table:TableName ws
joinClauses:JoinClause* ws
fromClause:FromClause? ws
joinClauses:(ws join:JoinClause { return join, nil })* ws
whereClause:(ws Where ws condition:Condition { return condition, nil })?
groupByClause:(ws GroupBy ws columns:ColumnList { return columns, nil })?
orderByClause:OrderByClause?
offsetClause:OffsetClause? {
return makeSelectStmt(columns, table, joinClauses, whereClause,
orderByClause:(ws order:OrderByClause { return order, nil })?
offsetClause:(ws offset:OffsetClause { return offset, nil })? {
return makeSelectStmt(columns, fromClause, joinClauses, whereClause,
distinctClause, topClause, groupByClause, orderByClause, offsetClause)
}
@@ -193,8 +204,49 @@ TopClause <- Top ws count:Integer {
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 {
return makeJoin(table, column)
} / Join ws subQuery:SubQuerySelectItem {
return makeJoin(nil, subQuery)
}
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)
}
SelectItem <- selectItem:(Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
var itemResult parsers.SelectItem
switch typedValue := selectItem.(type) {
case parsers.SelectItem:
@@ -322,11 +374,13 @@ From <- "FROM"i
Join <- "JOIN"i
Exists <- "EXISTS"i
Where <- "WHERE"i
And <- "AND"i
Or <- "OR"i
Or <- "OR"i wss
GroupBy <- "GROUP"i ws "BY"i
@@ -677,4 +731,6 @@ non_escape_character <- !(escape_character) char:.
ws <- [ \t\n\r]*
wss <- [ \t\n\r]+
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"
)
func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowType) interface{} {
func (r rowContext) aggregate_Avg(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem)
sum := 0.0
count := 0
if array, isArray := row.([]RowWithJoins); isArray {
for _, item := range array {
value := c.getFieldValue(selectExpression, item)
for _, item := range r.grouppedRows {
value := item.resolveSelectItem(selectExpression)
if numericValue, ok := value.(float64); ok {
sum += numericValue
count++
@@ -22,7 +21,6 @@ func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowTyp
count++
}
}
}
if count > 0 {
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)
count := 0
if array, isArray := row.([]RowWithJoins); isArray {
for _, item := range array {
value := c.getFieldValue(selectExpression, item)
for _, item := range r.grouppedRows {
value := item.resolveSelectItem(selectExpression)
if value != nil {
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)
max := 0.0
count := 0
if array, isArray := row.([]RowWithJoins); isArray {
for _, item := range array {
value := c.getFieldValue(selectExpression, item)
for _, item := range r.grouppedRows {
value := item.resolveSelectItem(selectExpression)
if numericValue, ok := value.(float64); ok {
if numericValue > max {
max = numericValue
@@ -67,7 +62,6 @@ func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowTyp
count++
}
}
}
if count > 0 {
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)
min := math.MaxFloat64
count := 0
if array, isArray := row.([]RowWithJoins); isArray {
for _, item := range array {
value := c.getFieldValue(selectExpression, item)
for _, item := range r.grouppedRows {
value := item.resolveSelectItem(selectExpression)
if numericValue, ok := value.(float64); ok {
if numericValue < min {
min = numericValue
@@ -96,7 +89,6 @@ func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowTyp
count++
}
}
}
if count > 0 {
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)
sum := 0.0
count := 0
if array, isArray := row.([]RowWithJoins); isArray {
for _, item := range array {
value := c.getFieldValue(selectExpression, item)
for _, item := range r.grouppedRows {
value := item.resolveSelectItem(selectExpression)
if numericValue, ok := value.(float64); ok {
sum += numericValue
count++
@@ -121,7 +112,6 @@ func (c memoryExecutorContext) aggregate_Sum(arguments []interface{}, row RowTyp
count++
}
}
}
if count > 0 {
return sum

View File

@@ -7,17 +7,17 @@ import (
"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{}
for _, arg := range arguments {
array := c.parseArray(arg, row)
array := r.parseArray(arg)
result = append(result, array...)
}
return result
}
func (c memoryExecutorContext) array_Length(arguments []interface{}, row RowType) int {
array := c.parseArray(arguments[0], row)
func (r rowContext) array_Length(arguments []interface{}) int {
array := r.parseArray(arguments[0])
if array == nil {
return 0
}
@@ -25,15 +25,15 @@ func (c memoryExecutorContext) array_Length(arguments []interface{}, row RowType
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 start int
var length int
array := c.parseArray(arguments[0], row)
startEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
array := r.parseArray(arguments[0])
startEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
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 {
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]
}
func (c memoryExecutorContext) set_Intersect(arguments []interface{}, row RowType) []interface{} {
set1 := c.parseArray(arguments[0], row)
set2 := c.parseArray(arguments[1], row)
func (r rowContext) set_Intersect(arguments []interface{}) []interface{} {
set1 := r.parseArray(arguments[0])
set2 := r.parseArray(arguments[1])
intersection := make(map[interface{}]struct{})
if set1 == nil || set2 == nil {
@@ -88,9 +88,9 @@ func (c memoryExecutorContext) set_Intersect(arguments []interface{}, row RowTyp
return result
}
func (c memoryExecutorContext) set_Union(arguments []interface{}, row RowType) []interface{} {
set1 := c.parseArray(arguments[0], row)
set2 := c.parseArray(arguments[1], row)
func (r rowContext) set_Union(arguments []interface{}) []interface{} {
set1 := r.parseArray(arguments[0])
set2 := r.parseArray(arguments[1])
var result []interface{}
union := make(map[interface{}]struct{})
@@ -111,9 +111,9 @@ func (c memoryExecutorContext) set_Union(arguments []interface{}, row RowType) [
return result
}
func (c memoryExecutorContext) parseArray(argument interface{}, row RowType) []interface{} {
func (r rowContext) parseArray(argument interface{}) []interface{} {
exItem := argument.(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
arrValue := reflect.ValueOf(ex)
if arrValue.Kind() != reflect.Slice {

View File

@@ -8,9 +8,9 @@ import (
"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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -44,9 +44,9 @@ func (c memoryExecutorContext) math_Acos(arguments []interface{}, row RowType) i
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -62,9 +62,9 @@ func (c memoryExecutorContext) math_Asin(arguments []interface{}, row RowType) i
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -75,9 +75,9 @@ func (c memoryExecutorContext) math_Atan(arguments []interface{}, row RowType) i
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -103,9 +103,9 @@ func (c memoryExecutorContext) math_Cos(arguments []interface{}, row RowType) in
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -121,9 +121,9 @@ func (c memoryExecutorContext) math_Cot(arguments []interface{}, row RowType) in
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -134,9 +134,9 @@ func (c memoryExecutorContext) math_Degrees(arguments []interface{}, row RowType
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -147,9 +147,9 @@ func (c memoryExecutorContext) math_Exp(arguments []interface{}, row RowType) in
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -193,9 +193,9 @@ func (c memoryExecutorContext) math_Log10(arguments []interface{}, row RowType)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -206,9 +206,9 @@ func (c memoryExecutorContext) math_Radians(arguments []interface{}, row RowType
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -261,9 +261,9 @@ func (c memoryExecutorContext) math_Sin(arguments []interface{}, row RowType) in
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -274,9 +274,9 @@ func (c memoryExecutorContext) math_Sqrt(arguments []interface{}, row RowType) i
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -287,9 +287,9 @@ func (c memoryExecutorContext) math_Square(arguments []interface{}, row RowType)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
@@ -300,9 +300,9 @@ func (c memoryExecutorContext) math_Tan(arguments []interface{}, row RowType) in
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch val := ex.(type) {
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
y, yIsNumber := numToFloat64(ex1)
x, xIsNumber := numToFloat64(ex2)
@@ -332,11 +332,11 @@ func (c memoryExecutorContext) math_Atn2(arguments []interface{}, row RowType) i
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
ex1Number, ex1IsNumber := numToInt(ex1)
ex2Number, ex2IsNumber := numToInt(ex2)
@@ -349,11 +349,11 @@ func (c memoryExecutorContext) math_IntAdd(arguments []interface{}, row RowType)
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
ex1Int, ex1IsInt := numToInt(ex1)
ex2Int, ex2IsInt := numToInt(ex2)
@@ -366,11 +366,11 @@ func (c memoryExecutorContext) math_IntBitAnd(arguments []interface{}, row RowTy
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2)
@@ -383,11 +383,11 @@ func (c memoryExecutorContext) math_IntBitLeftShift(arguments []interface{}, row
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
@@ -400,11 +400,11 @@ func (c memoryExecutorContext) math_IntBitOr(arguments []interface{}, row RowTyp
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2)
@@ -417,11 +417,11 @@ func (c memoryExecutorContext) math_IntBitRightShift(arguments []interface{}, ro
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
@@ -434,11 +434,11 @@ func (c memoryExecutorContext) math_IntBitXor(arguments []interface{}, row RowTy
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
@@ -451,11 +451,11 @@ func (c memoryExecutorContext) math_IntDiv(arguments []interface{}, row RowType)
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
@@ -468,11 +468,11 @@ func (c memoryExecutorContext) math_IntMul(arguments []interface{}, row RowType)
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
@@ -485,11 +485,11 @@ func (c memoryExecutorContext) math_IntSub(arguments []interface{}, row RowType)
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
@@ -502,11 +502,11 @@ func (c memoryExecutorContext) math_IntMod(arguments []interface{}, row RowType)
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)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1 := r.resolveSelectItem(exItem1)
ex2 := r.resolveSelectItem(exItem2)
base, baseIsNumber := numToFloat64(ex1)
exponent, exponentIsNumber := numToFloat64(ex2)
@@ -519,14 +519,14 @@ func (c memoryExecutorContext) math_Power(arguments []interface{}, row RowType)
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)
ex := c.getFieldValue(exItem1, row)
ex := r.resolveSelectItem(exItem1)
var base float64 = math.E
if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem)
baseValueObject := c.getFieldValue(exItem2, row)
baseValueObject := r.resolveSelectItem(exItem2)
baseValue, baseValueIsNumber := numToFloat64(baseValueObject)
if !baseValueIsNumber {
@@ -551,15 +551,15 @@ func (c memoryExecutorContext) math_Log(arguments []interface{}, row RowType) in
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)
ex := c.getFieldValue(exItem1, row)
ex := r.resolveSelectItem(exItem1)
binSize := 1.0
if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem)
binSizeValueObject := c.getFieldValue(exItem2, row)
binSizeValueObject := r.resolveSelectItem(exItem2)
binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject)
if !binSizeValueIsNumber {
@@ -584,11 +584,11 @@ func (c memoryExecutorContext) math_NumberBin(arguments []interface{}, row RowTy
return math.Floor(num/binSize) * binSize
}
func (c memoryExecutorContext) math_Pi() interface{} {
func (r rowContext) math_Pi() interface{} {
return math.Pi
}
func (c memoryExecutorContext) math_Rand() interface{} {
func (r rowContext) math_Rand() interface{} {
return rand.Float64()
}

View File

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

View File

@@ -4,11 +4,11 @@ import (
"github.com/pikami/cosmium/parsers"
)
func (c memoryExecutorContext) misc_In(arguments []interface{}, row RowType) bool {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row)
func (r rowContext) misc_In(arguments []interface{}) bool {
value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
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 {
return true
}

View File

@@ -14,7 +14,7 @@ func testQueryExecute(
data []memoryexecutor.RowType,
expectedData []memoryexecutor.RowType,
) {
result := memoryexecutor.Execute(query, data)
result := memoryexecutor.ExecuteQuery(query, data)
if !reflect.DeepEqual(result, expectedData) {
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{
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": "456", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true},
map[string]interface{}{"id": "123", "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,
"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) {
@@ -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"
)
func (c memoryExecutorContext) strings_StringEquals(arguments []interface{}, row RowType) bool {
str1 := c.parseString(arguments[0], row)
str2 := c.parseString(arguments[1], row)
ignoreCase := c.getBoolFlag(arguments, row)
func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
str1 := r.parseString(arguments[0])
str2 := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments)
if ignoreCase {
return strings.EqualFold(str1, str2)
@@ -20,10 +20,10 @@ func (c memoryExecutorContext) strings_StringEquals(arguments []interface{}, row
return str1 == str2
}
func (c memoryExecutorContext) strings_Contains(arguments []interface{}, row RowType) bool {
str1 := c.parseString(arguments[0], row)
str2 := c.parseString(arguments[1], row)
ignoreCase := c.getBoolFlag(arguments, row)
func (r rowContext) strings_Contains(arguments []interface{}) bool {
str1 := r.parseString(arguments[0])
str2 := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments)
if ignoreCase {
str1 = strings.ToLower(str1)
@@ -33,10 +33,10 @@ func (c memoryExecutorContext) strings_Contains(arguments []interface{}, row Row
return strings.Contains(str1, str2)
}
func (c memoryExecutorContext) strings_EndsWith(arguments []interface{}, row RowType) bool {
str1 := c.parseString(arguments[0], row)
str2 := c.parseString(arguments[1], row)
ignoreCase := c.getBoolFlag(arguments, row)
func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
str1 := r.parseString(arguments[0])
str2 := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments)
if ignoreCase {
str1 = strings.ToLower(str1)
@@ -46,10 +46,10 @@ func (c memoryExecutorContext) strings_EndsWith(arguments []interface{}, row Row
return strings.HasSuffix(str1, str2)
}
func (c memoryExecutorContext) strings_StartsWith(arguments []interface{}, row RowType) bool {
str1 := c.parseString(arguments[0], row)
str2 := c.parseString(arguments[1], row)
ignoreCase := c.getBoolFlag(arguments, row)
func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
str1 := r.parseString(arguments[0])
str2 := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments)
if ignoreCase {
str1 = strings.ToLower(str1)
@@ -59,12 +59,12 @@ func (c memoryExecutorContext) strings_StartsWith(arguments []interface{}, row R
return strings.HasPrefix(str1, str2)
}
func (c memoryExecutorContext) strings_Concat(arguments []interface{}, row RowType) string {
func (r rowContext) strings_Concat(arguments []interface{}) string {
result := ""
for _, arg := range arguments {
if selectItem, ok := arg.(parsers.SelectItem); ok {
value := c.getFieldValue(selectItem, row)
value := r.resolveSelectItem(selectItem)
result += convertToString(value)
}
}
@@ -72,13 +72,13 @@ func (c memoryExecutorContext) strings_Concat(arguments []interface{}, row RowTy
return result
}
func (c memoryExecutorContext) strings_IndexOf(arguments []interface{}, row RowType) int {
str1 := c.parseString(arguments[0], row)
str2 := c.parseString(arguments[1], row)
func (r rowContext) strings_IndexOf(arguments []interface{}) int {
str1 := r.parseString(arguments[0])
str2 := r.parseString(arguments[1])
start := 0
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
}
}
@@ -97,26 +97,26 @@ func (c memoryExecutorContext) strings_IndexOf(arguments []interface{}, row RowT
}
}
func (c memoryExecutorContext) strings_ToString(arguments []interface{}, row RowType) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row)
func (r rowContext) strings_ToString(arguments []interface{}) string {
value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return convertToString(value)
}
func (c memoryExecutorContext) strings_Upper(arguments []interface{}, row RowType) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row)
func (r rowContext) strings_Upper(arguments []interface{}) string {
value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return strings.ToUpper(convertToString(value))
}
func (c memoryExecutorContext) strings_Lower(arguments []interface{}, row RowType) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row)
func (r rowContext) strings_Lower(arguments []interface{}) string {
value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
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 length int
str := c.parseString(arguments[0], row)
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
str := r.parseString(arguments[0])
lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok {
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]
}
func (c memoryExecutorContext) strings_Length(arguments []interface{}, row RowType) int {
str := c.parseString(arguments[0], row)
func (r rowContext) strings_Length(arguments []interface{}) int {
str := r.parseString(arguments[0])
return len(str)
}
func (c memoryExecutorContext) strings_LTrim(arguments []interface{}, row RowType) string {
str := c.parseString(arguments[0], row)
func (r rowContext) strings_LTrim(arguments []interface{}) string {
str := r.parseString(arguments[0])
return strings.TrimLeft(str, " ")
}
func (c memoryExecutorContext) strings_Replace(arguments []interface{}, row RowType) string {
str := c.parseString(arguments[0], row)
oldStr := c.parseString(arguments[1], row)
newStr := c.parseString(arguments[2], row)
func (r rowContext) strings_Replace(arguments []interface{}) string {
str := r.parseString(arguments[0])
oldStr := r.parseString(arguments[1])
newStr := r.parseString(arguments[2])
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 times int
str := c.parseString(arguments[0], row)
timesEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
str := r.parseString(arguments[0])
timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if times, ok = timesEx.(int); !ok {
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)
}
func (c memoryExecutorContext) strings_Reverse(arguments []interface{}, row RowType) string {
str := c.parseString(arguments[0], row)
func (r rowContext) strings_Reverse(arguments []interface{}) string {
str := r.parseString(arguments[0])
runes := []rune(str)
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)
}
func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowType) string {
func (r rowContext) strings_Right(arguments []interface{}) string {
var ok bool
var length int
str := c.parseString(arguments[0], row)
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
str := r.parseString(arguments[0])
lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok {
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:]
}
func (c memoryExecutorContext) strings_RTrim(arguments []interface{}, row RowType) string {
str := c.parseString(arguments[0], row)
func (r rowContext) strings_RTrim(arguments []interface{}) string {
str := r.parseString(arguments[0])
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 startPos int
var length int
str := c.parseString(arguments[0], row)
startPosEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row)
str := r.parseString(arguments[0])
startPosEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if startPos, ok = startPosEx.(int); !ok {
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]
}
func (c memoryExecutorContext) strings_Trim(arguments []interface{}, row RowType) string {
str := c.parseString(arguments[0], row)
func (r rowContext) strings_Trim(arguments []interface{}) string {
str := r.parseString(arguments[0])
return strings.TrimSpace(str)
}
func (c memoryExecutorContext) getBoolFlag(arguments []interface{}, row RowType) bool {
func (r rowContext) getBoolFlag(arguments []interface{}) bool {
ignoreCase := false
if len(arguments) > 2 && arguments[2] != nil {
ignoreCaseItem := arguments[2].(parsers.SelectItem)
if value, ok := c.getFieldValue(ignoreCaseItem, row).(bool); ok {
if value, ok := r.resolveSelectItem(ignoreCaseItem).(bool); ok {
ignoreCase = value
}
}
@@ -257,9 +257,9 @@ func (c memoryExecutorContext) getBoolFlag(arguments []interface{}, row RowType)
return ignoreCase
}
func (c memoryExecutorContext) parseString(argument interface{}, row RowType) string {
func (r rowContext) parseString(argument interface{}) string {
exItem := argument.(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
if str1, ok := ex.(string); ok {
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"
)
func (c memoryExecutorContext) typeChecking_IsDefined(arguments []interface{}, row RowType) bool {
func (r rowContext) typeChecking_IsDefined(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
_, isArray := ex.([]interface{})
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
_, isBool := ex.(bool)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch num := ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
_, isInt := ex.(int)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
_, isFloat := ex.(float64)
_, isInt := ex.(int)
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
_, isObject := ex.(map[string]interface{})
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
switch ex.(type) {
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)
ex := c.getFieldValue(exItem, row)
ex := r.resolveSelectItem(exItem)
_, isStr := ex.(string)
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() {}