30 Commits

Author SHA1 Message Date
Pijus Kamandulis
bd4fe5abec Update azcosmos package 2025-02-25 20:43:23 +02:00
Pijus Kamandulis
f062e03f0c Update packages 2025-02-25 19:56:02 +02:00
Pijus Kamandulis
058b3271b7 OrderBy should bring NULL values to front 2025-02-25 19:47:29 +02:00
Pijus Kamandulis
1711c8fb5c Implement NOT logical operator 2025-02-25 19:33:32 +02:00
Pijus Kamandulis
851b3ca3a8 Fix IN clause with function calls 2025-02-20 18:45:20 +02:00
Pijus Kamandulis
d27c633e1d Better handling when passing null to string functions 2025-02-18 20:11:11 +02:00
Pijus Kamandulis
3987df89c0 Upgrade to golang 1.24.0 2025-02-18 19:16:21 +02:00
Pijus Kamandulis
6e3f4169a1 Fix 'ComparisonOperator' parsing 2025-02-18 19:12:08 +02:00
Pijus Kamandulis
14c5400d23 Keep old explorer images tagged with version 2025-02-09 22:42:51 +02:00
Pijus Kamandulis
1cf5ae92f4 Shared library stability improvements 2025-02-09 11:45:10 +02:00
Pijus Kamandulis
5d99b653cc Generate more realistic resource ids 2025-02-09 00:36:35 +02:00
Pijus Kamandulis
787cdb33cf Fix OFFSET clause 2025-02-08 15:28:06 +02:00
Pijus Kamandulis
5caa829ac1 Implement 'Transactional batch operations' 2025-02-04 20:35:15 +02:00
Pijus Kamandulis
887d456ad4 Return error code if server fails to start 2025-02-03 22:58:45 +02:00
Pijus Kamandulis
da1566875b Wait for server shutdown when stopping server 2025-02-03 22:21:54 +02:00
Pijus Kamandulis
3fee3bc816 Fix ARRAY_CONTAINS partial matches for nested objects 2025-02-03 19:29:29 +02:00
Pijus Kamandulis
8657c48fc8 Added support for table alias; Make AS keyword optional #9 2025-02-03 19:02:12 +02:00
Pijus Kamandulis
e080888c20 Add shared-libraries to release 2025-01-28 23:28:23 +02:00
Pijus Kamandulis
b8d79fd945 Upgrade upload-artifact pipeline action 2025-01-28 21:31:05 +02:00
Pijus Kamandulis
f25cb7fb03 Stamp binaries with version control information 2025-01-28 21:15:49 +02:00
Pijus Kamandulis
125f10d8a2 Add more error handling and mutex guards 2025-01-27 21:09:37 +02:00
Pijus Kamandulis
d6b816b55a Fix docker tag 2025-01-25 21:17:32 +02:00
Pijus Kamandulis
12215fba76 Update dependancies 2025-01-25 21:11:42 +02:00
Pijus Kamandulis
a1793c17ab Added docker image with explorer included 2025-01-25 20:20:55 +02:00
Pijus Kamandulis
96d3a0a7ae Handle server close; Update logger 2025-01-15 00:26:15 +02:00
Pijus Kamandulis
8b8b087aab Added setting for LogLevel 2025-01-09 21:07:41 +02:00
Pijus Kamandulis
c2c9dc03b3 Fixed issue with wrong signature generation for pkrange requests 2025-01-09 20:19:28 +02:00
Pijus Kamandulis
d86bac7d79 Implement CRUD for UDFs, SPs and Triggers 2025-01-06 20:45:43 +02:00
Pijus Kamandulis
69b76c1c3e Simplify constant initialization in unit tests 2024-12-26 20:43:57 +02:00
Pijus Kamandulis
8e3db3e44d Added support for 'ARRAY_CONTAINS', 'ARRAY_CONTAINS_ANY' and 'ARRAY_CONTAINS_ALL' functions 2024-12-26 20:27:59 +02:00
78 changed files with 5072 additions and 2594 deletions

View File

@@ -15,16 +15,17 @@ jobs:
uses: crazy-max/ghaction-xgo@v3.1.0 uses: crazy-max/ghaction-xgo@v3.1.0
with: with:
xgo_version: latest xgo_version: latest
go_version: 1.22.0 go_version: 1.24.0
dest: dist dest: dist
pkg: sharedlibrary pkg: sharedlibrary
prefix: cosmium prefix: cosmium
targets: linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64 targets: linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64
v: true v: true
buildmode: c-shared buildmode: c-shared
buildvcs: true
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: shared-libraries name: shared-libraries
path: dist/* path: dist/*

View File

@@ -17,16 +17,32 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.22.0 go-version: 1.24.0
- name: Cross-Compile with xgo
uses: crazy-max/ghaction-xgo@v3.1.0
with:
xgo_version: latest
go_version: 1.24.0
dest: sharedlibrary_dist
pkg: sharedlibrary
prefix: cosmium
targets: linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64
v: true
buildmode: c-shared
buildvcs: true
- name: Docker Login - name: Docker Login
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5 uses: goreleaser/goreleaser-action@v5
with: with:

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
dist/ dist/
sharedlibrary_dist/
ignored/ ignored/
explorer_www/ explorer_www/
main main

View File

@@ -26,6 +26,23 @@ brews:
commit_author: commit_author:
name: pikami name: pikami
email: git@pikami.org email: git@pikami.org
skip_upload: auto
archives:
- id: bundle
format: tar.gz
format_overrides:
- goos: windows
format: zip
- id: shared-libraries
meta: true
format: "tar.gz"
wrap_in_directory: true
name_template: "{{ .ProjectName }}_{{ .Version }}_shared-libraries"
files:
- LICENSE
- README.md
- sharedlibrary_dist/**
dockers: dockers:
- id: docker-linux-amd64 - id: docker-linux-amd64
@@ -33,7 +50,6 @@ dockers:
goarch: amd64 goarch: amd64
image_templates: image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64" - "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-amd64"
dockerfile: Dockerfile dockerfile: Dockerfile
use: buildx use: buildx
build_flag_templates: build_flag_templates:
@@ -51,9 +67,7 @@ dockers:
goarch: arm64 goarch: arm64
image_templates: image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64" - "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8" - "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8"
- "ghcr.io/pikami/{{ .ProjectName }}:latest-arm64v8"
dockerfile: Dockerfile dockerfile: Dockerfile
use: buildx use: buildx
build_flag_templates: build_flag_templates:
@@ -66,18 +80,67 @@ dockers:
- "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.version={{.Version}}"
- id: docker-explorer-linux-amd64
goos: linux
goarch: amd64
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-amd64"
dockerfile: Explorer.Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--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}}"
- id: docker-explorer-linux-arm64
goos: linux
goarch: arm64
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-arm64v8"
dockerfile: Explorer.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: docker_manifests:
- name_template: 'ghcr.io/pikami/{{ .ProjectName }}:latest' - name_template: 'ghcr.io/pikami/{{ .ProjectName }}:latest'
image_templates: skip_push: auto
- "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: image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64" - "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64" - "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8" - "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8"
- name_template: 'ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}'
skip_push: auto
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-arm64v8"
- name_template: 'ghcr.io/pikami/{{ .ProjectName }}:explorer'
skip_push: auto
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-arm64v8"
- name_template: 'ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer'
skip_push: auto
image_templates:
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-amd64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-arm64"
- "ghcr.io/pikami/{{ .ProjectName }}:{{ .Version }}-explorer-arm64v8"
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'

View File

@@ -1,4 +1,4 @@
FROM scratch FROM alpine:latest
WORKDIR /app WORKDIR /app
COPY cosmium /app/cosmium COPY cosmium /app/cosmium

9
Explorer.Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM ghcr.io/cosmiumdev/cosmos-explorer-base:latest AS explorer-base
FROM alpine:latest
COPY --from=explorer-base /cosmos-explorer /cosmos-explorer
WORKDIR /app
COPY cosmium /app/cosmium
ENTRYPOINT ["/app/cosmium", "-ExplorerDir", "/cosmos-explorer"]

View File

@@ -9,7 +9,7 @@ SERVER_LOCATION=./cmd/server
SHARED_LIB_LOCATION=./sharedlibrary SHARED_LIB_LOCATION=./sharedlibrary
SHARED_LIB_OPT=-buildmode=c-shared SHARED_LIB_OPT=-buildmode=c-shared
XGO_TARGETS=linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64 XGO_TARGETS=linux/amd64,linux/arm64,windows/amd64,windows/arm64,darwin/amd64,darwin/arm64
GOVERSION=1.22.0 GOVERSION=1.24.0
DIST_DIR=dist DIST_DIR=dist

View File

@@ -50,10 +50,17 @@ AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJI
If you want to run Cosmos DB Explorer alongside Cosmium, you'll need to build it yourself and point the `-ExplorerDir` argument to the dist directory. Please refer to the [Cosmos DB Explorer repository](https://github.com/Azure/cosmos-explorer) for instructions on building the application. If you want to run Cosmos DB Explorer alongside Cosmium, you'll need to build it yourself and point the `-ExplorerDir` argument to the dist directory. Please refer to the [Cosmos DB Explorer repository](https://github.com/Azure/cosmos-explorer) for instructions on building the application.
There's also a prebuilt docker image that includes the explorer: `ghcr.io/pikami/cosmium:explorer`
Once running, the explorer can be reached by navigating following URL: `https://127.0.0.1:8081/_explorer/` (might be different depending on your configuration). Once running, the explorer can be reached by navigating following URL: `https://127.0.0.1:8081/_explorer/` (might be different depending on your configuration).
### Running with docker (optional) ### Running with docker (optional)
There are two docker tags available:
- ghcr.io/pikami/cosmium:latest - Cosmium core service
- ghcr.io/pikami/cosmium:explorer - Cosmium with database explorer available on `https://127.0.0.1:8081/_explorer/`
If you wan to run the application using docker, configure it using environment variables see example: If you wan to run the application using docker, configure it using environment variables see example:
```sh ```sh
@@ -61,7 +68,7 @@ docker run --rm \
-e COSMIUM_PERSIST=/save.json \ -e COSMIUM_PERSIST=/save.json \
-v ./save.json:/save.json \ -v ./save.json:/save.json \
-p 8081:8081 \ -p 8081:8081 \
ghcr.io/pikami/cosmium ghcr.io/pikami/cosmium # or `ghcr.io/pikami/cosmium:explorer`
``` ```
### SSL Certificate ### SSL Certificate
@@ -78,7 +85,7 @@ To disable SSL and run Cosmium on HTTP instead, you can use the `-DisableTls` fl
- **-InitialData**: Path to JSON containing initial state - **-InitialData**: Path to JSON containing initial state
- **-Persist**: Saves data to the given path on application exit (When `-InitialData` argument is not supplied, it will try to load data from path supplied in `-Persist`) - **-Persist**: Saves data to the given path on application exit (When `-InitialData` argument is not supplied, it will try to load data from path supplied in `-Persist`)
- **-Port**: Listen port (default 8081) - **-Port**: Listen port (default 8081)
- **-Debug**: Runs application in debug mode, this provides additional logging - **-LogLevel**: Sets the logging level (one of: debug, info, error, silent) (default info)
These arguments allow you to configure various aspects of Cosmium's behavior according to your requirements. These arguments allow you to configure various aspects of Cosmium's behavior according to your requirements.
@@ -90,7 +97,7 @@ All mentioned arguments can also be set using environment variables:
- **COSMIUM_INITIALDATA** for `-InitialData` - **COSMIUM_INITIALDATA** for `-InitialData`
- **COSMIUM_PERSIST** for `-Persist` - **COSMIUM_PERSIST** for `-Persist`
- **COSMIUM_PORT** for `-Port` - **COSMIUM_PORT** for `-Port`
- **COSMIUM_DEBUG** for `-Debug` - **COSMIUM_LOGLEVEL** for `-LogLevel`
# License # License

24
api/api_models/models.go Normal file
View File

@@ -0,0 +1,24 @@
package apimodels
const (
BatchOperationTypeCreate = "Create"
BatchOperationTypeDelete = "Delete"
BatchOperationTypeReplace = "Replace"
BatchOperationTypeUpsert = "Upsert"
BatchOperationTypeRead = "Read"
BatchOperationTypePatch = "Patch"
)
type BatchOperation struct {
OperationType string `json:"operationType"`
Id string `json:"id"`
ResourceBody map[string]interface{} `json:"resourceBody"`
}
type BatchOperationResult struct {
StatusCode int `json:"statusCode"`
RequestCharge float64 `json:"requestCharge"`
ResourceBody map[string]interface{} `json:"resourceBody"`
Etag string `json:"etag"`
Message string `json:"message"`
}

View File

@@ -7,18 +7,21 @@ import (
) )
type ApiServer struct { type ApiServer struct {
stopServer chan interface{} stopServer chan interface{}
isActive bool onServerShutdown chan interface{}
router *gin.Engine isActive bool
config config.ServerConfig router *gin.Engine
config *config.ServerConfig
} }
func NewApiServer(dataRepository *repositories.DataRepository, config config.ServerConfig) *ApiServer { func NewApiServer(dataRepository *repositories.DataRepository, config *config.ServerConfig) *ApiServer {
stopChan := make(chan interface{}) stopChan := make(chan interface{})
onServerShutdownChan := make(chan interface{})
apiServer := &ApiServer{ apiServer := &ApiServer{
stopServer: stopChan, stopServer: stopChan,
config: config, onServerShutdown: onServerShutdownChan,
config: config,
} }
apiServer.CreateRouter(dataRepository) apiServer.CreateRouter(dataRepository)
@@ -32,4 +35,5 @@ func (s *ApiServer) GetRouter() *gin.Engine {
func (s *ApiServer) Stop() { func (s *ApiServer) Stop() {
s.stopServer <- true s.stopServer <- true
<-s.onServerShutdown
} }

View File

@@ -26,7 +26,8 @@ func ParseFlags() ServerConfig {
disableAuthentication := flag.Bool("DisableAuth", false, "Disable authentication") disableAuthentication := flag.Bool("DisableAuth", false, "Disable authentication")
disableTls := flag.Bool("DisableTls", false, "Disable TLS, serve over HTTP") disableTls := flag.Bool("DisableTls", false, "Disable TLS, serve over HTTP")
persistDataPath := flag.String("Persist", "", "Saves data to given path on application exit") persistDataPath := flag.String("Persist", "", "Saves data to given path on application exit")
debug := flag.Bool("Debug", false, "Runs application in debug mode, this provides additional logging") logLevel := NewEnumValue("info", []string{"debug", "info", "error", "silent"})
flag.Var(logLevel, "LogLevel", fmt.Sprintf("Sets the logging level %s", logLevel.AllowedValuesList()))
flag.Parse() flag.Parse()
setFlagsFromEnvironment() setFlagsFromEnvironment()
@@ -41,8 +42,8 @@ func ParseFlags() ServerConfig {
config.PersistDataFilePath = *persistDataPath config.PersistDataFilePath = *persistDataPath
config.DisableAuth = *disableAuthentication config.DisableAuth = *disableAuthentication
config.DisableTls = *disableTls config.DisableTls = *disableTls
config.Debug = *debug
config.AccountKey = *accountKey config.AccountKey = *accountKey
config.LogLevel = logLevel.value
config.PopulateCalculatedFields() config.PopulateCalculatedFields()
@@ -54,7 +55,19 @@ func (c *ServerConfig) PopulateCalculatedFields() {
c.DatabaseDomain = c.Host c.DatabaseDomain = c.Host
c.DatabaseEndpoint = fmt.Sprintf("https://%s:%d/", c.Host, c.Port) c.DatabaseEndpoint = fmt.Sprintf("https://%s:%d/", c.Host, c.Port)
c.ExplorerBaseUrlLocation = ExplorerBaseUrlLocation c.ExplorerBaseUrlLocation = ExplorerBaseUrlLocation
logger.EnableDebugOutput = c.Debug
switch c.LogLevel {
case "debug":
logger.SetLogLevel(logger.LogLevelDebug)
case "info":
logger.SetLogLevel(logger.LogLevelInfo)
case "error":
logger.SetLogLevel(logger.LogLevelError)
case "silent":
logger.SetLogLevel(logger.LogLevelSilent)
default:
logger.SetLogLevel(logger.LogLevelInfo)
}
} }
func (c *ServerConfig) ApplyDefaultsToEmptyFields() { func (c *ServerConfig) ApplyDefaultsToEmptyFields() {

36
api/config/enumFlag.go Normal file
View File

@@ -0,0 +1,36 @@
package config
import (
"fmt"
"strings"
)
type EnumValue struct {
allowedValues []string
value string
}
func (e *EnumValue) String() string {
return e.value
}
func (e *EnumValue) Set(v string) error {
for _, allowed := range e.allowedValues {
if v == allowed {
e.value = v
return nil
}
}
return fmt.Errorf("invalid value %q, must be one of: %s", v, strings.Join(e.allowedValues, ", "))
}
func NewEnumValue(defaultValue string, allowedValues []string) *EnumValue {
return &EnumValue{
allowedValues: allowedValues,
value: defaultValue,
}
}
func (e *EnumValue) AllowedValuesList() string {
return fmt.Sprintf("(one of: %s)", strings.Join(e.allowedValues, ", "))
}

View File

@@ -15,6 +15,6 @@ type ServerConfig struct {
PersistDataFilePath string `json:"persistDataFilePath"` PersistDataFilePath string `json:"persistDataFilePath"`
DisableAuth bool `json:"disableAuth"` DisableAuth bool `json:"disableAuth"`
DisableTls bool `json:"disableTls"` DisableTls bool `json:"disableTls"`
Debug bool `json:"debug"` LogLevel string `json:"logLevel"`
ExplorerBaseUrlLocation string `json:"explorerBaseUrlLocation"` ExplorerBaseUrlLocation string `json:"explorerBaseUrlLocation"`
} }

View File

@@ -6,11 +6,12 @@ import (
"net/http" "net/http"
"strconv" "strconv"
jsonpatch "github.com/cosmiumdev/json-patch/v5"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
apimodels "github.com/pikami/cosmium/api/api_models"
"github.com/pikami/cosmium/internal/constants" "github.com/pikami/cosmium/internal/constants"
"github.com/pikami/cosmium/internal/logger" "github.com/pikami/cosmium/internal/logger"
repositorymodels "github.com/pikami/cosmium/internal/repository_models" repositorymodels "github.com/pikami/cosmium/internal/repository_models"
jsonpatch "github.com/pikami/json-patch/v5"
) )
func (h *Handlers) GetAllDocuments(c *gin.Context) { func (h *Handlers) GetAllDocuments(c *gin.Context) {
@@ -135,7 +136,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
currentDocumentBytes, err := json.Marshal(document) currentDocumentBytes, err := json.Marshal(document)
if err != nil { if err != nil {
logger.Error("Failed to marshal existing document:", err) logger.ErrorLn("Failed to marshal existing document:", err)
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to marshal existing document"}) c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to marshal existing document"})
return return
} }
@@ -149,7 +150,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
var modifiedDocument map[string]interface{} var modifiedDocument map[string]interface{}
err = json.Unmarshal(modifiedDocumentBytes, &modifiedDocument) err = json.Unmarshal(modifiedDocumentBytes, &modifiedDocument)
if err != nil { if err != nil {
logger.Error("Failed to unmarshal modified document:", err) logger.ErrorLn("Failed to unmarshal modified document:", err)
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to unmarshal modified document"}) c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to unmarshal modified document"})
return return
} }
@@ -183,6 +184,13 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
databaseId := c.Param("databaseId") databaseId := c.Param("databaseId")
collectionId := c.Param("collId") collectionId := c.Param("collId")
// Handle batch requests
isBatchRequest, _ := strconv.ParseBool(c.GetHeader("x-ms-cosmos-is-batch-request"))
if isBatchRequest {
h.handleBatchRequest(c)
return
}
var requestBody map[string]interface{} var requestBody map[string]interface{}
if err := c.BindJSON(&requestBody); err != nil { if err := c.BindJSON(&requestBody); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
@@ -191,30 +199,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
query := requestBody["query"] query := requestBody["query"]
if query != nil { if query != nil {
if c.GetHeader("x-ms-cosmos-is-query-plan-request") != "" { h.handleDocumentQuery(c, requestBody)
c.IndentedJSON(http.StatusOK, constants.QueryPlanResponse)
return
}
var queryParameters map[string]interface{}
if paramsArray, ok := requestBody["parameters"].([]interface{}); ok {
queryParameters = parametersToMap(paramsArray)
}
docs, status := h.repository.ExecuteQueryDocuments(databaseId, collectionId, query.(string), queryParameters)
if status != repositorymodels.StatusOk {
// TODO: Currently we return everything if the query fails
h.GetAllDocuments(c)
return
}
collection, _ := h.repository.GetCollection(databaseId, collectionId)
c.Header("x-ms-item-count", fmt.Sprintf("%d", len(docs)))
c.IndentedJSON(http.StatusOK, gin.H{
"_rid": collection.ResourceID,
"Documents": docs,
"_count": len(docs),
})
return return
} }
@@ -253,3 +238,131 @@ func parametersToMap(pairs []interface{}) map[string]interface{} {
return result return result
} }
func (h *Handlers) handleDocumentQuery(c *gin.Context, requestBody map[string]interface{}) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
if c.GetHeader("x-ms-cosmos-is-query-plan-request") != "" {
c.IndentedJSON(http.StatusOK, constants.QueryPlanResponse)
return
}
var queryParameters map[string]interface{}
if paramsArray, ok := requestBody["parameters"].([]interface{}); ok {
queryParameters = parametersToMap(paramsArray)
}
docs, status := h.repository.ExecuteQueryDocuments(databaseId, collectionId, requestBody["query"].(string), queryParameters)
if status != repositorymodels.StatusOk {
// TODO: Currently we return everything if the query fails
h.GetAllDocuments(c)
return
}
collection, _ := h.repository.GetCollection(databaseId, collectionId)
c.Header("x-ms-item-count", fmt.Sprintf("%d", len(docs)))
c.IndentedJSON(http.StatusOK, gin.H{
"_rid": collection.ResourceID,
"Documents": docs,
"_count": len(docs),
})
}
func (h *Handlers) handleBatchRequest(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
batchOperations := make([]apimodels.BatchOperation, 0)
if err := c.BindJSON(&batchOperations); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
return
}
batchOperationResults := make([]apimodels.BatchOperationResult, len(batchOperations))
for idx, operation := range batchOperations {
switch operation.OperationType {
case apimodels.BatchOperationTypeCreate:
createdDocument, status := h.repository.CreateDocument(databaseId, collectionId, operation.ResourceBody)
responseCode := repositoryStatusToResponseCode(status)
if status == repositorymodels.StatusOk {
responseCode = http.StatusCreated
}
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: responseCode,
ResourceBody: createdDocument,
}
case apimodels.BatchOperationTypeDelete:
status := h.repository.DeleteDocument(databaseId, collectionId, operation.Id)
responseCode := repositoryStatusToResponseCode(status)
if status == repositorymodels.StatusOk {
responseCode = http.StatusNoContent
}
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: responseCode,
}
case apimodels.BatchOperationTypeReplace:
deleteStatus := h.repository.DeleteDocument(databaseId, collectionId, operation.Id)
if deleteStatus == repositorymodels.StatusNotFound {
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: http.StatusNotFound,
}
continue
}
createdDocument, createStatus := h.repository.CreateDocument(databaseId, collectionId, operation.ResourceBody)
responseCode := repositoryStatusToResponseCode(createStatus)
if createStatus == repositorymodels.StatusOk {
responseCode = http.StatusCreated
}
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: responseCode,
ResourceBody: createdDocument,
}
case apimodels.BatchOperationTypeUpsert:
documentId := operation.ResourceBody["id"].(string)
h.repository.DeleteDocument(databaseId, collectionId, documentId)
createdDocument, createStatus := h.repository.CreateDocument(databaseId, collectionId, operation.ResourceBody)
responseCode := repositoryStatusToResponseCode(createStatus)
if createStatus == repositorymodels.StatusOk {
responseCode = http.StatusCreated
}
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: responseCode,
ResourceBody: createdDocument,
}
case apimodels.BatchOperationTypeRead:
document, status := h.repository.GetDocument(databaseId, collectionId, operation.Id)
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: repositoryStatusToResponseCode(status),
ResourceBody: document,
}
case apimodels.BatchOperationTypePatch:
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: http.StatusNotImplemented,
Message: "Patch operation is not implemented",
}
default:
batchOperationResults[idx] = apimodels.BatchOperationResult{
StatusCode: http.StatusBadRequest,
Message: "Unknown operation type",
}
}
}
c.JSON(http.StatusOK, batchOperationResults)
}
func repositoryStatusToResponseCode(status repositorymodels.RepositoryStatus) int {
switch status {
case repositorymodels.StatusOk:
return http.StatusOK
case repositorymodels.StatusNotFound:
return http.StatusNotFound
case repositorymodels.Conflict:
return http.StatusConflict
case repositorymodels.BadRequest:
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}

View File

@@ -7,10 +7,10 @@ import (
type Handlers struct { type Handlers struct {
repository *repositories.DataRepository repository *repositories.DataRepository
config config.ServerConfig config *config.ServerConfig
} }
func NewHandlers(dataRepository *repositories.DataRepository, config config.ServerConfig) *Handlers { func NewHandlers(dataRepository *repositories.DataRepository, config *config.ServerConfig) *Handlers {
return &Handlers{ return &Handlers{
repository: dataRepository, repository: dataRepository,
config: config, config: config,

View File

@@ -10,7 +10,7 @@ import (
"github.com/pikami/cosmium/internal/logger" "github.com/pikami/cosmium/internal/logger"
) )
func Authentication(config config.ServerConfig) gin.HandlerFunc { func Authentication(config *config.ServerConfig) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
requestUrl := c.Request.URL.String() requestUrl := c.Request.URL.String()
if config.DisableAuth || if config.DisableAuth ||

View File

@@ -16,7 +16,7 @@ func RequestLogger() gin.HandlerFunc {
bodyStr := readBody(rdr1) bodyStr := readBody(rdr1)
if bodyStr != "" { if bodyStr != "" {
logger.Debug(bodyStr) logger.DebugLn(bodyStr)
} }
c.Request.Body = rdr2 c.Request.Body = rdr2

View File

@@ -7,7 +7,7 @@ import (
"github.com/pikami/cosmium/api/config" "github.com/pikami/cosmium/api/config"
) )
func StripTrailingSlashes(r *gin.Engine, config config.ServerConfig) gin.HandlerFunc { func StripTrailingSlashes(r *gin.Engine, config *config.ServerConfig) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
path := c.Request.URL.Path path := c.Request.URL.Path
if len(path) > 1 && path[len(path)-1] == '/' && !strings.Contains(path, config.ExplorerBaseUrlLocation) { if len(path) > 1 && path[len(path)-1] == '/' && !strings.Contains(path, config.ExplorerBaseUrlLocation) {

View File

@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
repositorymodels "github.com/pikami/cosmium/internal/repository_models" repositorymodels "github.com/pikami/cosmium/internal/repository_models"
"github.com/pikami/cosmium/internal/resourceid"
) )
func (h *Handlers) GetPartitionKeyRanges(c *gin.Context) { func (h *Handlers) GetPartitionKeyRanges(c *gin.Context) {
@@ -31,8 +32,9 @@ func (h *Handlers) GetPartitionKeyRanges(c *gin.Context) {
collectionRid = collection.ResourceID collectionRid = collection.ResourceID
} }
rid := resourceid.NewCombined(collectionRid, resourceid.New(resourceid.ResourceTypePartitionKeyRange))
c.IndentedJSON(http.StatusOK, gin.H{ c.IndentedJSON(http.StatusOK, gin.H{
"_rid": collectionRid, "_rid": rid,
"_count": len(partitionKeyRanges), "_count": len(partitionKeyRanges),
"PartitionKeyRanges": partitionKeyRanges, "PartitionKeyRanges": partitionKeyRanges,
}) })

View File

@@ -27,7 +27,9 @@ func (h *Handlers) GetServerInfo(c *gin.Context) {
"databaseAccountEndpoint": h.config.DatabaseEndpoint, "databaseAccountEndpoint": h.config.DatabaseEndpoint,
}, },
}, },
"enableMultipleWriteLocations": false, "enableMultipleWriteLocations": false,
"continuousBackupEnabled": false,
"enableNRegionSynchronousCommit": false,
"userReplicationPolicy": map[string]interface{}{ "userReplicationPolicy": map[string]interface{}{
"asyncReplication": false, "asyncReplication": false,
"minReplicaSetSize": 1, "minReplicaSetSize": 1,

View File

@@ -22,3 +22,97 @@ func (h *Handlers) GetAllStoredProcedures(c *gin.Context) {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"}) c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
} }
func (h *Handlers) GetStoredProcedure(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
spId := c.Param("spId")
sp, status := h.repository.GetStoredProcedure(databaseId, collectionId, spId)
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusOK, sp)
return
}
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) DeleteStoredProcedure(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
spId := c.Param("spId")
status := h.repository.DeleteStoredProcedure(databaseId, collectionId, spId)
if status == repositorymodels.StatusOk {
c.Status(http.StatusNoContent)
return
}
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
spId := c.Param("spId")
var sp repositorymodels.StoredProcedure
if err := c.BindJSON(&sp); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
return
}
status := h.repository.DeleteStoredProcedure(databaseId, collectionId, spId)
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
createdSP, status := h.repository.CreateStoredProcedure(databaseId, collectionId, sp)
if status == repositorymodels.Conflict {
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
return
}
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusOK, createdSP)
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
var sp repositorymodels.StoredProcedure
if err := c.BindJSON(&sp); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
return
}
createdSP, status := h.repository.CreateStoredProcedure(databaseId, collectionId, sp)
if status == repositorymodels.Conflict {
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
return
}
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusCreated, createdSP)
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}

View File

@@ -22,3 +22,97 @@ func (h *Handlers) GetAllTriggers(c *gin.Context) {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"}) c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
} }
func (h *Handlers) GetTrigger(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
triggerId := c.Param("triggerId")
trigger, status := h.repository.GetTrigger(databaseId, collectionId, triggerId)
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusOK, trigger)
return
}
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) DeleteTrigger(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
triggerId := c.Param("triggerId")
status := h.repository.DeleteTrigger(databaseId, collectionId, triggerId)
if status == repositorymodels.StatusOk {
c.Status(http.StatusNoContent)
return
}
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) ReplaceTrigger(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
triggerId := c.Param("triggerId")
var trigger repositorymodels.Trigger
if err := c.BindJSON(&trigger); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
return
}
status := h.repository.DeleteTrigger(databaseId, collectionId, triggerId)
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
createdTrigger, status := h.repository.CreateTrigger(databaseId, collectionId, trigger)
if status == repositorymodels.Conflict {
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
return
}
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusOK, createdTrigger)
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) CreateTrigger(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
var trigger repositorymodels.Trigger
if err := c.BindJSON(&trigger); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
return
}
createdTrigger, status := h.repository.CreateTrigger(databaseId, collectionId, trigger)
if status == repositorymodels.Conflict {
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
return
}
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusCreated, createdTrigger)
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}

View File

@@ -22,3 +22,97 @@ func (h *Handlers) GetAllUserDefinedFunctions(c *gin.Context) {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"}) c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
} }
func (h *Handlers) GetUserDefinedFunction(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
udfId := c.Param("udfId")
udf, status := h.repository.GetUserDefinedFunction(databaseId, collectionId, udfId)
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusOK, udf)
return
}
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) DeleteUserDefinedFunction(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
udfId := c.Param("udfId")
status := h.repository.DeleteUserDefinedFunction(databaseId, collectionId, udfId)
if status == repositorymodels.StatusOk {
c.Status(http.StatusNoContent)
return
}
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
udfId := c.Param("udfId")
var udf repositorymodels.UserDefinedFunction
if err := c.BindJSON(&udf); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
return
}
status := h.repository.DeleteUserDefinedFunction(databaseId, collectionId, udfId)
if status == repositorymodels.StatusNotFound {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
return
}
createdUdf, status := h.repository.CreateUserDefinedFunction(databaseId, collectionId, udf)
if status == repositorymodels.Conflict {
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
return
}
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusOK, createdUdf)
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}
func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
databaseId := c.Param("databaseId")
collectionId := c.Param("collId")
var udf repositorymodels.UserDefinedFunction
if err := c.BindJSON(&udf); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
return
}
createdUdf, status := h.repository.CreateUserDefinedFunction(databaseId, collectionId, udf)
if status == repositorymodels.Conflict {
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
return
}
if status == repositorymodels.StatusOk {
c.IndentedJSON(http.StatusCreated, createdUdf)
return
}
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
}

View File

@@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pikami/cosmium/api/handlers" "github.com/pikami/cosmium/api/handlers"
@@ -13,18 +15,25 @@ import (
tlsprovider "github.com/pikami/cosmium/internal/tls_provider" tlsprovider "github.com/pikami/cosmium/internal/tls_provider"
) )
var ginMux sync.Mutex
func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) { func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) {
routeHandlers := handlers.NewHandlers(repository, s.config) routeHandlers := handlers.NewHandlers(repository, s.config)
if !s.config.Debug { ginMux.Lock()
gin.DefaultWriter = logger.InfoWriter()
gin.DefaultErrorWriter = logger.ErrorWriter()
if s.config.LogLevel != "debug" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
ginMux.Unlock()
router := gin.Default(func(e *gin.Engine) { router := gin.Default(func(e *gin.Engine) {
e.RedirectTrailingSlash = false e.RedirectTrailingSlash = false
}) })
if s.config.Debug { if s.config.LogLevel == "debug" {
router.Use(middleware.RequestLogger()) router.Use(middleware.RequestLogger())
} }
@@ -50,9 +59,23 @@ func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) {
router.GET("/dbs/:databaseId", routeHandlers.GetDatabase) router.GET("/dbs/:databaseId", routeHandlers.GetDatabase)
router.DELETE("/dbs/:databaseId", routeHandlers.DeleteDatabase) router.DELETE("/dbs/:databaseId", routeHandlers.DeleteDatabase)
router.GET("/dbs/:databaseId/colls/:collId/udfs", routeHandlers.GetAllUserDefinedFunctions) router.POST("/dbs/:databaseId/colls/:collId/triggers", routeHandlers.CreateTrigger)
router.GET("/dbs/:databaseId/colls/:collId/sprocs", routeHandlers.GetAllStoredProcedures)
router.GET("/dbs/:databaseId/colls/:collId/triggers", routeHandlers.GetAllTriggers) router.GET("/dbs/:databaseId/colls/:collId/triggers", routeHandlers.GetAllTriggers)
router.GET("/dbs/:databaseId/colls/:collId/triggers/:triggerId", routeHandlers.GetTrigger)
router.PUT("/dbs/:databaseId/colls/:collId/triggers/:triggerId", routeHandlers.ReplaceTrigger)
router.DELETE("/dbs/:databaseId/colls/:collId/triggers/:triggerId", routeHandlers.DeleteTrigger)
router.POST("/dbs/:databaseId/colls/:collId/sprocs", routeHandlers.CreateStoredProcedure)
router.GET("/dbs/:databaseId/colls/:collId/sprocs", routeHandlers.GetAllStoredProcedures)
router.GET("/dbs/:databaseId/colls/:collId/sprocs/:sprocId", routeHandlers.GetStoredProcedure)
router.PUT("/dbs/:databaseId/colls/:collId/sprocs/:sprocId", routeHandlers.ReplaceStoredProcedure)
router.DELETE("/dbs/:databaseId/colls/:collId/sprocs/:sprocId", routeHandlers.DeleteStoredProcedure)
router.POST("/dbs/:databaseId/colls/:collId/udfs", routeHandlers.CreateUserDefinedFunction)
router.GET("/dbs/:databaseId/colls/:collId/udfs", routeHandlers.GetAllUserDefinedFunctions)
router.GET("/dbs/:databaseId/colls/:collId/udfs/:udfId", routeHandlers.GetUserDefinedFunction)
router.PUT("/dbs/:databaseId/colls/:collId/udfs/:udfId", routeHandlers.ReplaceUserDefinedFunction)
router.DELETE("/dbs/:databaseId/colls/:collId/udfs/:udfId", routeHandlers.DeleteUserDefinedFunction)
router.GET("/offers", handlers.GetOffers) router.GET("/offers", handlers.GetOffers)
router.GET("/", routeHandlers.GetServerInfo) router.GET("/", routeHandlers.GetServerInfo)
@@ -64,7 +87,7 @@ func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) {
s.router = router s.router = router
} }
func (s *ApiServer) Start() { func (s *ApiServer) Start() error {
listenAddress := fmt.Sprintf(":%d", s.config.Port) listenAddress := fmt.Sprintf(":%d", s.config.Port)
s.isActive = true s.isActive = true
@@ -73,42 +96,49 @@ func (s *ApiServer) Start() {
Handler: s.router.Handler(), Handler: s.router.Handler(),
} }
errChan := make(chan error, 1)
go func() { go func() {
<-s.stopServer <-s.stopServer
logger.Info("Shutting down server...") logger.InfoLn("Shutting down server...")
err := server.Shutdown(context.TODO()) err := server.Shutdown(context.TODO())
if err != nil { if err != nil {
logger.Error("Failed to shutdown server:", err) logger.ErrorLn("Failed to shutdown server:", err)
} }
s.onServerShutdown <- true
}() }()
go func() { go func() {
var err error
if s.config.DisableTls { if s.config.DisableTls {
logger.Infof("Listening and serving HTTP on %s\n", server.Addr) logger.Infof("Listening and serving HTTP on %s\n", server.Addr)
err := server.ListenAndServe() err = server.ListenAndServe()
if err != nil {
logger.Error("Failed to start HTTP server:", err)
}
s.isActive = false
} else if s.config.TLS_CertificatePath != "" && s.config.TLS_CertificateKey != "" { } else if s.config.TLS_CertificatePath != "" && s.config.TLS_CertificateKey != "" {
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr) logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
err := server.ListenAndServeTLS( err = server.ListenAndServeTLS(
s.config.TLS_CertificatePath, s.config.TLS_CertificatePath,
s.config.TLS_CertificateKey) s.config.TLS_CertificateKey)
if err != nil {
logger.Error("Failed to start HTTPS server:", err)
}
s.isActive = false
} else { } else {
tlsConfig := tlsprovider.GetDefaultTlsConfig() tlsConfig := tlsprovider.GetDefaultTlsConfig()
server.TLSConfig = tlsConfig server.TLSConfig = tlsConfig
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr) logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
err := server.ListenAndServeTLS("", "") err = server.ListenAndServeTLS("", "")
if err != nil {
logger.Error("Failed to start HTTPS server:", err)
}
s.isActive = false
} }
if err != nil && err != http.ErrServerClosed {
logger.ErrorLn("Failed to start server:", err)
errChan <- err
} else {
errChan <- nil
}
s.isActive = false
}() }()
select {
case err := <-errChan:
return err
case <-time.After(50 * time.Millisecond):
return nil
}
} }

View File

@@ -2,13 +2,11 @@ package tests_test
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos"
"github.com/pikami/cosmium/api/config" "github.com/pikami/cosmium/api/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -47,12 +45,7 @@ func Test_Authentication(t *testing.T) {
azcosmos.DatabaseProperties{ID: testDatabaseName}, azcosmos.DatabaseProperties{ID: testDatabaseName},
&azcosmos.CreateDatabaseOptions{}) &azcosmos.CreateDatabaseOptions{})
var respErr *azcore.ResponseError assert.Contains(t, err.Error(), "401 Unauthorized")
if errors.As(err, &respErr) {
assert.Equal(t, respErr.StatusCode, http.StatusUnauthorized)
} else {
panic(err)
}
}) })
t.Run("Should allow unauthorized requests to /_explorer", func(t *testing.T) { t.Run("Should allow unauthorized requests to /_explorer", func(t *testing.T) {
@@ -68,7 +61,7 @@ func Test_Authentication(t *testing.T) {
} }
func Test_Authentication_Disabled(t *testing.T) { func Test_Authentication_Disabled(t *testing.T) {
ts := runTestServerCustomConfig(config.ServerConfig{ ts := runTestServerCustomConfig(&config.ServerConfig{
AccountKey: config.DefaultAccountKey, AccountKey: config.DefaultAccountKey,
ExplorerPath: "/tmp/nothing", ExplorerPath: "/tmp/nothing",
ExplorerBaseUrlLocation: config.ExplorerBaseUrlLocation, ExplorerBaseUrlLocation: config.ExplorerBaseUrlLocation,

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/api" "github.com/pikami/cosmium/api"
"github.com/pikami/cosmium/api/config" "github.com/pikami/cosmium/api/config"
"github.com/pikami/cosmium/internal/logger"
"github.com/pikami/cosmium/internal/repositories" "github.com/pikami/cosmium/internal/repositories"
) )
@@ -14,13 +15,15 @@ type TestServer struct {
URL string URL string
} }
func runTestServerCustomConfig(config config.ServerConfig) *TestServer { func runTestServerCustomConfig(config *config.ServerConfig) *TestServer {
repository := repositories.NewDataRepository(repositories.RepositoryOptions{}) repository := repositories.NewDataRepository(repositories.RepositoryOptions{})
api := api.NewApiServer(repository, config) api := api.NewApiServer(repository, config)
server := httptest.NewServer(api.GetRouter()) server := httptest.NewServer(api.GetRouter())
config.DatabaseEndpoint = server.URL
return &TestServer{ return &TestServer{
Server: server, Server: server,
Repository: repository, Repository: repository,
@@ -29,12 +32,15 @@ func runTestServerCustomConfig(config config.ServerConfig) *TestServer {
} }
func runTestServer() *TestServer { func runTestServer() *TestServer {
config := config.ServerConfig{ config := &config.ServerConfig{
AccountKey: config.DefaultAccountKey, AccountKey: config.DefaultAccountKey,
ExplorerPath: "/tmp/nothing", ExplorerPath: "/tmp/nothing",
ExplorerBaseUrlLocation: config.ExplorerBaseUrlLocation, ExplorerBaseUrlLocation: config.ExplorerBaseUrlLocation,
} }
config.LogLevel = "debug"
logger.SetLogLevel(logger.LogLevelDebug)
return runTestServerCustomConfig(config) return runTestServerCustomConfig(config)
} }

View File

@@ -377,5 +377,140 @@ func Test_Documents_Patch(t *testing.T) {
assert.NotNil(t, r) assert.NotNil(t, r)
assert.Nil(t, err2) assert.Nil(t, err2)
}) })
}
func Test_Documents_TransactionalBatch(t *testing.T) {
ts, collectionClient := documents_InitializeDb(t)
defer ts.Server.Close()
t.Run("Should execute CREATE transactional batch", func(t *testing.T) {
context := context.TODO()
batch := collectionClient.NewTransactionalBatch(azcosmos.NewPartitionKeyString("pk"))
newItem := map[string]interface{}{
"id": "678901",
}
bytes, err := json.Marshal(newItem)
assert.Nil(t, err)
batch.CreateItem(bytes, nil)
response, err := collectionClient.ExecuteTransactionalBatch(context, batch, &azcosmos.TransactionalBatchOptions{})
assert.Nil(t, err)
assert.True(t, response.Success)
assert.Equal(t, 1, len(response.OperationResults))
operationResponse := response.OperationResults[0]
assert.NotNil(t, operationResponse)
assert.NotNil(t, operationResponse.ResourceBody)
assert.Equal(t, int32(http.StatusCreated), operationResponse.StatusCode)
var itemResponseBody map[string]interface{}
json.Unmarshal(operationResponse.ResourceBody, &itemResponseBody)
assert.Equal(t, newItem["id"], itemResponseBody["id"])
createdDoc, _ := ts.Repository.GetDocument(testDatabaseName, testCollectionName, newItem["id"].(string))
assert.Equal(t, newItem["id"], createdDoc["id"])
})
t.Run("Should execute DELETE transactional batch", func(t *testing.T) {
context := context.TODO()
batch := collectionClient.NewTransactionalBatch(azcosmos.NewPartitionKeyString("pk"))
batch.DeleteItem("12345", nil)
response, err := collectionClient.ExecuteTransactionalBatch(context, batch, &azcosmos.TransactionalBatchOptions{})
assert.Nil(t, err)
assert.True(t, response.Success)
assert.Equal(t, 1, len(response.OperationResults))
operationResponse := response.OperationResults[0]
assert.NotNil(t, operationResponse)
assert.Equal(t, int32(http.StatusNoContent), operationResponse.StatusCode)
_, status := ts.Repository.GetDocument(testDatabaseName, testCollectionName, "12345")
assert.Equal(t, repositorymodels.StatusNotFound, int(status))
})
t.Run("Should execute REPLACE transactional batch", func(t *testing.T) {
context := context.TODO()
batch := collectionClient.NewTransactionalBatch(azcosmos.NewPartitionKeyString("pk"))
newItem := map[string]interface{}{
"id": "67890",
"pk": "666",
}
bytes, err := json.Marshal(newItem)
assert.Nil(t, err)
batch.ReplaceItem("67890", bytes, nil)
response, err := collectionClient.ExecuteTransactionalBatch(context, batch, &azcosmos.TransactionalBatchOptions{})
assert.Nil(t, err)
assert.True(t, response.Success)
assert.Equal(t, 1, len(response.OperationResults))
operationResponse := response.OperationResults[0]
assert.NotNil(t, operationResponse)
assert.NotNil(t, operationResponse.ResourceBody)
assert.Equal(t, int32(http.StatusCreated), operationResponse.StatusCode)
var itemResponseBody map[string]interface{}
json.Unmarshal(operationResponse.ResourceBody, &itemResponseBody)
assert.Equal(t, newItem["id"], itemResponseBody["id"])
assert.Equal(t, newItem["pk"], itemResponseBody["pk"])
updatedDoc, _ := ts.Repository.GetDocument(testDatabaseName, testCollectionName, newItem["id"].(string))
assert.Equal(t, newItem["id"], updatedDoc["id"])
assert.Equal(t, newItem["pk"], updatedDoc["pk"])
})
t.Run("Should execute UPSERT transactional batch", func(t *testing.T) {
context := context.TODO()
batch := collectionClient.NewTransactionalBatch(azcosmos.NewPartitionKeyString("pk"))
newItem := map[string]interface{}{
"id": "678901",
"pk": "666",
}
bytes, err := json.Marshal(newItem)
assert.Nil(t, err)
batch.UpsertItem(bytes, nil)
response, err := collectionClient.ExecuteTransactionalBatch(context, batch, &azcosmos.TransactionalBatchOptions{})
assert.Nil(t, err)
assert.True(t, response.Success)
assert.Equal(t, 1, len(response.OperationResults))
operationResponse := response.OperationResults[0]
assert.NotNil(t, operationResponse)
assert.NotNil(t, operationResponse.ResourceBody)
assert.Equal(t, int32(http.StatusCreated), operationResponse.StatusCode)
var itemResponseBody map[string]interface{}
json.Unmarshal(operationResponse.ResourceBody, &itemResponseBody)
assert.Equal(t, newItem["id"], itemResponseBody["id"])
assert.Equal(t, newItem["pk"], itemResponseBody["pk"])
updatedDoc, _ := ts.Repository.GetDocument(testDatabaseName, testCollectionName, newItem["id"].(string))
assert.Equal(t, newItem["id"], updatedDoc["id"])
assert.Equal(t, newItem["pk"], updatedDoc["pk"])
})
t.Run("Should execute READ transactional batch", func(t *testing.T) {
context := context.TODO()
batch := collectionClient.NewTransactionalBatch(azcosmos.NewPartitionKeyString("pk"))
batch.ReadItem("67890", nil)
response, err := collectionClient.ExecuteTransactionalBatch(context, batch, &azcosmos.TransactionalBatchOptions{})
assert.Nil(t, err)
assert.True(t, response.Success)
assert.Equal(t, 1, len(response.OperationResults))
operationResponse := response.OperationResults[0]
assert.NotNil(t, operationResponse)
assert.NotNil(t, operationResponse.ResourceBody)
assert.Equal(t, int32(http.StatusOK), operationResponse.StatusCode)
var itemResponseBody map[string]interface{}
json.Unmarshal(operationResponse.ResourceBody, &itemResponseBody)
assert.Equal(t, "67890", itemResponseBody["id"])
})
} }

View File

@@ -18,8 +18,11 @@ func main() {
PersistDataFilePath: configuration.PersistDataFilePath, PersistDataFilePath: configuration.PersistDataFilePath,
}) })
server := api.NewApiServer(repository, configuration) server := api.NewApiServer(repository, &configuration)
server.Start() err := server.Start()
if err != nil {
panic(err)
}
waitForExit(server, repository, configuration) waitForExit(server, repository, configuration)
} }

View File

@@ -65,9 +65,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| Function | Implemented | | Function | Implemented |
| ------------------ | ----------- | | ------------------ | ----------- |
| ARRAY_CONCAT | Yes | | ARRAY_CONCAT | Yes |
| ARRAY_CONTAINS | No | | ARRAY_CONTAINS | Yes |
| ARRAY_CONTAINS_ANY | No | | ARRAY_CONTAINS_ANY | Yes |
| ARRAY_CONTAINS_ALL | No | | ARRAY_CONTAINS_ALL | Yes |
| ARRAY_LENGTH | Yes | | ARRAY_LENGTH | Yes |
| ARRAY_SLICE | Yes | | ARRAY_SLICE | Yes |
| CHOOSE | No | | CHOOSE | No |
@@ -204,6 +204,19 @@ Cosmium strives to support the core features of Cosmos DB, including:
| IS_PRIMITIVE | Yes | | IS_PRIMITIVE | Yes |
| IS_STRING | Yes | | IS_STRING | Yes |
### Transactional batch operations
Note: There's actually no transaction here. Think of this as a 'bulk operation' that can partially succeed.
| Operation | Implemented |
| --------- | ----------- |
| Create | Yes |
| Delete | Yes |
| Replace | Yes |
| Upsert | Yes |
| Read | Yes |
| Patch | No |
## Known Differences ## Known Differences
While Cosmium aims to replicate the behavior of Cosmos DB as closely as possible, there are certain differences and limitations to be aware of: While Cosmium aims to replicate the behavior of Cosmos DB as closely as possible, there are certain differences and limitations to be aware of:

43
go.mod
View File

@@ -1,33 +1,32 @@
module github.com/pikami/cosmium module github.com/pikami/cosmium
go 1.22.0 go 1.24.0
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6 github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.3.0
github.com/cosmiumdev/json-patch/v5 v5.9.3
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/pikami/json-patch/v5 v5.9.2 github.com/stretchr/testify v1.10.0
github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
) )
require ( require (
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/bytedance/sonic v1.12.6 // indirect github.com/bytedance/sonic v1.12.9 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -37,11 +36,11 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.12.0 // indirect golang.org/x/arch v0.14.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.22.0 // indirect
google.golang.org/protobuf v1.36.0 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

108
go.sum
View File

@@ -1,31 +1,32 @@
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6 h1:oBqQLSI1pZwGOdXJAoJJSzmff9tlfD4KroVfjQQmd0g= github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.3.0 h1:RGcdpSElvcXCwxydI0xzOBu1Gvp88OoiTGfbtO/z1m0=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6/go.mod h1:Beh5cHIXJ0oWEDWk9lNFtuklCojLLQ5hl+LqSNTTs0I= github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.3.0/go.mod h1:YwUyrNUtcZcibA99JcfCP6UUp95VVQKO2MJfBzgJDwA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
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 v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk= github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ=
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cosmiumdev/json-patch/v5 v5.9.3 h1:l+Og3+5edqV2NHDo58sz72eS733lbXVYP61seYK43Do=
github.com/cosmiumdev/json-patch/v5 v5.9.3/go.mod h1:WzSTCdia0WrlZtjnL19P4RiwWtfdyArm/E7stgEeP5g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -34,12 +35,12 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -48,9 +49,13 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -64,45 +69,48 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 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/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pikami/json-patch/v5 v5.9.2 h1:ciTlocWccYVE3DEa45dsMm02c/tOvcaBY7PpEUNZhrU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pikami/json-patch/v5 v5.9.2/go.mod h1:eJIScZ4xgf2aBHLi2UMzYtjlWESUBDOBf7EAx3JW0nI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
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= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,40 +1,140 @@
package logger package logger
import ( import (
"fmt"
"log" "log"
"os" "os"
"runtime"
"strings"
"sync"
) )
var EnableDebugOutput = false type LogLevelType int
var DebugLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile) var (
LogLevelDebug LogLevelType = 0
LogLevelInfo LogLevelType = 1
LogLevelError LogLevelType = 2
LogLevelSilent LogLevelType = 10
)
type LogWriter struct {
WriterLevel LogLevelType
}
var logLevelMutex sync.RWMutex
var logLevel = LogLevelInfo
var DebugLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
var InfoLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) var InfoLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
var ErrorLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile) var ErrorLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime)
func DebugLn(v ...any) {
if GetLogLevel() <= LogLevelDebug {
prefix := getCallerPrefix()
DebugLogger.Println(append([]interface{}{prefix}, v...)...)
}
}
func Debug(v ...any) { func Debug(v ...any) {
if EnableDebugOutput { if GetLogLevel() <= LogLevelDebug {
DebugLogger.Println(v...) prefix := getCallerPrefix()
DebugLogger.Println(append([]interface{}{prefix}, v...)...)
} }
} }
func Debugf(format string, v ...any) { func Debugf(format string, v ...any) {
if EnableDebugOutput { if GetLogLevel() <= LogLevelDebug {
DebugLogger.Printf(format, v...) prefix := getCallerPrefix()
DebugLogger.Printf(prefix+format, v...)
}
}
func InfoLn(v ...any) {
if GetLogLevel() <= LogLevelInfo {
InfoLogger.Println(v...)
} }
} }
func Info(v ...any) { func Info(v ...any) {
InfoLogger.Println(v...) if GetLogLevel() <= LogLevelInfo {
InfoLogger.Print(v...)
}
} }
func Infof(format string, v ...any) { func Infof(format string, v ...any) {
InfoLogger.Printf(format, v...) if GetLogLevel() <= LogLevelInfo {
InfoLogger.Printf(format, v...)
}
}
func ErrorLn(v ...any) {
if GetLogLevel() <= LogLevelError {
prefix := getCallerPrefix()
ErrorLogger.Println(append([]interface{}{prefix}, v...)...)
}
} }
func Error(v ...any) { func Error(v ...any) {
ErrorLogger.Println(v...) if GetLogLevel() <= LogLevelError {
prefix := getCallerPrefix()
ErrorLogger.Print(append([]interface{}{prefix}, v...)...)
}
} }
func Errorf(format string, v ...any) { func Errorf(format string, v ...any) {
ErrorLogger.Printf(format, v...) if GetLogLevel() <= LogLevelError {
prefix := getCallerPrefix()
ErrorLogger.Printf(prefix+format, v...)
}
}
func (lw *LogWriter) Write(p []byte) (n int, err error) {
switch lw.WriterLevel {
case LogLevelDebug:
Debug(string(p))
case LogLevelInfo:
Info(string(p))
case LogLevelError:
Error(string(p))
}
return len(p), nil
}
func ErrorWriter() *LogWriter {
return &LogWriter{WriterLevel: LogLevelError}
}
func InfoWriter() *LogWriter {
return &LogWriter{WriterLevel: LogLevelInfo}
}
func DebugWriter() *LogWriter {
return &LogWriter{WriterLevel: LogLevelDebug}
}
func SetLogLevel(level LogLevelType) {
logLevelMutex.Lock()
defer logLevelMutex.Unlock()
logLevel = level
}
func GetLogLevel() LogLevelType {
logLevelMutex.RLock()
defer logLevelMutex.RUnlock()
return logLevel
}
func getCallerPrefix() string {
_, file, line, ok := runtime.Caller(2)
if ok {
parts := strings.Split(file, "/")
if len(parts) > 0 {
file = parts[len(parts)-1]
}
return fmt.Sprintf("%s:%d - ", file, line)
}
return ""
} }

View File

@@ -50,6 +50,10 @@ func (r *DataRepository) DeleteCollection(databaseId string, collectionId string
} }
delete(r.storeState.Collections[databaseId], collectionId) delete(r.storeState.Collections[databaseId], collectionId)
delete(r.storeState.Documents[databaseId], collectionId)
delete(r.storeState.Triggers[databaseId], collectionId)
delete(r.storeState.StoredProcedures[databaseId], collectionId)
delete(r.storeState.UserDefinedFunctions[databaseId], collectionId)
return repositorymodels.StatusOk return repositorymodels.StatusOk
} }
@@ -71,12 +75,15 @@ func (r *DataRepository) CreateCollection(databaseId string, newCollection repos
newCollection = structhidrators.Hidrate(newCollection).(repositorymodels.Collection) newCollection = structhidrators.Hidrate(newCollection).(repositorymodels.Collection)
newCollection.TimeStamp = time.Now().Unix() newCollection.TimeStamp = time.Now().Unix()
newCollection.ResourceID = resourceid.NewCombined(database.ResourceID, resourceid.New()) newCollection.ResourceID = resourceid.NewCombined(database.ResourceID, resourceid.New(resourceid.ResourceTypeCollection))
newCollection.ETag = fmt.Sprintf("\"%s\"", uuid.New()) newCollection.ETag = fmt.Sprintf("\"%s\"", uuid.New())
newCollection.Self = fmt.Sprintf("dbs/%s/colls/%s/", database.ResourceID, newCollection.ResourceID) newCollection.Self = fmt.Sprintf("dbs/%s/colls/%s/", database.ResourceID, newCollection.ResourceID)
r.storeState.Collections[databaseId][newCollection.ID] = newCollection r.storeState.Collections[databaseId][newCollection.ID] = newCollection
r.storeState.Documents[databaseId][newCollection.ID] = make(map[string]repositorymodels.Document) r.storeState.Documents[databaseId][newCollection.ID] = make(map[string]repositorymodels.Document)
r.storeState.Triggers[databaseId][newCollection.ID] = make(map[string]repositorymodels.Trigger)
r.storeState.StoredProcedures[databaseId][newCollection.ID] = make(map[string]repositorymodels.StoredProcedure)
r.storeState.UserDefinedFunctions[databaseId][newCollection.ID] = make(map[string]repositorymodels.UserDefinedFunction)
return newCollection, repositorymodels.StatusOk return newCollection, repositorymodels.StatusOk
} }

View File

@@ -37,6 +37,11 @@ func (r *DataRepository) DeleteDatabase(id string) repositorymodels.RepositorySt
} }
delete(r.storeState.Databases, id) delete(r.storeState.Databases, id)
delete(r.storeState.Collections, id)
delete(r.storeState.Documents, id)
delete(r.storeState.Triggers, id)
delete(r.storeState.StoredProcedures, id)
delete(r.storeState.UserDefinedFunctions, id)
return repositorymodels.StatusOk return repositorymodels.StatusOk
} }
@@ -50,13 +55,16 @@ func (r *DataRepository) CreateDatabase(newDatabase repositorymodels.Database) (
} }
newDatabase.TimeStamp = time.Now().Unix() newDatabase.TimeStamp = time.Now().Unix()
newDatabase.ResourceID = resourceid.New() newDatabase.ResourceID = resourceid.New(resourceid.ResourceTypeDatabase)
newDatabase.ETag = fmt.Sprintf("\"%s\"", uuid.New()) newDatabase.ETag = fmt.Sprintf("\"%s\"", uuid.New())
newDatabase.Self = fmt.Sprintf("dbs/%s/", newDatabase.ResourceID) newDatabase.Self = fmt.Sprintf("dbs/%s/", newDatabase.ResourceID)
r.storeState.Databases[newDatabase.ID] = newDatabase r.storeState.Databases[newDatabase.ID] = newDatabase
r.storeState.Collections[newDatabase.ID] = make(map[string]repositorymodels.Collection) r.storeState.Collections[newDatabase.ID] = make(map[string]repositorymodels.Collection)
r.storeState.Documents[newDatabase.ID] = make(map[string]map[string]repositorymodels.Document) r.storeState.Documents[newDatabase.ID] = make(map[string]map[string]repositorymodels.Document)
r.storeState.Triggers[newDatabase.ID] = make(map[string]map[string]repositorymodels.Trigger)
r.storeState.StoredProcedures[newDatabase.ID] = make(map[string]map[string]repositorymodels.StoredProcedure)
r.storeState.UserDefinedFunctions[newDatabase.ID] = make(map[string]map[string]repositorymodels.UserDefinedFunction)
return newDatabase, repositorymodels.StatusOk return newDatabase, repositorymodels.StatusOk
} }

View File

@@ -95,7 +95,7 @@ func (r *DataRepository) CreateDocument(databaseId string, collectionId string,
} }
document["_ts"] = time.Now().Unix() document["_ts"] = time.Now().Unix()
document["_rid"] = resourceid.NewCombined(database.ResourceID, collection.ResourceID, resourceid.New()) document["_rid"] = resourceid.NewCombined(collection.ResourceID, resourceid.New(resourceid.ResourceTypeDocument))
document["_etag"] = fmt.Sprintf("\"%s\"", uuid.New()) document["_etag"] = fmt.Sprintf("\"%s\"", uuid.New())
document["_self"] = fmt.Sprintf("dbs/%s/colls/%s/docs/%s/", database.ResourceID, collection.ResourceID, document["_rid"]) document["_self"] = fmt.Sprintf("dbs/%s/colls/%s/docs/%s/", database.ResourceID, collection.ResourceID, document["_rid"])

View File

@@ -26,7 +26,7 @@ func (r *DataRepository) GetPartitionKeyRanges(databaseId string, collectionId s
timestamp = collection.TimeStamp timestamp = collection.TimeStamp
} }
pkrResourceId := resourceid.NewCombined(databaseRid, collectionRid, resourceid.New()) pkrResourceId := resourceid.NewCombined(collectionRid, resourceid.New(resourceid.ResourceTypePartitionKeyRange))
pkrSelf := fmt.Sprintf("dbs/%s/colls/%s/pkranges/%s/", databaseRid, collectionRid, pkrResourceId) pkrSelf := fmt.Sprintf("dbs/%s/colls/%s/pkranges/%s/", databaseRid, collectionRid, pkrResourceId)
etag := fmt.Sprintf("\"%s\"", uuid.New()) etag := fmt.Sprintf("\"%s\"", uuid.New())

View File

@@ -3,10 +3,7 @@ package repositories
import repositorymodels "github.com/pikami/cosmium/internal/repository_models" import repositorymodels "github.com/pikami/cosmium/internal/repository_models"
type DataRepository struct { type DataRepository struct {
storedProcedures []repositorymodels.StoredProcedure storeState repositorymodels.State
triggers []repositorymodels.Trigger
userDefinedFunctions []repositorymodels.UserDefinedFunction
storeState repositorymodels.State
initialDataFilePath string initialDataFilePath string
persistDataFilePath string persistDataFilePath string
@@ -19,13 +16,13 @@ type RepositoryOptions struct {
func NewDataRepository(options RepositoryOptions) *DataRepository { func NewDataRepository(options RepositoryOptions) *DataRepository {
repository := &DataRepository{ repository := &DataRepository{
storedProcedures: []repositorymodels.StoredProcedure{},
triggers: []repositorymodels.Trigger{},
userDefinedFunctions: []repositorymodels.UserDefinedFunction{},
storeState: repositorymodels.State{ storeState: repositorymodels.State{
Databases: make(map[string]repositorymodels.Database), Databases: make(map[string]repositorymodels.Database),
Collections: make(map[string]map[string]repositorymodels.Collection), Collections: make(map[string]map[string]repositorymodels.Collection),
Documents: make(map[string]map[string]map[string]repositorymodels.Document), Documents: make(map[string]map[string]map[string]repositorymodels.Document),
Triggers: make(map[string]map[string]map[string]repositorymodels.Trigger),
StoredProcedures: make(map[string]map[string]map[string]repositorymodels.StoredProcedure),
UserDefinedFunctions: make(map[string]map[string]map[string]repositorymodels.UserDefinedFunction),
}, },
initialDataFilePath: options.InitialDataFilePath, initialDataFilePath: options.InitialDataFilePath,
persistDataFilePath: options.PersistDataFilePath, persistDataFilePath: options.PersistDataFilePath,

View File

@@ -23,7 +23,7 @@ func (r *DataRepository) InitializeRepository() {
} }
if stat.IsDir() { if stat.IsDir() {
logger.Error("Argument '-Persist' must be a path to file, not a directory.") logger.ErrorLn("Argument '-Persist' must be a path to file, not a directory.")
os.Exit(1) os.Exit(1)
} }
@@ -46,8 +46,8 @@ func (r *DataRepository) LoadStateFS(filePath string) {
} }
func (r *DataRepository) LoadStateJSON(jsonData string) error { func (r *DataRepository) LoadStateJSON(jsonData string) error {
r.storeState.RLock() r.storeState.Lock()
defer r.storeState.RUnlock() defer r.storeState.Unlock()
var state repositorymodels.State var state repositorymodels.State
if err := json.Unmarshal([]byte(jsonData), &state); err != nil { if err := json.Unmarshal([]byte(jsonData), &state); err != nil {
@@ -60,10 +60,13 @@ func (r *DataRepository) LoadStateJSON(jsonData string) error {
r.ensureStoreStateNoNullReferences() r.ensureStoreStateNoNullReferences()
logger.Info("Loaded state:") logger.InfoLn("Loaded state:")
logger.Infof("Databases: %d\n", getLength(r.storeState.Databases)) logger.Infof("Databases: %d\n", getLength(r.storeState.Databases))
logger.Infof("Collections: %d\n", getLength(r.storeState.Collections)) logger.Infof("Collections: %d\n", getLength(r.storeState.Collections))
logger.Infof("Documents: %d\n", getLength(r.storeState.Documents)) logger.Infof("Documents: %d\n", getLength(r.storeState.Documents))
logger.Infof("Triggers: %d\n", getLength(r.storeState.Triggers))
logger.Infof("Stored procedures: %d\n", getLength(r.storeState.StoredProcedures))
logger.Infof("User defined functions: %d\n", getLength(r.storeState.UserDefinedFunctions))
return nil return nil
} }
@@ -80,10 +83,13 @@ func (r *DataRepository) SaveStateFS(filePath string) {
os.WriteFile(filePath, data, os.ModePerm) os.WriteFile(filePath, data, os.ModePerm)
logger.Info("Saved state:") logger.InfoLn("Saved state:")
logger.Infof("Databases: %d\n", getLength(r.storeState.Databases)) logger.Infof("Databases: %d\n", getLength(r.storeState.Databases))
logger.Infof("Collections: %d\n", getLength(r.storeState.Collections)) logger.Infof("Collections: %d\n", getLength(r.storeState.Collections))
logger.Infof("Documents: %d\n", getLength(r.storeState.Documents)) logger.Infof("Documents: %d\n", getLength(r.storeState.Documents))
logger.Infof("Triggers: %d\n", getLength(r.storeState.Triggers))
logger.Infof("Stored procedures: %d\n", getLength(r.storeState.StoredProcedures))
logger.Infof("User defined functions: %d\n", getLength(r.storeState.UserDefinedFunctions))
} }
func (r *DataRepository) GetState() (string, error) { func (r *DataRepository) GetState() (string, error) {
@@ -103,7 +109,10 @@ func getLength(v interface{}) int {
switch v.(type) { switch v.(type) {
case repositorymodels.Database, case repositorymodels.Database,
repositorymodels.Collection, repositorymodels.Collection,
repositorymodels.Document: repositorymodels.Document,
repositorymodels.Trigger,
repositorymodels.StoredProcedure,
repositorymodels.UserDefinedFunction:
return 1 return 1
} }
@@ -137,6 +146,18 @@ func (r *DataRepository) ensureStoreStateNoNullReferences() {
r.storeState.Documents = make(map[string]map[string]map[string]repositorymodels.Document) r.storeState.Documents = make(map[string]map[string]map[string]repositorymodels.Document)
} }
if r.storeState.Triggers == nil {
r.storeState.Triggers = make(map[string]map[string]map[string]repositorymodels.Trigger)
}
if r.storeState.StoredProcedures == nil {
r.storeState.StoredProcedures = make(map[string]map[string]map[string]repositorymodels.StoredProcedure)
}
if r.storeState.UserDefinedFunctions == nil {
r.storeState.UserDefinedFunctions = make(map[string]map[string]map[string]repositorymodels.UserDefinedFunction)
}
for database := range r.storeState.Databases { for database := range r.storeState.Databases {
if r.storeState.Collections[database] == nil { if r.storeState.Collections[database] == nil {
r.storeState.Collections[database] = make(map[string]repositorymodels.Collection) r.storeState.Collections[database] = make(map[string]repositorymodels.Collection)
@@ -146,6 +167,18 @@ func (r *DataRepository) ensureStoreStateNoNullReferences() {
r.storeState.Documents[database] = make(map[string]map[string]repositorymodels.Document) r.storeState.Documents[database] = make(map[string]map[string]repositorymodels.Document)
} }
if r.storeState.Triggers[database] == nil {
r.storeState.Triggers[database] = make(map[string]map[string]repositorymodels.Trigger)
}
if r.storeState.StoredProcedures[database] == nil {
r.storeState.StoredProcedures[database] = make(map[string]map[string]repositorymodels.StoredProcedure)
}
if r.storeState.UserDefinedFunctions[database] == nil {
r.storeState.UserDefinedFunctions[database] = make(map[string]map[string]repositorymodels.UserDefinedFunction)
}
for collection := range r.storeState.Collections[database] { for collection := range r.storeState.Collections[database] {
if r.storeState.Documents[database][collection] == nil { if r.storeState.Documents[database][collection] == nil {
r.storeState.Documents[database][collection] = make(map[string]repositorymodels.Document) r.storeState.Documents[database][collection] = make(map[string]repositorymodels.Document)
@@ -156,6 +189,18 @@ func (r *DataRepository) ensureStoreStateNoNullReferences() {
delete(r.storeState.Documents[database][collection], document) delete(r.storeState.Documents[database][collection], document)
} }
} }
if r.storeState.Triggers[database][collection] == nil {
r.storeState.Triggers[database][collection] = make(map[string]repositorymodels.Trigger)
}
if r.storeState.StoredProcedures[database][collection] == nil {
r.storeState.StoredProcedures[database][collection] = make(map[string]repositorymodels.StoredProcedure)
}
if r.storeState.UserDefinedFunctions[database][collection] == nil {
r.storeState.UserDefinedFunctions[database][collection] = make(map[string]repositorymodels.UserDefinedFunction)
}
} }
} }
} }

View File

@@ -1,7 +1,91 @@
package repositories package repositories
import repositorymodels "github.com/pikami/cosmium/internal/repository_models" import (
"fmt"
"time"
"github.com/google/uuid"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
"github.com/pikami/cosmium/internal/resourceid"
"golang.org/x/exp/maps"
)
func (r *DataRepository) GetAllStoredProcedures(databaseId string, collectionId string) ([]repositorymodels.StoredProcedure, repositorymodels.RepositoryStatus) { func (r *DataRepository) GetAllStoredProcedures(databaseId string, collectionId string) ([]repositorymodels.StoredProcedure, repositorymodels.RepositoryStatus) {
return r.storedProcedures, repositorymodels.StatusOk r.storeState.RLock()
defer r.storeState.RUnlock()
return maps.Values(r.storeState.StoredProcedures[databaseId][collectionId]), repositorymodels.StatusOk
}
func (r *DataRepository) GetStoredProcedure(databaseId string, collectionId string, spId string) (repositorymodels.StoredProcedure, repositorymodels.RepositoryStatus) {
r.storeState.RLock()
defer r.storeState.RUnlock()
if _, ok := r.storeState.Databases[databaseId]; !ok {
return repositorymodels.StoredProcedure{}, repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.StoredProcedure{}, repositorymodels.StatusNotFound
}
if sp, ok := r.storeState.StoredProcedures[databaseId][collectionId][spId]; ok {
return sp, repositorymodels.StatusOk
}
return repositorymodels.StoredProcedure{}, repositorymodels.StatusNotFound
}
func (r *DataRepository) DeleteStoredProcedure(databaseId string, collectionId string, spId string) repositorymodels.RepositoryStatus {
r.storeState.Lock()
defer r.storeState.Unlock()
if _, ok := r.storeState.Databases[databaseId]; !ok {
return repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.StatusNotFound
}
if _, ok := r.storeState.StoredProcedures[databaseId][collectionId][spId]; !ok {
return repositorymodels.StatusNotFound
}
delete(r.storeState.StoredProcedures[databaseId][collectionId], spId)
return repositorymodels.StatusOk
}
func (r *DataRepository) CreateStoredProcedure(databaseId string, collectionId string, sp repositorymodels.StoredProcedure) (repositorymodels.StoredProcedure, repositorymodels.RepositoryStatus) {
r.storeState.Lock()
defer r.storeState.Unlock()
var ok bool
var database repositorymodels.Database
var collection repositorymodels.Collection
if sp.ID == "" {
return repositorymodels.StoredProcedure{}, repositorymodels.BadRequest
}
if database, ok = r.storeState.Databases[databaseId]; !ok {
return repositorymodels.StoredProcedure{}, repositorymodels.StatusNotFound
}
if collection, ok = r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.StoredProcedure{}, repositorymodels.StatusNotFound
}
if _, ok = r.storeState.StoredProcedures[databaseId][collectionId][sp.ID]; ok {
return repositorymodels.StoredProcedure{}, repositorymodels.Conflict
}
sp.TimeStamp = time.Now().Unix()
sp.ResourceID = resourceid.NewCombined(collection.ResourceID, resourceid.New(resourceid.ResourceTypeStoredProcedure))
sp.ETag = fmt.Sprintf("\"%s\"", uuid.New())
sp.Self = fmt.Sprintf("dbs/%s/colls/%s/sprocs/%s/", database.ResourceID, collection.ResourceID, sp.ResourceID)
r.storeState.StoredProcedures[databaseId][collectionId][sp.ID] = sp
return sp, repositorymodels.StatusOk
} }

View File

@@ -1,7 +1,91 @@
package repositories package repositories
import repositorymodels "github.com/pikami/cosmium/internal/repository_models" import (
"fmt"
"time"
"github.com/google/uuid"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
"github.com/pikami/cosmium/internal/resourceid"
"golang.org/x/exp/maps"
)
func (r *DataRepository) GetAllTriggers(databaseId string, collectionId string) ([]repositorymodels.Trigger, repositorymodels.RepositoryStatus) { func (r *DataRepository) GetAllTriggers(databaseId string, collectionId string) ([]repositorymodels.Trigger, repositorymodels.RepositoryStatus) {
return r.triggers, repositorymodels.StatusOk r.storeState.RLock()
defer r.storeState.RUnlock()
return maps.Values(r.storeState.Triggers[databaseId][collectionId]), repositorymodels.StatusOk
}
func (r *DataRepository) GetTrigger(databaseId string, collectionId string, triggerId string) (repositorymodels.Trigger, repositorymodels.RepositoryStatus) {
r.storeState.RLock()
defer r.storeState.RUnlock()
if _, ok := r.storeState.Databases[databaseId]; !ok {
return repositorymodels.Trigger{}, repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.Trigger{}, repositorymodels.StatusNotFound
}
if trigger, ok := r.storeState.Triggers[databaseId][collectionId][triggerId]; ok {
return trigger, repositorymodels.StatusOk
}
return repositorymodels.Trigger{}, repositorymodels.StatusNotFound
}
func (r *DataRepository) DeleteTrigger(databaseId string, collectionId string, triggerId string) repositorymodels.RepositoryStatus {
r.storeState.Lock()
defer r.storeState.Unlock()
if _, ok := r.storeState.Databases[databaseId]; !ok {
return repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Triggers[databaseId][collectionId][triggerId]; !ok {
return repositorymodels.StatusNotFound
}
delete(r.storeState.Triggers[databaseId][collectionId], triggerId)
return repositorymodels.StatusOk
}
func (r *DataRepository) CreateTrigger(databaseId string, collectionId string, trigger repositorymodels.Trigger) (repositorymodels.Trigger, repositorymodels.RepositoryStatus) {
r.storeState.Lock()
defer r.storeState.Unlock()
var ok bool
var database repositorymodels.Database
var collection repositorymodels.Collection
if trigger.ID == "" {
return repositorymodels.Trigger{}, repositorymodels.BadRequest
}
if database, ok = r.storeState.Databases[databaseId]; !ok {
return repositorymodels.Trigger{}, repositorymodels.StatusNotFound
}
if collection, ok = r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.Trigger{}, repositorymodels.StatusNotFound
}
if _, ok = r.storeState.Triggers[databaseId][collectionId][trigger.ID]; ok {
return repositorymodels.Trigger{}, repositorymodels.Conflict
}
trigger.TimeStamp = time.Now().Unix()
trigger.ResourceID = resourceid.NewCombined(collection.ResourceID, resourceid.New(resourceid.ResourceTypeTrigger))
trigger.ETag = fmt.Sprintf("\"%s\"", uuid.New())
trigger.Self = fmt.Sprintf("dbs/%s/colls/%s/triggers/%s/", database.ResourceID, collection.ResourceID, trigger.ResourceID)
r.storeState.Triggers[databaseId][collectionId][trigger.ID] = trigger
return trigger, repositorymodels.StatusOk
} }

View File

@@ -1,7 +1,91 @@
package repositories package repositories
import repositorymodels "github.com/pikami/cosmium/internal/repository_models" import (
"fmt"
"time"
"github.com/google/uuid"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
"github.com/pikami/cosmium/internal/resourceid"
"golang.org/x/exp/maps"
)
func (r *DataRepository) GetAllUserDefinedFunctions(databaseId string, collectionId string) ([]repositorymodels.UserDefinedFunction, repositorymodels.RepositoryStatus) { func (r *DataRepository) GetAllUserDefinedFunctions(databaseId string, collectionId string) ([]repositorymodels.UserDefinedFunction, repositorymodels.RepositoryStatus) {
return r.userDefinedFunctions, repositorymodels.StatusOk r.storeState.RLock()
defer r.storeState.RUnlock()
return maps.Values(r.storeState.UserDefinedFunctions[databaseId][collectionId]), repositorymodels.StatusOk
}
func (r *DataRepository) GetUserDefinedFunction(databaseId string, collectionId string, udfId string) (repositorymodels.UserDefinedFunction, repositorymodels.RepositoryStatus) {
r.storeState.RLock()
defer r.storeState.RUnlock()
if _, ok := r.storeState.Databases[databaseId]; !ok {
return repositorymodels.UserDefinedFunction{}, repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.UserDefinedFunction{}, repositorymodels.StatusNotFound
}
if udf, ok := r.storeState.UserDefinedFunctions[databaseId][collectionId][udfId]; ok {
return udf, repositorymodels.StatusOk
}
return repositorymodels.UserDefinedFunction{}, repositorymodels.StatusNotFound
}
func (r *DataRepository) DeleteUserDefinedFunction(databaseId string, collectionId string, udfId string) repositorymodels.RepositoryStatus {
r.storeState.Lock()
defer r.storeState.Unlock()
if _, ok := r.storeState.Databases[databaseId]; !ok {
return repositorymodels.StatusNotFound
}
if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.StatusNotFound
}
if _, ok := r.storeState.UserDefinedFunctions[databaseId][collectionId][udfId]; !ok {
return repositorymodels.StatusNotFound
}
delete(r.storeState.UserDefinedFunctions[databaseId][collectionId], udfId)
return repositorymodels.StatusOk
}
func (r *DataRepository) CreateUserDefinedFunction(databaseId string, collectionId string, udf repositorymodels.UserDefinedFunction) (repositorymodels.UserDefinedFunction, repositorymodels.RepositoryStatus) {
r.storeState.Lock()
defer r.storeState.Unlock()
var ok bool
var database repositorymodels.Database
var collection repositorymodels.Collection
if udf.ID == "" {
return repositorymodels.UserDefinedFunction{}, repositorymodels.BadRequest
}
if database, ok = r.storeState.Databases[databaseId]; !ok {
return repositorymodels.UserDefinedFunction{}, repositorymodels.StatusNotFound
}
if collection, ok = r.storeState.Collections[databaseId][collectionId]; !ok {
return repositorymodels.UserDefinedFunction{}, repositorymodels.StatusNotFound
}
if _, ok := r.storeState.UserDefinedFunctions[databaseId][collectionId][udf.ID]; ok {
return repositorymodels.UserDefinedFunction{}, repositorymodels.Conflict
}
udf.TimeStamp = time.Now().Unix()
udf.ResourceID = resourceid.NewCombined(collection.ResourceID, resourceid.New(resourceid.ResourceTypeUserDefinedFunction))
udf.ETag = fmt.Sprintf("\"%s\"", uuid.New())
udf.Self = fmt.Sprintf("dbs/%s/colls/%s/udfs/%s/", database.ResourceID, collection.ResourceID, udf.ResourceID)
r.storeState.UserDefinedFunctions[databaseId][collectionId][udf.ID] = udf
return udf, repositorymodels.StatusOk
} }

View File

@@ -19,6 +19,22 @@ const (
BadRequest = 4 BadRequest = 4
) )
type TriggerOperation string
const (
All TriggerOperation = "All"
Create TriggerOperation = "Create"
Delete TriggerOperation = "Delete"
Replace TriggerOperation = "Replace"
)
type TriggerType string
const (
Pre TriggerType = "Pre"
Post TriggerType = "Post"
)
type Collection struct { type Collection struct {
ID string `json:"id"` ID string `json:"id"`
IndexingPolicy CollectionIndexingPolicy `json:"indexingPolicy"` IndexingPolicy CollectionIndexingPolicy `json:"indexingPolicy"`
@@ -60,29 +76,29 @@ type UserDefinedFunction struct {
Body string `json:"body"` Body string `json:"body"`
ID string `json:"id"` ID string `json:"id"`
ResourceID string `json:"_rid"` ResourceID string `json:"_rid"`
TimeStamp int `json:"_ts"` TimeStamp int64 `json:"_ts"`
Self string `json:"_self"` Self string `json:"_self"`
Etag string `json:"_etag"` ETag string `json:"_etag"`
} }
type StoredProcedure struct { type StoredProcedure struct {
Body string `json:"body"` Body string `json:"body"`
ID string `json:"id"` ID string `json:"id"`
ResourceID string `json:"_rid"` ResourceID string `json:"_rid"`
TimeStamp int `json:"_ts"` TimeStamp int64 `json:"_ts"`
Self string `json:"_self"` Self string `json:"_self"`
Etag string `json:"_etag"` ETag string `json:"_etag"`
} }
type Trigger struct { type Trigger struct {
Body string `json:"body"` Body string `json:"body"`
ID string `json:"id"` ID string `json:"id"`
TriggerOperation string `json:"triggerOperation"` TriggerOperation TriggerOperation `json:"triggerOperation"`
TriggerType string `json:"triggerType"` TriggerType TriggerType `json:"triggerType"`
ResourceID string `json:"_rid"` ResourceID string `json:"_rid"`
TimeStamp int `json:"_ts"` TimeStamp int64 `json:"_ts"`
Self string `json:"_self"` Self string `json:"_self"`
Etag string `json:"_etag"` ETag string `json:"_etag"`
} }
type Document map[string]interface{} type Document map[string]interface{}
@@ -113,4 +129,13 @@ type State struct {
// Map databaseId -> collectionId -> documentId -> Documents // Map databaseId -> collectionId -> documentId -> Documents
Documents map[string]map[string]map[string]Document `json:"documents"` Documents map[string]map[string]map[string]Document `json:"documents"`
// Map databaseId -> collectionId -> triggerId -> Trigger
Triggers map[string]map[string]map[string]Trigger `json:"triggers"`
// Map databaseId -> collectionId -> spId -> StoredProcedure
StoredProcedures map[string]map[string]map[string]StoredProcedure `json:"sprocs"`
// Map databaseId -> collectionId -> udfId -> UserDefinedFunction
UserDefinedFunctions map[string]map[string]map[string]UserDefinedFunction `json:"udfs"`
} }

View File

@@ -3,32 +3,76 @@ package resourceid
import ( import (
"encoding/base64" "encoding/base64"
"math/rand" "math/rand"
"strings"
"github.com/google/uuid" "github.com/google/uuid"
) )
func New() string { type ResourceType int
id := uuid.New().ID()
idBytes := uintToBytes(id)
// first byte should be bigger than 0x80 for collection ids const (
// clients classify this id as "user" otherwise ResourceTypeDatabase ResourceType = iota
if (idBytes[0] & 0x80) <= 0 { ResourceTypeCollection
idBytes[0] = byte(rand.Intn(0x80) + 0x80) ResourceTypeDocument
ResourceTypeStoredProcedure
ResourceTypeTrigger
ResourceTypeUserDefinedFunction
ResourceTypeConflict
ResourceTypePartitionKeyRange
ResourceTypeSchema
)
func New(resourceType ResourceType) string {
var idBytes []byte
switch resourceType {
case ResourceTypeDatabase:
idBytes = randomBytes(4)
case ResourceTypeCollection:
idBytes = randomBytes(4)
// first byte should be bigger than 0x80 for collection ids
// clients classify this id as "user" otherwise
if (idBytes[0] & 0x80) <= 0 {
idBytes[0] = byte(rand.Intn(0x80) + 0x80)
}
case ResourceTypeDocument:
idBytes = randomBytes(8)
idBytes[7] = byte(rand.Intn(0x10)) // Upper 4 bits = 0
case ResourceTypeStoredProcedure:
idBytes = randomBytes(8)
idBytes[7] = byte(rand.Intn(0x10)) | 0x08 // Upper 4 bits = 0x08
case ResourceTypeTrigger:
idBytes = randomBytes(8)
idBytes[7] = byte(rand.Intn(0x10)) | 0x07 // Upper 4 bits = 0x07
case ResourceTypeUserDefinedFunction:
idBytes = randomBytes(8)
idBytes[7] = byte(rand.Intn(0x10)) | 0x06 // Upper 4 bits = 0x06
case ResourceTypeConflict:
idBytes = randomBytes(8)
idBytes[7] = byte(rand.Intn(0x10)) | 0x04 // Upper 4 bits = 0x04
case ResourceTypePartitionKeyRange:
// we don't do partitions yet, so just use a fixed id
idBytes = []byte{0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x50}
case ResourceTypeSchema:
idBytes = randomBytes(8)
idBytes[7] = byte(rand.Intn(0x10)) | 0x09 // Upper 4 bits = 0x09
default:
idBytes = randomBytes(4)
} }
return base64.StdEncoding.EncodeToString(idBytes) encoded := base64.StdEncoding.EncodeToString(idBytes)
return strings.ReplaceAll(encoded, "/", "-")
} }
func NewCombined(ids ...string) string { func NewCombined(ids ...string) string {
combinedIdBytes := make([]byte, 0) combinedIdBytes := make([]byte, 0)
for _, id := range ids { for _, id := range ids {
idBytes, _ := base64.StdEncoding.DecodeString(id) idBytes, _ := base64.StdEncoding.DecodeString(strings.ReplaceAll(id, "-", "/"))
combinedIdBytes = append(combinedIdBytes, idBytes...) combinedIdBytes = append(combinedIdBytes, idBytes...)
} }
return base64.StdEncoding.EncodeToString(combinedIdBytes) encoded := base64.StdEncoding.EncodeToString(combinedIdBytes)
return strings.ReplaceAll(encoded, "/", "-")
} }
func uintToBytes(id uint32) []byte { func uintToBytes(id uint32) []byte {
@@ -39,3 +83,13 @@ func uintToBytes(id uint32) []byte {
return buf return buf
} }
func randomBytes(count int) []byte {
buf := make([]byte, count)
for i := 0; i < count; i += 4 {
id := uuid.New().ID()
idBytes := uintToBytes(id)
copy(buf[i:], idBytes)
}
return buf
}

View File

@@ -9,7 +9,7 @@ import (
func GetDefaultTlsConfig() *tls.Config { func GetDefaultTlsConfig() *tls.Config {
cert, err := tls.X509KeyPair([]byte(certificate), []byte(certificateKey)) cert, err := tls.X509KeyPair([]byte(certificate), []byte(certificateKey))
if err != nil { if err != nil {
logger.Error("Failed to parse certificate and key:", err) logger.ErrorLn("Failed to parse certificate and key:", err)
return &tls.Config{} return &tls.Config{}
} }

View File

@@ -17,6 +17,7 @@ type SelectStmt struct {
type Table struct { type Table struct {
Value string Value string
SelectItem SelectItem SelectItem SelectItem
IsInSelect bool
} }
type JoinItem struct { type JoinItem struct {
@@ -41,6 +42,7 @@ type SelectItem struct {
SelectItems []SelectItem SelectItems []SelectItem
Type SelectItemType Type SelectItemType
Value interface{} Value interface{}
Invert bool
IsTopLevel bool IsTopLevel bool
} }
@@ -123,11 +125,14 @@ const (
FunctionCallIsPrimitive FunctionCallType = "IsPrimitive" FunctionCallIsPrimitive FunctionCallType = "IsPrimitive"
FunctionCallIsString FunctionCallType = "IsString" FunctionCallIsString FunctionCallType = "IsString"
FunctionCallArrayConcat FunctionCallType = "ArrayConcat" FunctionCallArrayConcat FunctionCallType = "ArrayConcat"
FunctionCallArrayLength FunctionCallType = "ArrayLength" FunctionCallArrayContains FunctionCallType = "ArrayContains"
FunctionCallArraySlice FunctionCallType = "ArraySlice" FunctionCallArrayContainsAny FunctionCallType = "ArrayContainsAny"
FunctionCallSetIntersect FunctionCallType = "SetIntersect" FunctionCallArrayContainsAll FunctionCallType = "ArrayContainsAll"
FunctionCallSetUnion FunctionCallType = "SetUnion" FunctionCallArrayLength FunctionCallType = "ArrayLength"
FunctionCallArraySlice FunctionCallType = "ArraySlice"
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
FunctionCallSetUnion FunctionCallType = "SetUnion"
FunctionCallMathAbs FunctionCallType = "MathAbs" FunctionCallMathAbs FunctionCallType = "MathAbs"
FunctionCallMathAcos FunctionCallType = "MathAcos" FunctionCallMathAcos FunctionCallType = "MathAcos"

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Parse_AggregateFunctions(t *testing.T) { func Test_Parse_AggregateFunctions(t *testing.T) {
@@ -27,7 +28,7 @@ func Test_Parse_AggregateFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -51,7 +52,7 @@ func Test_Parse_AggregateFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -75,7 +76,7 @@ func Test_Parse_AggregateFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -99,7 +100,7 @@ func Test_Parse_AggregateFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -123,7 +124,7 @@ func Test_Parse_AggregateFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Parse_ArrayFunctions(t *testing.T) { func Test_Parse_ArrayFunctions(t *testing.T) {
@@ -31,7 +32,120 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse function ARRAY_CONTAINS()", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_CONTAINS(c.a1, "value") FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "a1"},
Type: parsers.SelectItemTypeField,
},
testutils.SelectItem_Constant_String("value"),
nil,
},
},
},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse function ARRAY_CONTAINS() with partial match", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_CONTAINS(["a", "b"], "value", true) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
parsers.SelectItem{
Type: parsers.SelectItemTypeArray,
SelectItems: []parsers.SelectItem{
testutils.SelectItem_Constant_String("a"),
testutils.SelectItem_Constant_String("b"),
},
},
testutils.SelectItem_Constant_String("value"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse function ARRAY_CONTAINS_ANY()", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_CONTAINS_ANY(["a", "b"], "value", true) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
parsers.SelectItem{
Type: parsers.SelectItemTypeArray,
SelectItems: []parsers.SelectItem{
testutils.SelectItem_Constant_String("a"),
testutils.SelectItem_Constant_String("b"),
},
},
testutils.SelectItem_Constant_String("value"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse function ARRAY_CONTAINS_ALL()", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_CONTAINS_ALL(["a", "b"], "value", true) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
parsers.SelectItem{
Type: parsers.SelectItemTypeArray,
SelectItems: []parsers.SelectItem{
testutils.SelectItem_Constant_String("a"),
testutils.SelectItem_Constant_String("b"),
},
},
testutils.SelectItem_Constant_String("value"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -55,7 +169,7 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -75,25 +189,13 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
Path: []string{"c", "array"}, Path: []string{"c", "array"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(0),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(2),
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 0,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -121,7 +223,7 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -149,7 +251,7 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Parse_Join(t *testing.T) { func Test_Parse_Join(t *testing.T) {
@@ -17,7 +18,7 @@ func Test_Parse_Join(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
Table: parsers.Table{ Table: parsers.Table{
@@ -40,7 +41,7 @@ func Test_Parse_Join(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"cc"}, IsTopLevel: true}, {Path: []string{"cc"}, IsTopLevel: true},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
Table: parsers.Table{ Table: parsers.Table{

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_MathFunctions(t *testing.T) { func Test_Execute_MathFunctions(t *testing.T) {
@@ -644,7 +645,7 @@ func testMathFunctionParse(
}, },
}, },
}, },
Table: parsers.Table{Value: expectedTable}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path(expectedTable)},
}, },
) )
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
"github.com/pikami/cosmium/parsers/nosql" "github.com/pikami/cosmium/parsers/nosql"
testutils "github.com/pikami/cosmium/test_utils"
) )
// For Parser Debugging // For Parser Debugging
@@ -48,7 +49,7 @@ func Test_Parse(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
OrderExpressions: []parsers.OrderExpression{ OrderExpressions: []parsers.OrderExpression{
{ {
SelectItem: parsers.SelectItem{Path: []string{"c", "id"}}, SelectItem: parsers.SelectItem{Path: []string{"c", "id"}},
@@ -72,7 +73,7 @@ func Test_Parse(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
@@ -92,7 +93,7 @@ func Test_Parse(t *testing.T) {
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -102,20 +103,40 @@ func Test_Parse(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
},
},
},
},
)
})
t.Run("Should parse IN function with function call", func(t *testing.T) {
testQueryParse(
t,
`Select c.id FROM c WHERE (ToString(c.id) IN ("123", "456"))`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
testutils.SelectItem_Path("c", "id"),
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallIn,
Arguments: []interface{}{
parsers.SelectItem{ parsers.SelectItem{
Type: parsers.SelectItemTypeConstant, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.Constant{ Value: parsers.FunctionCall{
Type: parsers.ConstantTypeString, Type: parsers.FunctionCallToString,
Value: "123", Arguments: []interface{}{
}, testutils.SelectItem_Path("c", "id"),
}, },
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "456",
}, },
}, },
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
}, },
}, },
}, },
@@ -135,10 +156,9 @@ func Test_Parse(t *testing.T) {
}, },
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "c", Value: "c",
SelectItem: parsers.SelectItem{ SelectItem: testutils.SelectItem_Path("c", "tags"),
Path: []string{"c", "tags"}, IsInSelect: true,
},
}, },
}, },
) )

File diff suppressed because it is too large Load Diff

View File

@@ -204,14 +204,22 @@ TopClause <- Top ws count:Integer {
return count, nil return count, nil
} }
FromClause <- From ws table:TableName selectItem:(ws "IN"i ws column:SelectItem { return column, nil })? { FromClause <- From ws table:TableName selectItem:(ws In ws column:SelectItem { return column, nil }) {
tableTyped := table.(parsers.Table) tableTyped := table.(parsers.Table)
if selectItem != nil { if selectItem != nil {
tableTyped.SelectItem = selectItem.(parsers.SelectItem) tableTyped.SelectItem = selectItem.(parsers.SelectItem)
tableTyped.IsInSelect = true
} }
return tableTyped, nil return tableTyped, nil
} / From ws column:SelectItem {
tableSelectItem := column.(parsers.SelectItem)
table := parsers.Table{
Value: tableSelectItem.Alias,
SelectItem: tableSelectItem,
}
return table, nil
} / From ws subQuery:SubQuerySelectItem { } / From ws subQuery:SubQuerySelectItem {
subQueryTyped := subQuery.(parsers.SelectItem) subQueryTyped := subQuery.(parsers.SelectItem)
table := parsers.Table{ table := parsers.Table{
@@ -243,13 +251,13 @@ SubQuerySelectItem <- subQuery:SubQuery asClause:(ws alias:AsClause { return ali
return selectItem, nil return selectItem, nil
} }
JoinClause <- Join ws table:TableName ws "IN"i ws column:SelectItem { JoinClause <- Join ws table:TableName ws In ws column:SelectItem {
return makeJoin(table, column) return makeJoin(table, column)
} / Join ws subQuery:SubQuerySelectItem { } / Join ws subQuery:SubQuerySelectItem {
return makeJoin(nil, subQuery) return makeJoin(nil, subQuery)
} }
OffsetClause <- "OFFSET"i ws offset:IntegerLiteral ws "LIMIT"i ws limit:IntegerLiteral { OffsetClause <- Offset ws offset:IntegerLiteral ws "LIMIT"i ws limit:IntegerLiteral {
return []interface{}{offset.(parsers.Constant).Value, limit.(parsers.Constant).Value}, nil return []interface{}{offset.(parsers.Constant).Value, limit.(parsers.Constant).Value}, nil
} }
@@ -317,7 +325,11 @@ SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectAr
return itemResult, nil return itemResult, nil
} }
AsClause <- ws As ws alias:Identifier { return alias, nil } AsClause <- (ws As)? ws !ExcludedKeywords alias:Identifier {
return alias, nil
}
ExcludedKeywords <- Select / Top / As / From / In / Join / Exists / Where / And / Or / Not / GroupBy / OrderBy / Offset
DotFieldAccess <- "." id:Identifier { DotFieldAccess <- "." id:Identifier {
return id, nil return id, nil
@@ -346,8 +358,14 @@ AndExpression <- ex1:ComparisonExpression ex2:(ws And ws ex:ComparisonExpression
ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil } ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil }
/ left:SelectItem ws op:ComparisonOperator ws right:SelectItem { / left:SelectItem ws op:ComparisonOperator ws right:SelectItem {
return parsers.ComparisonExpression{Left:left,Right:right,Operation:op.(string)}, nil return parsers.ComparisonExpression{Left:left,Right:right,Operation:op.(string)}, nil
} / inv:(Not ws)? ex:SelectItem {
if inv != nil {
ex1 := ex.(parsers.SelectItem)
ex1.Invert = true
return ex1, nil
}
return ex, nil
} / ex:BooleanLiteral { return ex, nil } } / ex:BooleanLiteral { return ex, nil }
/ ex:SelectItem { return ex, nil }
OrderByClause <- OrderBy ws ex1:OrderExpression others:(ws "," ws ex:OrderExpression { return ex, nil })* { OrderByClause <- OrderBy ws ex1:OrderExpression others:(ws "," ws ex:OrderExpression { return ex, nil })* {
return makeOrderByClause(ex1, others) return makeOrderByClause(ex1, others)
@@ -373,6 +391,8 @@ As <- "AS"i
From <- "FROM"i From <- "FROM"i
In <- "IN"i
Join <- "JOIN"i Join <- "JOIN"i
Exists <- "EXISTS"i Exists <- "EXISTS"i
@@ -383,11 +403,15 @@ And <- "AND"i
Or <- "OR"i wss Or <- "OR"i wss
Not <- "NOT"i
GroupBy <- "GROUP"i ws "BY"i GroupBy <- "GROUP"i ws "BY"i
OrderBy <- "ORDER"i ws "BY"i OrderBy <- "ORDER"i ws "BY"i
ComparisonOperator <- ("=" / "!=" / "<" / "<=" / ">" / ">=") { Offset <- "OFFSET"i
ComparisonOperator <- ("<=" / ">=" / "=" / "!=" / "<" / ">") {
return string(c.text), nil return string(c.text), nil
} }
@@ -457,6 +481,9 @@ AggregateFunctions <- AvgAggregateExpression
/ SumAggregateExpression / SumAggregateExpression
ArrayFunctions <- ArrayConcatExpression ArrayFunctions <- ArrayConcatExpression
/ ArrayContainsExpression
/ ArrayContainsAnyExpression
/ ArrayContainsAllExpression
/ ArrayLengthExpression / ArrayLengthExpression
/ ArraySliceExpression / ArraySliceExpression
/ SetIntersectExpression / SetIntersectExpression
@@ -626,6 +653,18 @@ ArrayConcatExpression <- "ARRAY_CONCAT"i ws "(" ws arrays:SelectItem others:(ws
return createFunctionCall(parsers.FunctionCallArrayConcat, append([]interface{}{arrays}, others.([]interface{})...)) return createFunctionCall(parsers.FunctionCallArrayConcat, append([]interface{}{arrays}, others.([]interface{})...))
} }
ArrayContainsExpression <- "ARRAY_CONTAINS"i ws "(" ws array:SelectItem ws "," ws item:SelectItem partialMatch:(ws "," ws ex:SelectItem { return ex, nil })? ws ")" {
return createFunctionCall(parsers.FunctionCallArrayContains, []interface{}{array, item, partialMatch})
}
ArrayContainsAnyExpression <- "ARRAY_CONTAINS_ANY"i ws "(" ws array:SelectItem items:(ws "," ws ex:SelectItem { return ex, nil })+ ws ")" {
return createFunctionCall(parsers.FunctionCallArrayContainsAny, append([]interface{}{array}, items.([]interface{})...))
}
ArrayContainsAllExpression <- "ARRAY_CONTAINS_ALL"i ws "(" ws array:SelectItem items:(ws "," ws ex:SelectItem { return ex, nil })+ ws ")" {
return createFunctionCall(parsers.FunctionCallArrayContainsAll, append([]interface{}{array}, items.([]interface{})...))
}
ArrayLengthExpression <- "ARRAY_LENGTH"i ws "(" ws array:SelectItem ws ")" { ArrayLengthExpression <- "ARRAY_LENGTH"i ws "(" ws array:SelectItem ws ")" {
return createFunctionCall(parsers.FunctionCallArrayLength, []interface{}{array}) return createFunctionCall(parsers.FunctionCallArrayLength, []interface{}{array})
} }
@@ -685,7 +724,9 @@ MathNumberBinExpression <- "NumberBin"i ws "(" ws ex1:SelectItem others:(ws ","
MathPiExpression <- "PI"i ws "(" ws ")" { return createFunctionCall(parsers.FunctionCallMathPi, []interface{}{}) } MathPiExpression <- "PI"i ws "(" ws ")" { return createFunctionCall(parsers.FunctionCallMathPi, []interface{}{}) }
MathRandExpression <- "RAND"i ws "(" ws ")" { return createFunctionCall(parsers.FunctionCallMathRand, []interface{}{}) } MathRandExpression <- "RAND"i ws "(" ws ")" { return createFunctionCall(parsers.FunctionCallMathRand, []interface{}{}) }
InFunction <- ex1:SelectProperty ws "IN"i ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" { InFunction <- ex1:SelectProperty ws In ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" {
return createFunctionCall(parsers.FunctionCallIn, append([]interface{}{ex1, ex2}, others.([]interface{})...))
} / "(" ws ex1:SelectItem ws In ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" ws ")" {
return createFunctionCall(parsers.FunctionCallIn, append([]interface{}{ex1, ex2}, others.([]interface{})...)) return createFunctionCall(parsers.FunctionCallIn, append([]interface{}{ex1, ex2}, others.([]interface{})...))
} }

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Parse_Select(t *testing.T) { func Test_Parse_Select(t *testing.T) {
@@ -17,7 +18,7 @@ func Test_Parse_Select(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -31,7 +32,7 @@ func Test_Parse_Select(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "@param"}}, {Path: []string{"c", "@param"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -44,7 +45,7 @@ func Test_Parse_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Distinct: true, Distinct: true,
}, },
) )
@@ -58,7 +59,7 @@ func Test_Parse_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Count: 1, Count: 1,
}, },
) )
@@ -72,7 +73,7 @@ func Test_Parse_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Count: 5, Count: 5,
Offset: 3, Offset: 3,
}, },
@@ -87,7 +88,7 @@ func Test_Parse_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}, IsTopLevel: true}, {Path: []string{"c", "id"}, IsTopLevel: true},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -100,7 +101,20 @@ func Test_Parse_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c"}, IsTopLevel: true}, {Path: []string{"c"}, IsTopLevel: true},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse SELECT c", func(t *testing.T) {
testQueryParse(
t,
`SELECT c FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c"}, IsTopLevel: false},
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -120,7 +134,27 @@ func Test_Parse_Select(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
},
)
})
t.Run("Should parse SELECT with alias", func(t *testing.T) {
testQueryParse(
t,
`SELECT
c.id AS aliasWithAs,
c.pk aliasWithoutAs
FROM root c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Alias: "aliasWithAs", Path: []string{"c", "id"}},
{Alias: "aliasWithoutAs", Path: []string{"c", "pk"}},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{Alias: "c", Path: []string{"root"}},
},
}, },
) )
}) })
@@ -140,7 +174,7 @@ func Test_Parse_Select(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_StringFunctions(t *testing.T) { func Test_Execute_StringFunctions(t *testing.T) {
@@ -23,25 +24,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -61,19 +50,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
nil, nil,
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -93,13 +76,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{ parsers.SelectItem{
Path: []string{"c", "pk"}, Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
@@ -108,7 +85,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -128,25 +105,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -166,25 +131,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -204,25 +157,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -242,25 +183,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("2"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(1),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "2",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -284,7 +213,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -308,7 +237,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -332,7 +261,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -352,18 +281,12 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(5),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 5,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -387,7 +310,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -411,7 +334,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -431,25 +354,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("old"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_String("new"),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "old",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "new",
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -469,18 +380,12 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(3),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -504,7 +409,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -524,18 +429,12 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(3),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -559,7 +458,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -579,25 +478,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(1),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(5),
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 5,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })
@@ -621,7 +508,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
) )
}) })

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Parse_SubQuery(t *testing.T) { func Test_Parse_SubQuery(t *testing.T) {
@@ -22,7 +23,7 @@ func Test_Parse_SubQuery(t *testing.T) {
Alias: "c", Alias: "c",
Type: parsers.SelectItemTypeSubQuery, Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{ Value: parsers.SelectStmt{
Table: parsers.Table{Value: "cc"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("cc")},
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"cc", "info"}, IsTopLevel: true}, {Path: []string{"cc", "info"}, IsTopLevel: true},
}, },
@@ -42,9 +43,7 @@ func Test_Parse_SubQuery(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"cc", "name"}}, {Path: []string{"cc", "name"}},
}, },
Table: parsers.Table{ Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Value: "c",
},
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
Table: parsers.Table{ Table: parsers.Table{
@@ -55,13 +54,12 @@ func Test_Parse_SubQuery(t *testing.T) {
Type: parsers.SelectItemTypeSubQuery, Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{ Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}}, testutils.SelectItem_Path("tag", "name"),
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "tag", Value: "tag",
SelectItem: parsers.SelectItem{ SelectItem: testutils.SelectItem_Path("c", "tags"),
Path: []string{"c", "tags"}, IsInSelect: true,
},
}, },
}, },
}, },
@@ -82,10 +80,10 @@ func Test_Parse_SubQuery(t *testing.T) {
WHERE hasTags`, WHERE hasTags`,
parsers.SelectStmt{ parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, testutils.SelectItem_Path("c", "id"),
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "c", SelectItem: testutils.SelectItem_Path("c"),
}, },
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
@@ -100,13 +98,12 @@ func Test_Parse_SubQuery(t *testing.T) {
Type: parsers.SelectItemTypeSubQuery, Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{ Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}}, testutils.SelectItem_Path("tag", "name"),
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "tag", Value: "tag",
SelectItem: parsers.SelectItem{ SelectItem: testutils.SelectItem_Path("c", "tags"),
Path: []string{"c", "tags"}, IsInSelect: true,
},
}, },
Exists: true, Exists: true,
}, },

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_TypeCheckingFunctions(t *testing.T) { func Test_Execute_TypeCheckingFunctions(t *testing.T) {
@@ -27,7 +28,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -63,7 +64,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -99,7 +100,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -135,7 +136,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -171,7 +172,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -207,7 +208,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -243,7 +244,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -279,7 +280,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -315,7 +316,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -351,7 +352,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Parse_Were(t *testing.T) { func Test_Parse_Were(t *testing.T) {
@@ -18,14 +19,11 @@ func Test_Parse_Were(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.ComparisonExpression{ Filters: parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}}, Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Bool(true),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
}, },
}, },
) )
@@ -44,25 +42,19 @@ func Test_Parse_Were(t *testing.T) {
{Path: []string{"c", "_rid"}}, {Path: []string{"c", "_rid"}},
{Path: []string{"c", "_ts"}}, {Path: []string{"c", "_ts"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.LogicalExpression{ Filters: parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeOr, Operation: parsers.LogicalExpressionTypeOr,
Expressions: []interface{}{ Expressions: []interface{}{
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("12345"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "12345"},
},
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "pk"}}, Left: parsers.SelectItem{Path: []string{"c", "pk"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Int(123),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 123},
},
}, },
}, },
}, },
@@ -75,22 +67,19 @@ func Test_Parse_Were(t *testing.T) {
t, t,
`select c.id `select c.id
FROM c FROM c
WHERE c.isCool=true AND (c.id = "123" OR c.id = "456")`, WHERE c.isCool=true AND (c.id = "123" OR c.id <= "456")`,
parsers.SelectStmt{ parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.LogicalExpression{ Filters: parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeAnd, Operation: parsers.LogicalExpressionTypeAnd,
Expressions: []interface{}{ Expressions: []interface{}{
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}}, Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Bool(true),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
}, },
parsers.LogicalExpression{ parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeOr, Operation: parsers.LogicalExpressionTypeOr,
@@ -98,18 +87,12 @@ func Test_Parse_Were(t *testing.T) {
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "123"},
},
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "<=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("456"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "456"},
},
}, },
}, },
}, },
@@ -131,47 +114,32 @@ func Test_Parse_Were(t *testing.T) {
AND c.param=@param_id1`, AND c.param=@param_id1`,
parsers.SelectStmt{ parsers.SelectStmt{
SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}}, SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}},
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.LogicalExpression{ Filters: parsers.LogicalExpression{
Expressions: []interface{}{ Expressions: []interface{}{
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "boolean"}}, Left: parsers.SelectItem{Path: []string{"c", "boolean"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Bool(true),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
Operation: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "integer"}}, Left: parsers.SelectItem{Path: []string{"c", "integer"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Int(1),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 1},
},
Operation: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "float"}}, Left: parsers.SelectItem{Path: []string{"c", "float"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Float(6.9),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeFloat, Value: 6.9},
},
Operation: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "string"}}, Left: parsers.SelectItem{Path: []string{"c", "string"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("hello"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "hello"},
},
Operation: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "param"}}, Left: parsers.SelectItem{Path: []string{"c", "param"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Parameter("@param_id1"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: "@param_id1"},
},
Operation: "=", Operation: "=",
}, },
}, },
@@ -180,4 +148,21 @@ func Test_Parse_Were(t *testing.T) {
}, },
) )
}) })
t.Run("Should correctly parse NOT conditions", func(t *testing.T) {
testQueryParse(
t,
`select c.id
FROM c
WHERE NOT c.boolean`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{
Path: []string{"c", "boolean"},
Invert: true,
},
},
)
})
} }

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_AggregateFunctions(t *testing.T) { func Test_Execute_AggregateFunctions(t *testing.T) {
@@ -38,7 +39,7 @@ func Test_Execute_AggregateFunctions(t *testing.T) {
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "key"}}, {Path: []string{"c", "key"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -67,7 +68,7 @@ func Test_Execute_AggregateFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -99,7 +100,7 @@ func Test_Execute_AggregateFunctions(t *testing.T) {
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "key"}}, {Path: []string{"c", "key"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -132,7 +133,7 @@ func Test_Execute_AggregateFunctions(t *testing.T) {
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "key"}}, {Path: []string{"c", "key"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -165,7 +166,7 @@ func Test_Execute_AggregateFunctions(t *testing.T) {
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "key"}}, {Path: []string{"c", "key"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -198,7 +199,7 @@ func Test_Execute_AggregateFunctions(t *testing.T) {
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "key"}}, {Path: []string{"c", "key"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{

View File

@@ -16,6 +16,86 @@ func (r rowContext) array_Concat(arguments []interface{}) []interface{} {
return result return result
} }
func (r rowContext) array_Contains(arguments []interface{}) bool {
array := r.parseArray(arguments[0])
if array == nil {
return false
}
exprToSearch := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
partialSearch := false
if len(arguments) > 2 {
boolExpr := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if boolValue, ok := boolExpr.(bool); ok {
partialSearch = boolValue
} else {
logger.ErrorLn("array_Contains - got parameters of wrong type")
return false
}
}
for _, item := range array {
if partialSearch {
if r.partialMatch(item, exprToSearch) {
return true
}
} else {
if reflect.DeepEqual(item, exprToSearch) {
return true
}
}
}
return false
}
func (r rowContext) array_Contains_Any(arguments []interface{}) bool {
array := r.parseArray(arguments[0])
if array == nil {
return false
}
valueSelectItems := arguments[1:]
for _, valueSelectItem := range valueSelectItems {
value := r.resolveSelectItem(valueSelectItem.(parsers.SelectItem))
for _, item := range array {
if reflect.DeepEqual(item, value) {
return true
}
}
}
return false
}
func (r rowContext) array_Contains_All(arguments []interface{}) bool {
array := r.parseArray(arguments[0])
if array == nil {
return false
}
valueSelectItems := arguments[1:]
for _, valueSelectItem := range valueSelectItems {
value := r.resolveSelectItem(valueSelectItem.(parsers.SelectItem))
found := false
for _, item := range array {
if reflect.DeepEqual(item, value) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (r rowContext) array_Length(arguments []interface{}) int { func (r rowContext) array_Length(arguments []interface{}) int {
array := r.parseArray(arguments[0]) array := r.parseArray(arguments[0])
if array == nil { if array == nil {
@@ -36,13 +116,13 @@ func (r rowContext) array_Slice(arguments []interface{}) []interface{} {
lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem)) lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("array_Slice - got length parameters of wrong type") logger.ErrorLn("array_Slice - got length parameters of wrong type")
return []interface{}{} return []interface{}{}
} }
} }
if start, ok = startEx.(int); !ok { if start, ok = startEx.(int); !ok {
logger.Error("array_Slice - got start parameters of wrong type") logger.ErrorLn("array_Slice - got start parameters of wrong type")
return []interface{}{} return []interface{}{}
} }
@@ -117,7 +197,7 @@ func (r rowContext) parseArray(argument interface{}) []interface{} {
arrValue := reflect.ValueOf(ex) arrValue := reflect.ValueOf(ex)
if arrValue.Kind() != reflect.Slice { if arrValue.Kind() != reflect.Slice {
logger.Error("parseArray got parameters of wrong type") logger.ErrorLn("parseArray got parameters of wrong type")
return nil return nil
} }
@@ -129,3 +209,21 @@ func (r rowContext) parseArray(argument interface{}) []interface{} {
return result return result
} }
func (r rowContext) partialMatch(item interface{}, exprToSearch interface{}) bool {
itemValue := reflect.ValueOf(item)
exprValue := reflect.ValueOf(exprToSearch)
if itemValue.Kind() != reflect.Map || exprValue.Kind() != reflect.Map {
logger.ErrorLn("partialMatch got parameters of wrong type")
return false
}
for _, key := range exprValue.MapKeys() {
if !reflect.DeepEqual(itemValue.MapIndex(key).Interface(), exprValue.MapIndex(key).Interface()) {
return false
}
}
return true
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_ArrayFunctions(t *testing.T) { func Test_Execute_ArrayFunctions(t *testing.T) {
@@ -41,7 +42,7 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -52,6 +53,300 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
) )
}) })
t.Run("Should execute function ARRAY_CONTAINS()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
Parameters: map[string]interface{}{
"@categories": []interface{}{"coats", "jackets", "sweatshirts"},
"@objectArray": []interface{}{map[string]interface{}{"category": "shirts", "color": "blue", "nestedObject": map[string]interface{}{"size": "M"}}},
"@fullMatchObject": map[string]interface{}{"category": "shirts", "color": "blue", "nestedObject": map[string]interface{}{"size": "M"}},
"@partialMatchObject": map[string]interface{}{"category": "shirts"},
"@missingPartialMatchObject": map[string]interface{}{"category": "shorts", "color": "blue"},
"@nestedPartialMatchObject": map[string]interface{}{"nestedObject": map[string]interface{}{"size": "M"}},
},
SelectItems: []parsers.SelectItem{
{
Alias: "ContainsItem",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@categories"),
testutils.SelectItem_Constant_String("coats"),
},
},
},
{
Alias: "MissingItem",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@categories"),
testutils.SelectItem_Constant_String("hoodies"),
},
},
},
{
Alias: "ContainsFullMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@fullMatchObject"),
},
},
},
{
Alias: "MissingFullMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@partialMatchObject"),
},
},
},
{
Alias: "ContainsPartialMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@partialMatchObject"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
{
Alias: "MissingPartialMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@missingPartialMatchObject"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
{
Alias: "ContainsNestedPartialMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@nestedPartialMatchObject"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"ContainsItem": true,
"MissingItem": false,
"ContainsFullMatchObject": true,
"MissingFullMatchObject": false,
"ContainsPartialMatchObject": true,
"MissingPartialMatchObject": false,
"ContainsNestedPartialMatchObject": true,
},
},
)
})
t.Run("Should execute function ARRAY_CONTAINS_ANY()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
Parameters: map[string]interface{}{
"@mixedArray": []interface{}{1, true, "3", []int{1, 2, 3}},
"@numbers": []interface{}{1, 2, 3, 4},
"@emptyArray": []interface{}{},
"@arr123": []interface{}{1, 2, 3},
},
SelectItems: []parsers.SelectItem{
{
Alias: "matchesEntireArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@mixedArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Bool(true),
testutils.SelectItem_Constant_String("3"),
testutils.SelectItem_Constant_Parameter("@arr123"),
},
},
},
{
Alias: "matchesSomeValues",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
testutils.SelectItem_Constant_Int(4),
testutils.SelectItem_Constant_Int(5),
},
},
},
{
Alias: "matchSingleValue",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(1),
},
},
},
{
Alias: "noMatches",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(5),
testutils.SelectItem_Constant_Int(6),
testutils.SelectItem_Constant_Int(7),
testutils.SelectItem_Constant_Int(8),
},
},
},
{
Alias: "emptyArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@emptyArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"matchesEntireArray": true,
"matchesSomeValues": true,
"matchSingleValue": true,
"noMatches": false,
"emptyArray": false,
},
},
)
})
t.Run("Should execute function ARRAY_CONTAINS_ALL()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
Parameters: map[string]interface{}{
"@mixedArray": []interface{}{1, true, "3", []interface{}{1, 2, 3}},
"@numbers": []interface{}{1, 2, 3, 4},
"@emptyArray": []interface{}{},
"@arr123": []interface{}{1, 2, 3},
},
SelectItems: []parsers.SelectItem{
{
Alias: "matchesEntireArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@mixedArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Bool(true),
testutils.SelectItem_Constant_String("3"),
testutils.SelectItem_Constant_Parameter("@arr123"),
},
},
},
{
Alias: "matchesSomeValues",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
testutils.SelectItem_Constant_Int(4),
testutils.SelectItem_Constant_Int(5),
},
},
},
{
Alias: "matchSingleValue",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(1),
},
},
},
{
Alias: "noMatches",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(5),
testutils.SelectItem_Constant_Int(6),
testutils.SelectItem_Constant_Int(7),
testutils.SelectItem_Constant_Int(8),
},
},
},
{
Alias: "emptyArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@emptyArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"matchesEntireArray": true,
"matchesSomeValues": false,
"matchSingleValue": true,
"noMatches": false,
"emptyArray": false,
},
},
)
})
t.Run("Should execute function ARRAY_LENGTH()", func(t *testing.T) { t.Run("Should execute function ARRAY_LENGTH()", func(t *testing.T) {
testQueryExecute( testQueryExecute(
t, t,
@@ -75,7 +370,7 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -105,25 +400,13 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
Path: []string{"c", "arr2"}, Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(1),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(2),
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -161,7 +444,7 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -199,7 +482,7 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_Joins(t *testing.T) { func Test_Execute_Joins(t *testing.T) {
@@ -33,7 +34,7 @@ func Test_Execute_Joins(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"cc", "name"}}, {Path: []string{"cc", "name"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
Table: parsers.Table{ Table: parsers.Table{
@@ -62,7 +63,7 @@ func Test_Execute_Joins(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"cc"}, IsTopLevel: true}, {Path: []string{"cc"}, IsTopLevel: true},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
Table: parsers.Table{ Table: parsers.Table{

View File

@@ -21,7 +21,7 @@ func (r rowContext) math_Abs(arguments []interface{}) interface{} {
} }
return val return val
default: default:
logger.Debug("math_Abs - got parameters of wrong type") logger.DebugLn("math_Abs - got parameters of wrong type")
return 0 return 0
} }
} }
@@ -32,12 +32,12 @@ func (r rowContext) math_Acos(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Acos - got parameters of wrong type") logger.DebugLn("math_Acos - got parameters of wrong type")
return nil return nil
} }
if val < -1 || val > 1 { if val < -1 || val > 1 {
logger.Debug("math_Acos - value out of domain for acos") logger.DebugLn("math_Acos - value out of domain for acos")
return nil return nil
} }
@@ -50,12 +50,12 @@ func (r rowContext) math_Asin(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Asin - got parameters of wrong type") logger.DebugLn("math_Asin - got parameters of wrong type")
return nil return nil
} }
if val < -1 || val > 1 { if val < -1 || val > 1 {
logger.Debug("math_Asin - value out of domain for acos") logger.DebugLn("math_Asin - value out of domain for acos")
return nil return nil
} }
@@ -68,7 +68,7 @@ func (r rowContext) math_Atan(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Atan - got parameters of wrong type") logger.DebugLn("math_Atan - got parameters of wrong type")
return nil return nil
} }
@@ -85,7 +85,7 @@ func (r rowContext) math_Ceiling(arguments []interface{}) interface{} {
case int: case int:
return val return val
default: default:
logger.Debug("math_Ceiling - got parameters of wrong type") logger.DebugLn("math_Ceiling - got parameters of wrong type")
return 0 return 0
} }
} }
@@ -96,7 +96,7 @@ func (r rowContext) math_Cos(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Cos - got parameters of wrong type") logger.DebugLn("math_Cos - got parameters of wrong type")
return nil return nil
} }
@@ -109,12 +109,12 @@ func (r rowContext) math_Cot(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Cot - got parameters of wrong type") logger.DebugLn("math_Cot - got parameters of wrong type")
return nil return nil
} }
if val == 0 { if val == 0 {
logger.Debug("math_Cot - cotangent undefined for zero") logger.DebugLn("math_Cot - cotangent undefined for zero")
return nil return nil
} }
@@ -127,7 +127,7 @@ func (r rowContext) math_Degrees(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Degrees - got parameters of wrong type") logger.DebugLn("math_Degrees - got parameters of wrong type")
return nil return nil
} }
@@ -140,7 +140,7 @@ func (r rowContext) math_Exp(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Exp - got parameters of wrong type") logger.DebugLn("math_Exp - got parameters of wrong type")
return nil return nil
} }
@@ -157,7 +157,7 @@ func (r rowContext) math_Floor(arguments []interface{}) interface{} {
case int: case int:
return val return val
default: default:
logger.Debug("math_Floor - got parameters of wrong type") logger.DebugLn("math_Floor - got parameters of wrong type")
return 0 return 0
} }
} }
@@ -170,7 +170,7 @@ func (r rowContext) math_IntBitNot(arguments []interface{}) interface{} {
case int: case int:
return ^val return ^val
default: default:
logger.Debug("math_IntBitNot - got parameters of wrong type") logger.DebugLn("math_IntBitNot - got parameters of wrong type")
return nil return nil
} }
} }
@@ -181,12 +181,12 @@ func (r rowContext) math_Log10(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Log10 - got parameters of wrong type") logger.DebugLn("math_Log10 - got parameters of wrong type")
return nil return nil
} }
if val <= 0 { if val <= 0 {
logger.Debug("math_Log10 - value must be greater than 0") logger.DebugLn("math_Log10 - value must be greater than 0")
return nil return nil
} }
@@ -199,7 +199,7 @@ func (r rowContext) math_Radians(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Radians - got parameters of wrong type") logger.DebugLn("math_Radians - got parameters of wrong type")
return nil return nil
} }
@@ -216,7 +216,7 @@ func (r rowContext) math_Round(arguments []interface{}) interface{} {
case int: case int:
return val return val
default: default:
logger.Debug("math_Round - got parameters of wrong type") logger.DebugLn("math_Round - got parameters of wrong type")
return nil return nil
} }
} }
@@ -243,7 +243,7 @@ func (r rowContext) math_Sign(arguments []interface{}) interface{} {
return 0 return 0
} }
default: default:
logger.Debug("math_Sign - got parameters of wrong type") logger.DebugLn("math_Sign - got parameters of wrong type")
return nil return nil
} }
} }
@@ -254,7 +254,7 @@ func (r rowContext) math_Sin(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Sin - got parameters of wrong type") logger.DebugLn("math_Sin - got parameters of wrong type")
return nil return nil
} }
@@ -267,7 +267,7 @@ func (r rowContext) math_Sqrt(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Sqrt - got parameters of wrong type") logger.DebugLn("math_Sqrt - got parameters of wrong type")
return nil return nil
} }
@@ -280,7 +280,7 @@ func (r rowContext) math_Square(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Square - got parameters of wrong type") logger.DebugLn("math_Square - got parameters of wrong type")
return nil return nil
} }
@@ -293,7 +293,7 @@ func (r rowContext) math_Tan(arguments []interface{}) interface{} {
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
logger.Debug("math_Tan - got parameters of wrong type") logger.DebugLn("math_Tan - got parameters of wrong type")
return nil return nil
} }
@@ -310,7 +310,7 @@ func (r rowContext) math_Trunc(arguments []interface{}) interface{} {
case int: case int:
return float64(val) return float64(val)
default: default:
logger.Debug("math_Trunc - got parameters of wrong type") logger.DebugLn("math_Trunc - got parameters of wrong type")
return nil return nil
} }
} }
@@ -325,7 +325,7 @@ func (r rowContext) math_Atn2(arguments []interface{}) interface{} {
x, xIsNumber := numToFloat64(ex2) x, xIsNumber := numToFloat64(ex2)
if !yIsNumber || !xIsNumber { if !yIsNumber || !xIsNumber {
logger.Debug("math_Atn2 - got parameters of wrong type") logger.DebugLn("math_Atn2 - got parameters of wrong type")
return nil return nil
} }
@@ -342,7 +342,7 @@ func (r rowContext) math_IntAdd(arguments []interface{}) interface{} {
ex2Number, ex2IsNumber := numToInt(ex2) ex2Number, ex2IsNumber := numToInt(ex2)
if !ex1IsNumber || !ex2IsNumber { if !ex1IsNumber || !ex2IsNumber {
logger.Debug("math_IntAdd - got parameters of wrong type") logger.DebugLn("math_IntAdd - got parameters of wrong type")
return nil return nil
} }
@@ -359,7 +359,7 @@ func (r rowContext) math_IntBitAnd(arguments []interface{}) interface{} {
ex2Int, ex2IsInt := numToInt(ex2) ex2Int, ex2IsInt := numToInt(ex2)
if !ex1IsInt || !ex2IsInt { if !ex1IsInt || !ex2IsInt {
logger.Debug("math_IntBitAnd - got parameters of wrong type") logger.DebugLn("math_IntBitAnd - got parameters of wrong type")
return nil return nil
} }
@@ -376,7 +376,7 @@ func (r rowContext) math_IntBitLeftShift(arguments []interface{}) interface{} {
num2, num2IsInt := numToInt(ex2) num2, num2IsInt := numToInt(ex2)
if !num1IsInt || !num2IsInt { if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitLeftShift - got parameters of wrong type") logger.DebugLn("math_IntBitLeftShift - got parameters of wrong type")
return nil return nil
} }
@@ -393,7 +393,7 @@ func (r rowContext) math_IntBitOr(arguments []interface{}) interface{} {
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt { if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitOr - got parameters of wrong type") logger.DebugLn("math_IntBitOr - got parameters of wrong type")
return nil return nil
} }
@@ -410,7 +410,7 @@ func (r rowContext) math_IntBitRightShift(arguments []interface{}) interface{} {
num2, num2IsInt := numToInt(ex2) num2, num2IsInt := numToInt(ex2)
if !num1IsInt || !num2IsInt { if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitRightShift - got parameters of wrong type") logger.DebugLn("math_IntBitRightShift - got parameters of wrong type")
return nil return nil
} }
@@ -427,7 +427,7 @@ func (r rowContext) math_IntBitXor(arguments []interface{}) interface{} {
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt { if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitXor - got parameters of wrong type") logger.DebugLn("math_IntBitXor - got parameters of wrong type")
return nil return nil
} }
@@ -444,7 +444,7 @@ func (r rowContext) math_IntDiv(arguments []interface{}) interface{} {
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt || num2 == 0 { if !num1IsInt || !num2IsInt || num2 == 0 {
logger.Debug("math_IntDiv - got parameters of wrong type or divide by zero") logger.DebugLn("math_IntDiv - got parameters of wrong type or divide by zero")
return nil return nil
} }
@@ -461,7 +461,7 @@ func (r rowContext) math_IntMul(arguments []interface{}) interface{} {
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt { if !num1IsInt || !num2IsInt {
logger.Debug("math_IntMul - got parameters of wrong type") logger.DebugLn("math_IntMul - got parameters of wrong type")
return nil return nil
} }
@@ -478,7 +478,7 @@ func (r rowContext) math_IntSub(arguments []interface{}) interface{} {
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt { if !num1IsInt || !num2IsInt {
logger.Debug("math_IntSub - got parameters of wrong type") logger.DebugLn("math_IntSub - got parameters of wrong type")
return nil return nil
} }
@@ -495,7 +495,7 @@ func (r rowContext) math_IntMod(arguments []interface{}) interface{} {
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt || num2 == 0 { if !num1IsInt || !num2IsInt || num2 == 0 {
logger.Debug("math_IntMod - got parameters of wrong type or divide by zero") logger.DebugLn("math_IntMod - got parameters of wrong type or divide by zero")
return nil return nil
} }
@@ -512,7 +512,7 @@ func (r rowContext) math_Power(arguments []interface{}) interface{} {
exponent, exponentIsNumber := numToFloat64(ex2) exponent, exponentIsNumber := numToFloat64(ex2)
if !baseIsNumber || !exponentIsNumber { if !baseIsNumber || !exponentIsNumber {
logger.Debug("math_Power - got parameters of wrong type") logger.DebugLn("math_Power - got parameters of wrong type")
return nil return nil
} }
@@ -530,21 +530,21 @@ func (r rowContext) math_Log(arguments []interface{}) interface{} {
baseValue, baseValueIsNumber := numToFloat64(baseValueObject) baseValue, baseValueIsNumber := numToFloat64(baseValueObject)
if !baseValueIsNumber { if !baseValueIsNumber {
logger.Debug("math_Log - base parameter must be a numeric value") logger.DebugLn("math_Log - base parameter must be a numeric value")
return nil return nil
} }
if baseValue > 0 && baseValue != 1 { if baseValue > 0 && baseValue != 1 {
base = baseValue base = baseValue
} else { } else {
logger.Debug("math_Log - base must be greater than 0 and not equal to 1") logger.DebugLn("math_Log - base must be greater than 0 and not equal to 1")
return nil return nil
} }
} }
num, numIsNumber := numToFloat64(ex) num, numIsNumber := numToFloat64(ex)
if !numIsNumber || num <= 0 { if !numIsNumber || num <= 0 {
logger.Debug("math_Log - parameter must be a positive numeric value") logger.DebugLn("math_Log - parameter must be a positive numeric value")
return nil return nil
} }
@@ -563,21 +563,21 @@ func (r rowContext) math_NumberBin(arguments []interface{}) interface{} {
binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject) binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject)
if !binSizeValueIsNumber { if !binSizeValueIsNumber {
logger.Debug("math_NumberBin - base parameter must be a numeric value") logger.DebugLn("math_NumberBin - base parameter must be a numeric value")
return nil return nil
} }
if binSizeValue != 0 { if binSizeValue != 0 {
binSize = binSizeValue binSize = binSizeValue
} else { } else {
logger.Debug("math_NumberBin - base must not be equal to 0") logger.DebugLn("math_NumberBin - base must not be equal to 0")
return nil return nil
} }
} }
num, numIsNumber := numToFloat64(ex) num, numIsNumber := numToFloat64(ex)
if !numIsNumber { if !numIsNumber {
logger.Debug("math_NumberBin - parameter must be a numeric value") logger.DebugLn("math_NumberBin - parameter must be a numeric value")
return nil return nil
} }

View File

@@ -6,6 +6,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_MathFunctions(t *testing.T) { func Test_Execute_MathFunctions(t *testing.T) {
@@ -261,7 +262,7 @@ func testMathFunctionExecute(
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
data, data,
expectedData, expectedData,

View File

@@ -60,6 +60,15 @@ func ExecuteQuery(query parsers.SelectStmt, documents []RowType) []RowType {
projectedDocuments = deduplicate(projectedDocuments) projectedDocuments = deduplicate(projectedDocuments)
} }
// Apply offset
if query.Offset > 0 {
if query.Offset < len(projectedDocuments) {
projectedDocuments = projectedDocuments[query.Offset:]
} else {
projectedDocuments = []RowType{}
}
}
// Apply result limit // Apply result limit
if query.Count > 0 && len(projectedDocuments) > query.Count { if query.Count > 0 && len(projectedDocuments) > query.Count {
projectedDocuments = projectedDocuments[:query.Count] projectedDocuments = projectedDocuments[:query.Count]
@@ -80,10 +89,15 @@ func resolveFrom(query parsers.SelectStmt, doc RowType) []rowContext {
initialTableName = query.Table.Value initialTableName = query.Table.Value
} }
if initialTableName == "" {
initialTableName = resolveDestinationColumnName(query.Table.SelectItem, 0, query.Parameters)
}
initialRow = rowContext{ initialRow = rowContext{
parameters: query.Parameters, parameters: query.Parameters,
tables: map[string]RowType{ tables: map[string]RowType{
initialTableName: doc, initialTableName: doc,
"$root": doc,
}, },
} }
} }
@@ -93,15 +107,33 @@ func resolveFrom(query parsers.SelectStmt, doc RowType) []rowContext {
if destinationTableName == "" { if destinationTableName == "" {
destinationTableName = query.Table.Value destinationTableName = query.Table.Value
} }
if destinationTableName == "" {
selectValue := initialRow.parseArray(query.Table.SelectItem) destinationTableName = resolveDestinationColumnName(query.Table.SelectItem, 0, initialRow.parameters)
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
if query.Table.IsInSelect || query.Table.SelectItem.Type == parsers.SelectItemTypeSubQuery {
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
}
if len(query.Table.SelectItem.Path) > 0 {
sourceTableName := query.Table.SelectItem.Path[0]
sourceTableData := initialRow.tables[sourceTableName]
if sourceTableData == nil {
// When source table is not found, assume it's root document
initialRow.tables[sourceTableName] = initialRow.tables["$root"]
}
}
newRowData := initialRow.resolveSelectItem(query.Table.SelectItem)
initialRow.tables[destinationTableName] = newRowData
return []rowContext{initialRow}
} }
return []rowContext{initialRow} return []rowContext{initialRow}
@@ -160,6 +192,10 @@ func (r rowContext) applyFilters(filters interface{}) bool {
case parsers.SelectItem: case parsers.SelectItem:
resolvedValue := r.resolveSelectItem(typedFilters) resolvedValue := r.resolveSelectItem(typedFilters)
if value, ok := resolvedValue.(bool); ok { if value, ok := resolvedValue.(bool); ok {
if typedFilters.Invert {
return !value
}
return value return value
} }
} }
@@ -172,7 +208,7 @@ func (r rowContext) filters_ComparisonExpression(expression parsers.ComparisonEx
rightExpression, rightExpressionOk := expression.Right.(parsers.SelectItem) rightExpression, rightExpressionOk := expression.Right.(parsers.SelectItem)
if !leftExpressionOk || !rightExpressionOk { if !leftExpressionOk || !rightExpressionOk {
logger.Error("ComparisonExpression has incorrect Left or Right type") logger.ErrorLn("ComparisonExpression has incorrect Left or Right type")
return false return false
} }
@@ -310,18 +346,7 @@ func (r rowContext) applyProjection(selectItems []parsers.SelectItem) RowType {
// Construct a new row based on the selected columns // Construct a new row based on the selected columns
row := make(map[string]interface{}) row := make(map[string]interface{})
for index, selectItem := range selectItems { for index, selectItem := range selectItems {
destinationName := selectItem.Alias destinationName := resolveDestinationColumnName(selectItem, index, r.parameters)
if destinationName == "" {
if len(selectItem.Path) > 0 {
destinationName = selectItem.Path[len(selectItem.Path)-1]
} else {
destinationName = fmt.Sprintf("$%d", index+1)
}
if destinationName[0] == '@' {
destinationName = r.parameters[destinationName].(string)
}
}
row[destinationName] = r.resolveSelectItem(selectItem) row[destinationName] = r.resolveSelectItem(selectItem)
} }
@@ -329,6 +354,23 @@ func (r rowContext) applyProjection(selectItems []parsers.SelectItem) RowType {
return row return row
} }
func resolveDestinationColumnName(selectItem parsers.SelectItem, itemIndex int, queryParameters map[string]interface{}) string {
if selectItem.Alias != "" {
return selectItem.Alias
}
destinationName := fmt.Sprintf("$%d", itemIndex+1)
if len(selectItem.Path) > 0 {
destinationName = selectItem.Path[len(selectItem.Path)-1]
}
if destinationName[0] == '@' {
destinationName = queryParameters[destinationName].(string)
}
return destinationName
}
func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{} { func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{} {
if selectItem.Type == parsers.SelectItemTypeArray { if selectItem.Type == parsers.SelectItemTypeArray {
return r.selectItem_SelectItemTypeArray(selectItem) return r.selectItem_SelectItemTypeArray(selectItem)
@@ -351,7 +393,7 @@ func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{}
return r.selectItem_SelectItemTypeFunctionCall(typedFunctionCall) return r.selectItem_SelectItemTypeFunctionCall(typedFunctionCall)
} }
logger.Error("parsers.SelectItem has incorrect Value type (expected parsers.FunctionCall)") logger.ErrorLn("parsers.SelectItem has incorrect Value type (expected parsers.FunctionCall)")
return nil return nil
} }
@@ -379,7 +421,7 @@ func (r rowContext) selectItem_SelectItemTypeConstant(selectItem parsers.SelectI
var ok bool var ok bool
if typedValue, ok = selectItem.Value.(parsers.Constant); !ok { if typedValue, ok = selectItem.Value.(parsers.Constant); !ok {
// TODO: Handle error // TODO: Handle error
logger.Error("parsers.Constant has incorrect Value type") logger.ErrorLn("parsers.Constant has incorrect Value type")
} }
if typedValue.Type == parsers.ConstantTypeParameterConstant && if typedValue.Type == parsers.ConstantTypeParameterConstant &&
@@ -470,6 +512,12 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
case parsers.FunctionCallArrayConcat: case parsers.FunctionCallArrayConcat:
return r.array_Concat(functionCall.Arguments) return r.array_Concat(functionCall.Arguments)
case parsers.FunctionCallArrayContains:
return r.array_Contains(functionCall.Arguments)
case parsers.FunctionCallArrayContainsAny:
return r.array_Contains_Any(functionCall.Arguments)
case parsers.FunctionCallArrayContainsAll:
return r.array_Contains_All(functionCall.Arguments)
case parsers.FunctionCallArrayLength: case parsers.FunctionCallArrayLength:
return r.array_Length(functionCall.Arguments) return r.array_Length(functionCall.Arguments)
case parsers.FunctionCallArraySlice: case parsers.FunctionCallArraySlice:
@@ -622,6 +670,14 @@ func hasAggregateFunctions(selectItems []parsers.SelectItem) bool {
} }
func compareValues(val1, val2 interface{}) int { func compareValues(val1, val2 interface{}) int {
if val1 == nil && val2 == nil {
return 0
} else if val1 == nil {
return -1
} else if val2 == nil {
return 1
}
if reflect.TypeOf(val1) != reflect.TypeOf(val2) { if reflect.TypeOf(val1) != reflect.TypeOf(val2) {
return 1 return 1
} }

View File

@@ -6,6 +6,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func testQueryExecute( func testQueryExecute(
@@ -49,7 +50,7 @@ func Test_Execute(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
OrderExpressions: []parsers.OrderExpression{ OrderExpressions: []parsers.OrderExpression{
{ {
SelectItem: parsers.SelectItem{Path: []string{"c", "pk"}}, SelectItem: parsers.SelectItem{Path: []string{"c", "pk"}},
@@ -78,7 +79,7 @@ func Test_Execute(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
GroupBy: []parsers.SelectItem{ GroupBy: []parsers.SelectItem{
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
@@ -101,7 +102,7 @@ func Test_Execute(t *testing.T) {
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{ Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{ Value: parsers.FunctionCall{
@@ -111,20 +112,44 @@ func Test_Execute(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "456"},
map[string]interface{}{"id": "123"},
},
)
})
t.Run("Should execute IN function with function call", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
testutils.SelectItem_Path("c", "id"),
},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallIn,
Arguments: []interface{}{
parsers.SelectItem{ parsers.SelectItem{
Type: parsers.SelectItemTypeConstant, Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.Constant{ Value: parsers.FunctionCall{
Type: parsers.ConstantTypeString, Type: parsers.FunctionCallToString,
Value: "123", Arguments: []interface{}{
}, testutils.SelectItem_Path("c", "id"),
}, },
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "456",
}, },
}, },
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
}, },
}, },
}, },
@@ -148,10 +173,9 @@ func Test_Execute(t *testing.T) {
}, },
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "c", Value: "c",
SelectItem: parsers.SelectItem{ SelectItem: testutils.SelectItem_Path("c", "tags"),
Path: []string{"c", "tags"}, IsInSelect: true,
},
}, },
}, },
mockData, mockData,

View File

@@ -5,14 +5,15 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_Select(t *testing.T) { func Test_Execute_Select(t *testing.T) {
mockData := []memoryexecutor.RowType{ mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "12345", "pk": 123, "_self": "self1", "_rid": "rid1", "_ts": 123456, "isCool": false}, map[string]interface{}{"id": "12345", "pk": 123, "_self": "self1", "_rid": "rid1", "_ts": 123456, "isCool": false, "order": nil},
map[string]interface{}{"id": "67890", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true}, map[string]interface{}{"id": "67890", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true, "order": 1},
map[string]interface{}{"id": "456", "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, "order": 2},
map[string]interface{}{"id": "123", "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, "order": 3},
} }
t.Run("Should execute simple SELECT", func(t *testing.T) { t.Run("Should execute simple SELECT", func(t *testing.T) {
@@ -23,7 +24,7 @@ func Test_Execute_Select(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -43,7 +44,7 @@ func Test_Execute_Select(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "@param"}}, {Path: []string{"c", "@param"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Parameters: map[string]interface{}{ Parameters: map[string]interface{}{
"@param": "pk", "@param": "pk",
}, },
@@ -65,7 +66,7 @@ func Test_Execute_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Distinct: true, Distinct: true,
}, },
mockData, mockData,
@@ -84,7 +85,7 @@ func Test_Execute_Select(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Count: 1, Count: 1,
}, },
mockData, mockData,
@@ -102,20 +103,20 @@ func Test_Execute_Select(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "pk"}}, {Path: []string{"c", "pk"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Count: 2, Count: 2,
Offset: 1, Offset: 1,
OrderExpressions: []parsers.OrderExpression{ OrderExpressions: []parsers.OrderExpression{
{ {
SelectItem: parsers.SelectItem{Path: []string{"c", "id"}}, SelectItem: parsers.SelectItem{Path: []string{"c", "order"}},
Direction: parsers.OrderDirectionDesc, Direction: parsers.OrderDirectionDesc,
}, },
}, },
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
map[string]interface{}{"id": "67890", "pk": 456},
map[string]interface{}{"id": "456", "pk": 456}, map[string]interface{}{"id": "456", "pk": 456},
map[string]interface{}{"id": "67890", "pk": 456},
}, },
) )
}) })
@@ -127,7 +128,7 @@ func Test_Execute_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}, IsTopLevel: true}, {Path: []string{"c", "id"}, IsTopLevel: true},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -146,7 +147,7 @@ func Test_Execute_Select(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c"}, IsTopLevel: true}, {Path: []string{"c"}, IsTopLevel: true},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
mockData, mockData,
@@ -167,7 +168,7 @@ func Test_Execute_Select(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -193,7 +194,7 @@ func Test_Execute_Select(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{

View File

@@ -9,10 +9,14 @@ import (
) )
func (r rowContext) strings_StringEquals(arguments []interface{}) bool { func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
str1 := r.parseString(arguments[0]) str1, str1ok := r.parseString(arguments[0])
str2 := r.parseString(arguments[1]) str2, str2ok := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments) ignoreCase := r.getBoolFlag(arguments)
if !str1ok || !str2ok {
return false
}
if ignoreCase { if ignoreCase {
return strings.EqualFold(str1, str2) return strings.EqualFold(str1, str2)
} }
@@ -21,10 +25,14 @@ func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
} }
func (r rowContext) strings_Contains(arguments []interface{}) bool { func (r rowContext) strings_Contains(arguments []interface{}) bool {
str1 := r.parseString(arguments[0]) str1, str1ok := r.parseString(arguments[0])
str2 := r.parseString(arguments[1]) str2, str2ok := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments) ignoreCase := r.getBoolFlag(arguments)
if !str1ok || !str2ok {
return false
}
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
str2 = strings.ToLower(str2) str2 = strings.ToLower(str2)
@@ -34,10 +42,14 @@ func (r rowContext) strings_Contains(arguments []interface{}) bool {
} }
func (r rowContext) strings_EndsWith(arguments []interface{}) bool { func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
str1 := r.parseString(arguments[0]) str1, str1ok := r.parseString(arguments[0])
str2 := r.parseString(arguments[1]) str2, str2ok := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments) ignoreCase := r.getBoolFlag(arguments)
if !str1ok || !str2ok {
return false
}
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
str2 = strings.ToLower(str2) str2 = strings.ToLower(str2)
@@ -47,10 +59,14 @@ func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
} }
func (r rowContext) strings_StartsWith(arguments []interface{}) bool { func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
str1 := r.parseString(arguments[0]) str1, str1ok := r.parseString(arguments[0])
str2 := r.parseString(arguments[1]) str2, str2ok := r.parseString(arguments[1])
ignoreCase := r.getBoolFlag(arguments) ignoreCase := r.getBoolFlag(arguments)
if !str1ok || !str2ok {
return false
}
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
str2 = strings.ToLower(str2) str2 = strings.ToLower(str2)
@@ -73,8 +89,12 @@ func (r rowContext) strings_Concat(arguments []interface{}) string {
} }
func (r rowContext) strings_IndexOf(arguments []interface{}) int { func (r rowContext) strings_IndexOf(arguments []interface{}) int {
str1 := r.parseString(arguments[0]) str1, str1ok := r.parseString(arguments[0])
str2 := r.parseString(arguments[1]) str2, str2ok := r.parseString(arguments[1])
if !str1ok || !str2ok {
return -1
}
start := 0 start := 0
if len(arguments) > 2 && arguments[2] != nil { if len(arguments) > 2 && arguments[2] != nil {
@@ -115,11 +135,15 @@ func (r rowContext) strings_Lower(arguments []interface{}) string {
func (r rowContext) strings_Left(arguments []interface{}) string { func (r rowContext) strings_Left(arguments []interface{}) string {
var ok bool var ok bool
var length int var length int
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem)) lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if !strOk {
return ""
}
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Left - got parameters of wrong type") logger.ErrorLn("strings_Left - got parameters of wrong type")
return "" return ""
} }
@@ -135,30 +159,47 @@ func (r rowContext) strings_Left(arguments []interface{}) string {
} }
func (r rowContext) strings_Length(arguments []interface{}) int { func (r rowContext) strings_Length(arguments []interface{}) int {
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
if !strOk {
return 0
}
return len(str) return len(str)
} }
func (r rowContext) strings_LTrim(arguments []interface{}) string { func (r rowContext) strings_LTrim(arguments []interface{}) string {
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
if !strOk {
return ""
}
return strings.TrimLeft(str, " ") return strings.TrimLeft(str, " ")
} }
func (r rowContext) strings_Replace(arguments []interface{}) string { func (r rowContext) strings_Replace(arguments []interface{}) string {
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
oldStr := r.parseString(arguments[1]) oldStr, oldStrOk := r.parseString(arguments[1])
newStr := r.parseString(arguments[2]) newStr, newStrOk := r.parseString(arguments[2])
if !strOk || !oldStrOk || !newStrOk {
return ""
}
return strings.Replace(str, oldStr, newStr, -1) return strings.Replace(str, oldStr, newStr, -1)
} }
func (r rowContext) strings_Replicate(arguments []interface{}) string { func (r rowContext) strings_Replicate(arguments []interface{}) string {
var ok bool var ok bool
var times int var times int
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem)) timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if !strOk {
return ""
}
if times, ok = timesEx.(int); !ok { if times, ok = timesEx.(int); !ok {
logger.Error("strings_Replicate - got parameters of wrong type") logger.ErrorLn("strings_Replicate - got parameters of wrong type")
return "" return ""
} }
@@ -174,9 +215,13 @@ func (r rowContext) strings_Replicate(arguments []interface{}) string {
} }
func (r rowContext) strings_Reverse(arguments []interface{}) string { func (r rowContext) strings_Reverse(arguments []interface{}) string {
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
runes := []rune(str) runes := []rune(str)
if !strOk {
return ""
}
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i] runes[i], runes[j] = runes[j], runes[i]
} }
@@ -187,11 +232,15 @@ func (r rowContext) strings_Reverse(arguments []interface{}) string {
func (r rowContext) strings_Right(arguments []interface{}) string { func (r rowContext) strings_Right(arguments []interface{}) string {
var ok bool var ok bool
var length int var length int
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem)) lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if !strOk {
return ""
}
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Right - got parameters of wrong type") logger.ErrorLn("strings_Right - got parameters of wrong type")
return "" return ""
} }
@@ -207,7 +256,11 @@ func (r rowContext) strings_Right(arguments []interface{}) string {
} }
func (r rowContext) strings_RTrim(arguments []interface{}) string { func (r rowContext) strings_RTrim(arguments []interface{}) string {
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
if !strOk {
return ""
}
return strings.TrimRight(str, " ") return strings.TrimRight(str, " ")
} }
@@ -215,16 +268,20 @@ func (r rowContext) strings_Substring(arguments []interface{}) string {
var ok bool var ok bool
var startPos int var startPos int
var length int var length int
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
startPosEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem)) startPosEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem)) lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if !strOk {
return ""
}
if startPos, ok = startPosEx.(int); !ok { if startPos, ok = startPosEx.(int); !ok {
logger.Error("strings_Substring - got start parameters of wrong type") logger.ErrorLn("strings_Substring - got start parameters of wrong type")
return "" return ""
} }
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Substring - got length parameters of wrong type") logger.ErrorLn("strings_Substring - got length parameters of wrong type")
return "" return ""
} }
@@ -241,7 +298,11 @@ func (r rowContext) strings_Substring(arguments []interface{}) string {
} }
func (r rowContext) strings_Trim(arguments []interface{}) string { func (r rowContext) strings_Trim(arguments []interface{}) string {
str := r.parseString(arguments[0]) str, strOk := r.parseString(arguments[0])
if !strOk {
return ""
}
return strings.TrimSpace(str) return strings.TrimSpace(str)
} }
@@ -257,15 +318,15 @@ func (r rowContext) getBoolFlag(arguments []interface{}) bool {
return ignoreCase return ignoreCase
} }
func (r rowContext) parseString(argument interface{}) string { func (r rowContext) parseString(argument interface{}) (value string, ok bool) {
exItem := argument.(parsers.SelectItem) exItem := argument.(parsers.SelectItem)
ex := r.resolveSelectItem(exItem) ex := r.resolveSelectItem(exItem)
if str1, ok := ex.(string); ok { if str1, ok := ex.(string); ok {
return str1 return str1, true
} }
logger.Error("StringEquals got parameters of wrong type") logger.ErrorLn("StringEquals got parameters of wrong type")
return "" return "", false
} }
func convertToString(value interface{}) string { func convertToString(value interface{}) string {

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_StringFunctions(t *testing.T) { func Test_Execute_StringFunctions(t *testing.T) {
@@ -33,25 +34,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "pk"}, Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("aaa"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "aaa",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -81,19 +70,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "pk"}, Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("aaa"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "aaa",
},
},
nil, nil,
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -119,20 +102,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String(" "),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(123),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: " ",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: 123,
},
},
parsers.SelectItem{ parsers.SelectItem{
Path: []string{"c", "pk"}, Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
@@ -141,7 +112,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -171,25 +142,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("2"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "2",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -219,25 +178,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("3"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "3",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -267,25 +214,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"}, Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("1"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Bool(true),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "1",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -315,25 +250,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "str"}, Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("o"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(4),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "o",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 4,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -367,7 +290,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -397,18 +320,12 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "str"}, Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(3),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -442,7 +359,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -476,7 +393,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -506,25 +423,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "str"}, Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_String("world"),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_String("universe"),
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "world",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "universe",
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -554,18 +459,12 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "str"}, Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(3),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -599,7 +498,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -629,18 +528,12 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "str"}, Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(3),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -674,7 +567,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -704,25 +597,13 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "str"}, Path: []string{"c", "str"},
Type: parsers.SelectItemTypeField, Type: parsers.SelectItemTypeField,
}, },
parsers.SelectItem{ testutils.SelectItem_Constant_Int(2),
Type: parsers.SelectItemTypeConstant, testutils.SelectItem_Constant_Int(4),
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 4,
},
},
}, },
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -756,7 +637,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_SubQuery(t *testing.T) { func Test_Execute_SubQuery(t *testing.T) {
@@ -41,7 +42,7 @@ func Test_Execute_SubQuery(t *testing.T) {
Alias: "c", Alias: "c",
Type: parsers.SelectItemTypeSubQuery, Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{ Value: parsers.SelectStmt{
Table: parsers.Table{Value: "cc"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("cc")},
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"cc", "info"}, IsTopLevel: true}, {Path: []string{"cc", "info"}, IsTopLevel: true},
}, },
@@ -66,9 +67,7 @@ func Test_Execute_SubQuery(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"cc", "name"}}, {Path: []string{"cc", "name"}},
}, },
Table: parsers.Table{ Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Value: "c",
},
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
Table: parsers.Table{ Table: parsers.Table{
@@ -79,13 +78,12 @@ func Test_Execute_SubQuery(t *testing.T) {
Type: parsers.SelectItemTypeSubQuery, Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{ Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}}, testutils.SelectItem_Path("tag", "name"),
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "tag", Value: "tag",
SelectItem: parsers.SelectItem{ SelectItem: testutils.SelectItem_Path("c", "tags"),
Path: []string{"c", "tags"}, IsInSelect: true,
},
}, },
}, },
}, },
@@ -107,10 +105,10 @@ func Test_Execute_SubQuery(t *testing.T) {
t, t,
parsers.SelectStmt{ parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, testutils.SelectItem_Path("c", "id"),
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "c", SelectItem: testutils.SelectItem_Path("c"),
}, },
JoinItems: []parsers.JoinItem{ JoinItems: []parsers.JoinItem{
{ {
@@ -125,13 +123,12 @@ func Test_Execute_SubQuery(t *testing.T) {
Type: parsers.SelectItemTypeSubQuery, Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{ Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}}, testutils.SelectItem_Path("tag", "name"),
}, },
Table: parsers.Table{ Table: parsers.Table{
Value: "tag", Value: "tag",
SelectItem: parsers.SelectItem{ SelectItem: testutils.SelectItem_Path("c", "tags"),
Path: []string{"c", "tags"}, IsInSelect: true,
},
}, },
Exists: true, Exists: true,
}, },

View File

@@ -6,6 +6,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_TypeCheckingFunctions(t *testing.T) { func Test_Execute_TypeCheckingFunctions(t *testing.T) {
@@ -40,7 +41,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -76,7 +77,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -112,7 +113,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -148,7 +149,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -184,7 +185,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -220,7 +221,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -256,7 +257,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -292,7 +293,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -328,7 +329,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{
@@ -364,7 +365,7 @@ func Test_Execute_TypeCheckingFunctions(t *testing.T) {
}, },
}, },
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
}, },
mockData, mockData,
[]memoryexecutor.RowType{ []memoryexecutor.RowType{

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor" memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
) )
func Test_Execute_Where(t *testing.T) { func Test_Execute_Where(t *testing.T) {
@@ -22,14 +23,11 @@ func Test_Execute_Where(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.ComparisonExpression{ Filters: parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}}, Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Bool(true),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
}, },
}, },
mockData, mockData,
@@ -48,14 +46,11 @@ func Test_Execute_Where(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.ComparisonExpression{ Filters: parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Parameter("@param_id"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: "@param_id"},
},
}, },
Parameters: map[string]interface{}{ Parameters: map[string]interface{}{
"@param_id": "456", "@param_id": "456",
@@ -76,25 +71,19 @@ func Test_Execute_Where(t *testing.T) {
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
{Path: []string{"c", "_self"}, Alias: "self"}, {Path: []string{"c", "_self"}, Alias: "self"},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.LogicalExpression{ Filters: parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeAnd, Operation: parsers.LogicalExpressionTypeAnd,
Expressions: []interface{}{ Expressions: []interface{}{
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("67890"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "67890"},
},
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "pk"}}, Left: parsers.SelectItem{Path: []string{"c", "pk"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Int(456),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 456},
},
}, },
}, },
}, },
@@ -113,17 +102,14 @@ func Test_Execute_Where(t *testing.T) {
SelectItems: []parsers.SelectItem{ SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}}, {Path: []string{"c", "id"}},
}, },
Table: parsers.Table{Value: "c"}, Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.LogicalExpression{ Filters: parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeAnd, Operation: parsers.LogicalExpressionTypeAnd,
Expressions: []interface{}{ Expressions: []interface{}{
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}}, Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_Bool(true),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
}, },
parsers.LogicalExpression{ parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeOr, Operation: parsers.LogicalExpressionTypeOr,
@@ -131,18 +117,12 @@ func Test_Execute_Where(t *testing.T) {
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("123"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "123"},
},
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Operation: "=", Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}}, Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{ Right: testutils.SelectItem_Constant_String("456"),
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "456"},
},
}, },
}, },
}, },
@@ -156,4 +136,22 @@ func Test_Execute_Where(t *testing.T) {
}, },
) )
}) })
t.Run("Should execute parse NOT conditions", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}},
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
Filters: parsers.SelectItem{
Path: []string{"c", "isCool"},
Invert: true,
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "12345"},
},
)
})
} }

View File

@@ -3,6 +3,7 @@ package main
import "C" import "C"
import ( import (
"encoding/json" "encoding/json"
"strings"
repositorymodels "github.com/pikami/cosmium/internal/repository_models" repositorymodels "github.com/pikami/cosmium/internal/repository_models"
) )
@@ -20,7 +21,7 @@ func CreateCollection(serverName *C.char, databaseId *C.char, collectionJson *C.
} }
var collection repositorymodels.Collection var collection repositorymodels.Collection
err := json.Unmarshal([]byte(collectionStr), &collection) err := json.NewDecoder(strings.NewReader(collectionStr)).Decode(&collection)
if err != nil { if err != nil {
return ResponseFailedToParseRequest return ResponseFailedToParseRequest
} }
@@ -47,7 +48,10 @@ func GetCollection(serverName *C.char, databaseId *C.char, collectionId *C.char)
return C.CString("") return C.CString("")
} }
collectionJson, _ := json.Marshal(collection) collectionJson, err := json.Marshal(collection)
if err != nil {
return C.CString("")
}
return C.CString(string(collectionJson)) return C.CString(string(collectionJson))
} }
@@ -67,7 +71,10 @@ func GetAllCollections(serverName *C.char, databaseId *C.char) *C.char {
return C.CString("") return C.CString("")
} }
collectionsJson, _ := json.Marshal(collections) collectionsJson, err := json.Marshal(collections)
if err != nil {
return C.CString("")
}
return C.CString(string(collectionsJson)) return C.CString(string(collectionsJson))
} }

View File

@@ -3,6 +3,7 @@ package main
import "C" import "C"
import ( import (
"encoding/json" "encoding/json"
"strings"
repositorymodels "github.com/pikami/cosmium/internal/repository_models" repositorymodels "github.com/pikami/cosmium/internal/repository_models"
) )
@@ -19,7 +20,7 @@ func CreateDatabase(serverName *C.char, databaseJson *C.char) int {
} }
var database repositorymodels.Database var database repositorymodels.Database
err := json.Unmarshal([]byte(databaseStr), &database) err := json.NewDecoder(strings.NewReader(databaseStr)).Decode(&database)
if err != nil { if err != nil {
return ResponseFailedToParseRequest return ResponseFailedToParseRequest
} }
@@ -45,7 +46,10 @@ func GetDatabase(serverName *C.char, databaseId *C.char) *C.char {
return C.CString("") return C.CString("")
} }
databaseJson, _ := json.Marshal(database) databaseJson, err := json.Marshal(database)
if err != nil {
return C.CString("")
}
return C.CString(string(databaseJson)) return C.CString(string(databaseJson))
} }

View File

@@ -3,6 +3,7 @@ package main
import "C" import "C"
import ( import (
"encoding/json" "encoding/json"
"strings"
repositorymodels "github.com/pikami/cosmium/internal/repository_models" repositorymodels "github.com/pikami/cosmium/internal/repository_models"
) )
@@ -21,7 +22,7 @@ func CreateDocument(serverName *C.char, databaseId *C.char, collectionId *C.char
} }
var document repositorymodels.Document var document repositorymodels.Document
err := json.Unmarshal([]byte(documentStr), &document) err := json.NewDecoder(strings.NewReader(documentStr)).Decode(&document)
if err != nil { if err != nil {
return ResponseFailedToParseRequest return ResponseFailedToParseRequest
} }
@@ -49,7 +50,10 @@ func GetDocument(serverName *C.char, databaseId *C.char, collectionId *C.char, d
return C.CString("") return C.CString("")
} }
documentJson, _ := json.Marshal(document) documentJson, err := json.Marshal(document)
if err != nil {
return C.CString("")
}
return C.CString(string(documentJson)) return C.CString(string(documentJson))
} }
@@ -70,7 +74,10 @@ func GetAllDocuments(serverName *C.char, databaseId *C.char, collectionId *C.cha
return C.CString("") return C.CString("")
} }
documentsJson, _ := json.Marshal(documents) documentsJson, err := json.Marshal(documents)
if err != nil {
return C.CString("")
}
return C.CString(string(documentsJson)) return C.CString(string(documentsJson))
} }

View File

@@ -13,8 +13,10 @@ type ServerInstance struct {
repository *repositories.DataRepository repository *repositories.DataRepository
} }
var serverInstances map[string]*ServerInstance var (
var mutex sync.RWMutex serverInstances = make(map[string]*ServerInstance)
mutex = sync.Mutex{}
)
const ( const (
ResponseSuccess = 0 ResponseSuccess = 0
@@ -25,6 +27,7 @@ const (
ResponseFailedToParseRequest = 103 ResponseFailedToParseRequest = 103
ResponseServerInstanceAlreadyExists = 104 ResponseServerInstanceAlreadyExists = 104
ResponseServerInstanceNotFound = 105 ResponseServerInstanceNotFound = 105
ResponseFailedToStartServer = 106
ResponseRepositoryNotFound = 200 ResponseRepositoryNotFound = 200
ResponseRepositoryConflict = 201 ResponseRepositoryConflict = 201
@@ -32,12 +35,8 @@ const (
) )
func getInstance(serverName string) (*ServerInstance, bool) { func getInstance(serverName string) (*ServerInstance, bool) {
mutex.RLock() mutex.Lock()
defer mutex.RUnlock() defer mutex.Unlock()
if serverInstances == nil {
serverInstances = make(map[string]*ServerInstance)
}
var ok bool var ok bool
var serverInstance *ServerInstance var serverInstance *ServerInstance
@@ -52,10 +51,6 @@ func addInstance(serverName string, serverInstance *ServerInstance) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
if serverInstances == nil {
serverInstances = make(map[string]*ServerInstance)
}
serverInstances[serverName] = serverInstance serverInstances[serverName] = serverInstance
} }
@@ -63,10 +58,6 @@ func removeInstance(serverName string) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
if serverInstances == nil {
return
}
delete(serverInstances, serverName) delete(serverInstances, serverName)
} }

View File

@@ -1,8 +1,13 @@
package main package main
/*
#include <stdlib.h>
*/
import "C" import "C"
import ( import (
"encoding/json" "encoding/json"
"strings"
"unsafe"
"github.com/pikami/cosmium/api" "github.com/pikami/cosmium/api"
"github.com/pikami/cosmium/api/config" "github.com/pikami/cosmium/api/config"
@@ -11,29 +16,32 @@ import (
//export CreateServerInstance //export CreateServerInstance
func CreateServerInstance(serverName *C.char, configurationJSON *C.char) int { func CreateServerInstance(serverName *C.char, configurationJSON *C.char) int {
configStr := C.GoString(configurationJSON)
serverNameStr := C.GoString(serverName) serverNameStr := C.GoString(serverName)
configStr := C.GoString(configurationJSON)
if _, ok := getInstance(serverNameStr); ok { if _, ok := getInstance(serverNameStr); ok {
return ResponseServerInstanceAlreadyExists return ResponseServerInstanceAlreadyExists
} }
var configuration config.ServerConfig var configuration config.ServerConfig
err := json.Unmarshal([]byte(configStr), &configuration) err := json.NewDecoder(strings.NewReader(configStr)).Decode(&configuration)
if err != nil { if err != nil {
return ResponseFailedToParseConfiguration return ResponseFailedToParseConfiguration
} }
configuration.PopulateCalculatedFields()
configuration.ApplyDefaultsToEmptyFields() configuration.ApplyDefaultsToEmptyFields()
configuration.PopulateCalculatedFields()
repository := repositories.NewDataRepository(repositories.RepositoryOptions{ repository := repositories.NewDataRepository(repositories.RepositoryOptions{
InitialDataFilePath: configuration.InitialDataFilePath, InitialDataFilePath: configuration.InitialDataFilePath,
PersistDataFilePath: configuration.PersistDataFilePath, PersistDataFilePath: configuration.PersistDataFilePath,
}) })
server := api.NewApiServer(repository, configuration) server := api.NewApiServer(repository, &configuration)
server.Start() err = server.Start()
if err != nil {
return ResponseFailedToStartServer
}
addInstance(serverNameStr, &ServerInstance{ addInstance(serverNameStr, &ServerInstance{
server: server, server: server,
@@ -87,4 +95,9 @@ func LoadServerInstanceState(serverName *C.char, stateJSON *C.char) int {
return ResponseServerInstanceNotFound return ResponseServerInstanceNotFound
} }
//export FreeMemory
func FreeMemory(ptr *C.char) {
C.free(unsafe.Pointer(ptr))
}
func main() {} func main() {}

View File

@@ -42,7 +42,7 @@ int test_ServerInstanceStateMethods()
return 0; return 0;
} }
const char *expected_state = "{\"databases\":{\"test-db\":{\"id\":\"test-db\",\"_ts\":0,\"_rid\":\"\",\"_etag\":\"\",\"_self\":\"\"}},\"collections\":{\"test-db\":{}},\"documents\":{\"test-db\":{}}}"; const char *expected_state = "{\"databases\":{\"test-db\":{\"id\":\"test-db\",\"_ts\":0,\"_rid\":\"\",\"_etag\":\"\",\"_self\":\"\"}},\"collections\":{\"test-db\":{}},\"documents\":{\"test-db\":{}},\"triggers\":{\"test-db\":{}},\"sprocs\":{\"test-db\":{}},\"udfs\":{\"test-db\":{}}}";
char *compact_state = compact_json(state); char *compact_state = compact_json(state);
if (!compact_state) if (!compact_state)
{ {

View File

@@ -0,0 +1,59 @@
package testutils
import "github.com/pikami/cosmium/parsers"
func SelectItem_Constant_String(value string) parsers.SelectItem {
return parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: value,
},
}
}
func SelectItem_Constant_Int(value int) parsers.SelectItem {
return parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: value,
},
}
}
func SelectItem_Constant_Float(value float64) parsers.SelectItem {
return parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeFloat,
Value: value,
},
}
}
func SelectItem_Constant_Bool(value bool) parsers.SelectItem {
return parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: value,
},
}
}
func SelectItem_Constant_Parameter(name string) parsers.SelectItem {
return parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeParameterConstant,
Value: name,
},
}
}
func SelectItem_Path(path ...string) parsers.SelectItem {
return parsers.SelectItem{
Path: path,
}
}