9 Commits

Author SHA1 Message Date
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
Pijus Kamandulis
f5b8453995 Support patch operations 'set' and 'incr' #7 2024-12-25 23:32:50 +02:00
Pijus Kamandulis
928ca29fe4 Support parameter in bracket #8 2024-12-25 21:28:42 +02:00
Pijus Kamandulis
39cd9e2357 Update dependancies 2024-12-20 20:27:42 +02:00
Pijus Kamandulis
bcf4b513b6 Expose repository functions to sharedlibs 2024-12-20 20:25:32 +02:00
53 changed files with 3770 additions and 1884 deletions

View File

@@ -78,7 +78,7 @@ To disable SSL and run Cosmium on HTTP instead, you can use the `-DisableTls` fl
- **-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`)
- **-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.
@@ -90,7 +90,7 @@ All mentioned arguments can also be set using environment variables:
- **COSMIUM_INITIALDATA** for `-InitialData`
- **COSMIUM_PERSIST** for `-Persist`
- **COSMIUM_PORT** for `-Port`
- **COSMIUM_DEBUG** for `-Debug`
- **COSMIUM_LOGLEVEL** for `-LogLevel`
# License

View File

@@ -26,7 +26,8 @@ func ParseFlags() ServerConfig {
disableAuthentication := flag.Bool("DisableAuth", false, "Disable authentication")
disableTls := flag.Bool("DisableTls", false, "Disable TLS, serve over HTTP")
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()
setFlagsFromEnvironment()
@@ -41,8 +42,8 @@ func ParseFlags() ServerConfig {
config.PersistDataFilePath = *persistDataPath
config.DisableAuth = *disableAuthentication
config.DisableTls = *disableTls
config.Debug = *debug
config.AccountKey = *accountKey
config.LogLevel = logLevel.value
config.PopulateCalculatedFields()
@@ -54,7 +55,19 @@ func (c *ServerConfig) PopulateCalculatedFields() {
c.DatabaseDomain = c.Host
c.DatabaseEndpoint = fmt.Sprintf("https://%s:%d/", c.Host, c.Port)
c.ExplorerBaseUrlLocation = ExplorerBaseUrlLocation
logger.EnableDebugOutput = c.Debug
switch c.LogLevel {
case "debug":
logger.LogLevel = logger.LogLevelDebug
case "info":
logger.LogLevel = logger.LogLevelInfo
case "error":
logger.LogLevel = logger.LogLevelError
case "silent":
logger.LogLevel = logger.LogLevelSilent
default:
logger.LogLevel = logger.LogLevelInfo
}
}
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"`
DisableAuth bool `json:"disableAuth"`
DisableTls bool `json:"disableTls"`
Debug bool `json:"debug"`
LogLevel string `json:"logLevel"`
ExplorerBaseUrlLocation string `json:"explorerBaseUrlLocation"`
}

View File

@@ -6,11 +6,11 @@ import (
"net/http"
"strconv"
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/gin-gonic/gin"
"github.com/pikami/cosmium/internal/constants"
"github.com/pikami/cosmium/internal/logger"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
jsonpatch "github.com/pikami/json-patch/v5"
)
func (h *Handlers) GetAllDocuments(c *gin.Context) {
@@ -135,7 +135,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
currentDocumentBytes, err := json.Marshal(document)
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"})
return
}
@@ -149,7 +149,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
var modifiedDocument map[string]interface{}
err = json.Unmarshal(modifiedDocumentBytes, &modifiedDocument)
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"})
return
}

View File

@@ -75,7 +75,8 @@ func requestToResourceId(c *gin.Context) string {
isFeed := c.Request.Header.Get("A-Im") == "Incremental Feed"
if resourceType == "pkranges" && isFeed {
resourceId = collId
// CosmosSDK replaces '/' with '-' in resource id requests
resourceId = strings.Replace(collId, "-", "/", -1)
}
return resourceId

View File

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

View File

@@ -22,3 +22,97 @@ func (h *Handlers) GetAllStoredProcedures(c *gin.Context) {
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"})
}
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"})
}
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

@@ -16,7 +16,10 @@ import (
func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) {
routeHandlers := handlers.NewHandlers(repository, s.config)
if !s.config.Debug {
gin.DefaultWriter = logger.InfoWriter()
gin.DefaultErrorWriter = logger.ErrorWriter()
if s.config.LogLevel != "debug" {
gin.SetMode(gin.ReleaseMode)
}
@@ -24,7 +27,7 @@ func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) {
e.RedirectTrailingSlash = false
})
if s.config.Debug {
if s.config.LogLevel == "debug" {
router.Use(middleware.RequestLogger())
}
@@ -50,9 +53,23 @@ func (s *ApiServer) CreateRouter(repository *repositories.DataRepository) {
router.GET("/dbs/:databaseId", routeHandlers.GetDatabase)
router.DELETE("/dbs/:databaseId", routeHandlers.DeleteDatabase)
router.GET("/dbs/:databaseId/colls/:collId/udfs", routeHandlers.GetAllUserDefinedFunctions)
router.GET("/dbs/:databaseId/colls/:collId/sprocs", routeHandlers.GetAllStoredProcedures)
router.POST("/dbs/:databaseId/colls/:collId/triggers", routeHandlers.CreateTrigger)
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("/", routeHandlers.GetServerInfo)
@@ -75,10 +92,10 @@ func (s *ApiServer) Start() {
go func() {
<-s.stopServer
logger.Info("Shutting down server...")
logger.InfoLn("Shutting down server...")
err := server.Shutdown(context.TODO())
if err != nil {
logger.Error("Failed to shutdown server:", err)
logger.ErrorLn("Failed to shutdown server:", err)
}
}()
@@ -87,7 +104,7 @@ func (s *ApiServer) Start() {
logger.Infof("Listening and serving HTTP on %s\n", server.Addr)
err := server.ListenAndServe()
if err != nil {
logger.Error("Failed to start HTTP server:", err)
logger.ErrorLn("Failed to start HTTP server:", err)
}
s.isActive = false
} else if s.config.TLS_CertificatePath != "" && s.config.TLS_CertificateKey != "" {
@@ -96,7 +113,7 @@ func (s *ApiServer) Start() {
s.config.TLS_CertificatePath,
s.config.TLS_CertificateKey)
if err != nil {
logger.Error("Failed to start HTTPS server:", err)
logger.ErrorLn("Failed to start HTTPS server:", err)
}
s.isActive = false
} else {
@@ -106,7 +123,7 @@ func (s *ApiServer) Start() {
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
err := server.ListenAndServeTLS("", "")
if err != nil {
logger.Error("Failed to start HTTPS server:", err)
logger.ErrorLn("Failed to start HTTPS server:", err)
}
s.isActive = false
}

View File

@@ -147,6 +147,21 @@ func Test_Documents(t *testing.T) {
)
})
t.Run("Should query document with query parameters as accessor", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`select c.id
FROM c
WHERE c[@param]="67890"
ORDER BY c.id`,
[]azcosmos.QueryParameter{
{Name: "@param", Value: "id"},
},
[]interface{}{
map[string]interface{}{"id": "67890"},
},
)
})
t.Run("Should query array accessor", func(t *testing.T) {
testCosmosQuery(t, collectionClient,
`SELECT c.id,
@@ -220,11 +235,14 @@ func Test_Documents_Patch(t *testing.T) {
t.Run("Should PATCH document", func(t *testing.T) {
context := context.TODO()
expectedData := map[string]interface{}{"id": "67890", "pk": "456", "newField": "newValue"}
expectedData := map[string]interface{}{"id": "67890", "pk": "666", "newField": "newValue", "incr": 15., "setted": "isSet"}
patch := azcosmos.PatchOperations{}
patch.AppendAdd("/newField", "newValue")
patch.AppendIncrement("/incr", 15)
patch.AppendRemove("/isCool")
patch.AppendReplace("/pk", "666")
patch.AppendSet("/setted", "isSet")
itemResponse, err := collectionClient.PatchItem(
context,
@@ -237,13 +255,15 @@ func Test_Documents_Patch(t *testing.T) {
)
assert.Nil(t, err)
var itemResponseBody map[string]string
var itemResponseBody map[string]interface{}
json.Unmarshal(itemResponse.Value, &itemResponseBody)
assert.Equal(t, expectedData["id"], itemResponseBody["id"])
assert.Equal(t, expectedData["pk"], itemResponseBody["pk"])
assert.Empty(t, itemResponseBody["isCool"])
assert.Equal(t, expectedData["newField"], itemResponseBody["newField"])
assert.Equal(t, expectedData["incr"], itemResponseBody["incr"])
assert.Equal(t, expectedData["setted"], itemResponseBody["setted"])
})
t.Run("Should not allow to PATCH document ID", func(t *testing.T) {

View File

@@ -65,9 +65,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| Function | Implemented |
| ------------------ | ----------- |
| ARRAY_CONCAT | Yes |
| ARRAY_CONTAINS | No |
| ARRAY_CONTAINS_ANY | No |
| ARRAY_CONTAINS_ALL | No |
| ARRAY_CONTAINS | Yes |
| ARRAY_CONTAINS_ANY | Yes |
| ARRAY_CONTAINS_ALL | Yes |
| ARRAY_LENGTH | Yes |
| ARRAY_SLICE | Yes |
| CHOOSE | No |

4
go.mod
View File

@@ -5,9 +5,9 @@ go 1.22.0
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.6
github.com/evanphx/json-patch/v5 v5.9.0
github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0
github.com/pikami/json-patch/v5 v5.9.2
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
)
@@ -39,7 +39,7 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.36.0 // indirect

8
go.sum
View File

@@ -22,8 +22,6 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -66,6 +64,8 @@ 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/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/pikami/json-patch/v5 v5.9.2 h1:ciTlocWccYVE3DEa45dsMm02c/tOvcaBY7PpEUNZhrU=
github.com/pikami/json-patch/v5 v5.9.2/go.mod h1:eJIScZ4xgf2aBHLi2UMzYtjlWESUBDOBf7EAx3JW0nI=
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=
@@ -92,8 +92,8 @@ golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
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.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

@@ -5,36 +5,100 @@ import (
"os"
)
var EnableDebugOutput = false
type LogLevelType int
var (
LogLevelDebug LogLevelType = 0
LogLevelInfo LogLevelType = 1
LogLevelError LogLevelType = 2
LogLevelSilent LogLevelType = 10
)
type LogWriter struct {
WriterLevel LogLevelType
}
var LogLevel = LogLevelInfo
var DebugLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
var InfoLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
var ErrorLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
func Debug(v ...any) {
if EnableDebugOutput {
func DebugLn(v ...any) {
if LogLevel <= LogLevelDebug {
DebugLogger.Println(v...)
}
}
func Debug(v ...any) {
if LogLevel <= LogLevelDebug {
DebugLogger.Print(v...)
}
}
func Debugf(format string, v ...any) {
if EnableDebugOutput {
if LogLevel <= LogLevelDebug {
DebugLogger.Printf(format, v...)
}
}
func InfoLn(v ...any) {
if LogLevel <= LogLevelInfo {
InfoLogger.Println(v...)
}
}
func Info(v ...any) {
InfoLogger.Println(v...)
if LogLevel <= LogLevelInfo {
InfoLogger.Print(v...)
}
}
func Infof(format string, v ...any) {
InfoLogger.Printf(format, v...)
if LogLevel <= LogLevelInfo {
InfoLogger.Printf(format, v...)
}
}
func ErrorLn(v ...any) {
if LogLevel <= LogLevelError {
ErrorLogger.Println(v...)
}
}
func Error(v ...any) {
ErrorLogger.Println(v...)
if LogLevel <= LogLevelError {
ErrorLogger.Print(v...)
}
}
func Errorf(format string, v ...any) {
ErrorLogger.Printf(format, v...)
if LogLevel <= LogLevelError {
ErrorLogger.Printf(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}
}

View File

@@ -77,6 +77,9 @@ func (r *DataRepository) CreateCollection(databaseId string, newCollection repos
r.storeState.Collections[databaseId][newCollection.ID] = newCollection
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
}

View File

@@ -57,6 +57,9 @@ func (r *DataRepository) CreateDatabase(newDatabase repositorymodels.Database) (
r.storeState.Databases[newDatabase.ID] = newDatabase
r.storeState.Collections[newDatabase.ID] = make(map[string]repositorymodels.Collection)
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
}

View File

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

View File

@@ -23,7 +23,7 @@ func (r *DataRepository) InitializeRepository() {
}
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)
}
@@ -60,10 +60,13 @@ func (r *DataRepository) LoadStateJSON(jsonData string) error {
r.ensureStoreStateNoNullReferences()
logger.Info("Loaded state:")
logger.InfoLn("Loaded state:")
logger.Infof("Databases: %d\n", getLength(r.storeState.Databases))
logger.Infof("Collections: %d\n", getLength(r.storeState.Collections))
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
}
@@ -80,10 +83,13 @@ func (r *DataRepository) SaveStateFS(filePath string) {
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("Collections: %d\n", getLength(r.storeState.Collections))
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) {
@@ -103,7 +109,10 @@ func getLength(v interface{}) int {
switch v.(type) {
case repositorymodels.Database,
repositorymodels.Collection,
repositorymodels.Document:
repositorymodels.Document,
repositorymodels.Trigger,
repositorymodels.StoredProcedure,
repositorymodels.UserDefinedFunction:
return 1
}
@@ -137,6 +146,18 @@ func (r *DataRepository) ensureStoreStateNoNullReferences() {
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 {
if r.storeState.Collections[database] == nil {
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)
}
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] {
if r.storeState.Documents[database][collection] == nil {
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)
}
}
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
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) {
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(database.ResourceID, collection.ResourceID, resourceid.New())
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
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) {
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(database.ResourceID, collection.ResourceID, resourceid.New())
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
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) {
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(database.ResourceID, collection.ResourceID, resourceid.New())
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
)
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 {
ID string `json:"id"`
IndexingPolicy CollectionIndexingPolicy `json:"indexingPolicy"`
@@ -60,29 +76,29 @@ type UserDefinedFunction struct {
Body string `json:"body"`
ID string `json:"id"`
ResourceID string `json:"_rid"`
TimeStamp int `json:"_ts"`
TimeStamp int64 `json:"_ts"`
Self string `json:"_self"`
Etag string `json:"_etag"`
ETag string `json:"_etag"`
}
type StoredProcedure struct {
Body string `json:"body"`
ID string `json:"id"`
ResourceID string `json:"_rid"`
TimeStamp int `json:"_ts"`
TimeStamp int64 `json:"_ts"`
Self string `json:"_self"`
Etag string `json:"_etag"`
ETag string `json:"_etag"`
}
type Trigger struct {
Body string `json:"body"`
ID string `json:"id"`
TriggerOperation string `json:"triggerOperation"`
TriggerType string `json:"triggerType"`
ResourceID string `json:"_rid"`
TimeStamp int `json:"_ts"`
Self string `json:"_self"`
Etag string `json:"_etag"`
Body string `json:"body"`
ID string `json:"id"`
TriggerOperation TriggerOperation `json:"triggerOperation"`
TriggerType TriggerType `json:"triggerType"`
ResourceID string `json:"_rid"`
TimeStamp int64 `json:"_ts"`
Self string `json:"_self"`
ETag string `json:"_etag"`
}
type Document map[string]interface{}
@@ -113,4 +129,13 @@ type State struct {
// Map databaseId -> collectionId -> documentId -> 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

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

View File

@@ -123,11 +123,14 @@ const (
FunctionCallIsPrimitive FunctionCallType = "IsPrimitive"
FunctionCallIsString FunctionCallType = "IsString"
FunctionCallArrayConcat FunctionCallType = "ArrayConcat"
FunctionCallArrayLength FunctionCallType = "ArrayLength"
FunctionCallArraySlice FunctionCallType = "ArraySlice"
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
FunctionCallSetUnion FunctionCallType = "SetUnion"
FunctionCallArrayConcat FunctionCallType = "ArrayConcat"
FunctionCallArrayContains FunctionCallType = "ArrayContains"
FunctionCallArrayContainsAny FunctionCallType = "ArrayContainsAny"
FunctionCallArrayContainsAll FunctionCallType = "ArrayContainsAll"
FunctionCallArrayLength FunctionCallType = "ArrayLength"
FunctionCallArraySlice FunctionCallType = "ArraySlice"
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
FunctionCallSetUnion FunctionCallType = "SetUnion"
FunctionCallMathAbs FunctionCallType = "MathAbs"
FunctionCallMathAcos FunctionCallType = "MathAcos"

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
)
func Test_Parse_ArrayFunctions(t *testing.T) {
@@ -36,6 +37,119 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
)
})
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{Value: "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{Value: "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{Value: "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{Value: "c"},
},
)
})
t.Run("Should parse function ARRAY_LENGTH()", func(t *testing.T) {
testQueryParse(
t,
@@ -75,20 +189,8 @@ func Test_Parse_ArrayFunctions(t *testing.T) {
Path: []string{"c", "array"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 0,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
testutils.SelectItem_Constant_Int(0),
testutils.SelectItem_Constant_Int(2),
},
},
},

View File

@@ -7,6 +7,7 @@ import (
"github.com/pikami/cosmium/parsers"
"github.com/pikami/cosmium/parsers/nosql"
testutils "github.com/pikami/cosmium/test_utils"
)
// For Parser Debugging
@@ -102,20 +103,8 @@ func Test_Parse(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "456",
},
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
},
},
},

File diff suppressed because it is too large Load Diff

View File

@@ -325,6 +325,7 @@ DotFieldAccess <- "." id:Identifier {
ArrayFieldAccess <- "[\"" id:Identifier "\"]" { return id, nil }
/ "[" id:Integer "]" { return strconv.Itoa(id.(int)), nil }
/ "[" id:ParameterConstant "]" { return id.(parsers.Constant).Value.(string), nil }
Identifier <- [a-zA-Z_][a-zA-Z0-9_]* {
return string(c.text), nil
@@ -456,6 +457,9 @@ AggregateFunctions <- AvgAggregateExpression
/ SumAggregateExpression
ArrayFunctions <- ArrayConcatExpression
/ ArrayContainsExpression
/ ArrayContainsAnyExpression
/ ArrayContainsAllExpression
/ ArrayLengthExpression
/ ArraySliceExpression
/ SetIntersectExpression
@@ -625,6 +629,18 @@ ArrayConcatExpression <- "ARRAY_CONCAT"i ws "(" ws arrays:SelectItem others:(ws
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 ")" {
return createFunctionCall(parsers.FunctionCallArrayLength, []interface{}{array})
}

View File

@@ -22,6 +22,20 @@ func Test_Parse_Select(t *testing.T) {
)
})
t.Run("Should parse SELECT with query parameters as accessor", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id, c[@param] FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{Path: []string{"c", "@param"}},
},
Table: parsers.Table{Value: "c"},
},
)
})
t.Run("Should parse SELECT DISTINCT", func(t *testing.T) {
testQueryParse(
t,

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
)
func Test_Execute_StringFunctions(t *testing.T) {
@@ -23,20 +24,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
@@ -61,13 +50,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
testutils.SelectItem_Constant_String("123"),
nil,
},
},
@@ -93,13 +76,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
testutils.SelectItem_Constant_String("123"),
parsers.SelectItem{
Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField,
@@ -128,20 +105,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
@@ -166,20 +131,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
@@ -204,20 +157,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
@@ -242,20 +183,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "2",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
testutils.SelectItem_Constant_String("2"),
testutils.SelectItem_Constant_Int(1),
},
},
},
@@ -352,13 +281,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 5,
},
},
testutils.SelectItem_Constant_Int(5),
},
},
},
@@ -431,20 +354,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "old",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "new",
},
},
testutils.SelectItem_Constant_String("old"),
testutils.SelectItem_Constant_String("new"),
},
},
},
@@ -469,13 +380,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
testutils.SelectItem_Constant_Int(3),
},
},
},
@@ -524,13 +429,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 3,
},
},
testutils.SelectItem_Constant_Int(3),
},
},
},
@@ -579,20 +478,8 @@ func Test_Execute_StringFunctions(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 5,
},
},
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(5),
},
},
},

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/pikami/cosmium/parsers"
testutils "github.com/pikami/cosmium/test_utils"
)
func Test_Parse_Were(t *testing.T) {
@@ -22,10 +23,7 @@ func Test_Parse_Were(t *testing.T) {
Filters: parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
Right: testutils.SelectItem_Constant_Bool(true),
},
},
)
@@ -51,18 +49,12 @@ func Test_Parse_Were(t *testing.T) {
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "12345"},
},
Right: testutils.SelectItem_Constant_String("12345"),
},
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "pk"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 123},
},
Right: testutils.SelectItem_Constant_Int(123),
},
},
},
@@ -87,10 +79,7 @@ func Test_Parse_Were(t *testing.T) {
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
Right: testutils.SelectItem_Constant_Bool(true),
},
parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeOr,
@@ -98,18 +87,12 @@ func Test_Parse_Were(t *testing.T) {
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "123"},
},
Right: testutils.SelectItem_Constant_String("123"),
},
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "456"},
},
Right: testutils.SelectItem_Constant_String("456"),
},
},
},
@@ -135,43 +118,28 @@ func Test_Parse_Were(t *testing.T) {
Filters: parsers.LogicalExpression{
Expressions: []interface{}{
parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "boolean"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
Left: parsers.SelectItem{Path: []string{"c", "boolean"}},
Right: testutils.SelectItem_Constant_Bool(true),
Operation: "=",
},
parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "integer"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 1},
},
Left: parsers.SelectItem{Path: []string{"c", "integer"}},
Right: testutils.SelectItem_Constant_Int(1),
Operation: "=",
},
parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "float"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeFloat, Value: 6.9},
},
Left: parsers.SelectItem{Path: []string{"c", "float"}},
Right: testutils.SelectItem_Constant_Float(6.9),
Operation: "=",
},
parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "string"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "hello"},
},
Left: parsers.SelectItem{Path: []string{"c", "string"}},
Right: testutils.SelectItem_Constant_String("hello"),
Operation: "=",
},
parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "param"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: "@param_id1"},
},
Left: parsers.SelectItem{Path: []string{"c", "param"}},
Right: testutils.SelectItem_Constant_Parameter("@param_id1"),
Operation: "=",
},
},

View File

@@ -16,6 +16,86 @@ func (r rowContext) array_Concat(arguments []interface{}) []interface{} {
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 {
array := r.parseArray(arguments[0])
if array == nil {
@@ -36,13 +116,13 @@ func (r rowContext) array_Slice(arguments []interface{}) []interface{} {
lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
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{}{}
}
}
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{}{}
}
@@ -117,7 +197,7 @@ func (r rowContext) parseArray(argument interface{}) []interface{} {
arrValue := reflect.ValueOf(ex)
if arrValue.Kind() != reflect.Slice {
logger.Error("parseArray got parameters of wrong type")
logger.ErrorLn("parseArray got parameters of wrong type")
return nil
}
@@ -129,3 +209,21 @@ func (r rowContext) parseArray(argument interface{}) []interface{} {
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 itemValue.MapIndex(key).Interface() != exprValue.MapIndex(key).Interface() {
return false
}
}
return true
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
)
func Test_Execute_ArrayFunctions(t *testing.T) {
@@ -52,6 +53,286 @@ 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"}},
"@fullMatchObject": map[string]interface{}{"category": "shirts", "color": "blue"},
"@partialMatchObject": map[string]interface{}{"category": "shirts"},
"@missingPartialMatchObject": map[string]interface{}{"category": "shorts", "color": "blue"},
},
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),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"ContainsItem": true,
"MissingItem": false,
"ContainsFullMatchObject": true,
"MissingFullMatchObject": false,
"ContainsPartialMatchObject": true,
"MissingPartialMatchObject": false,
},
},
)
})
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) {
testQueryExecute(
t,
@@ -105,20 +386,8 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(2),
},
},
},

View File

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

View File

@@ -172,7 +172,7 @@ func (r rowContext) filters_ComparisonExpression(expression parsers.ComparisonEx
rightExpression, rightExpressionOk := expression.Right.(parsers.SelectItem)
if !leftExpressionOk || !rightExpressionOk {
logger.Error("ComparisonExpression has incorrect Left or Right type")
logger.ErrorLn("ComparisonExpression has incorrect Left or Right type")
return false
}
@@ -317,6 +317,10 @@ func (r rowContext) applyProjection(selectItems []parsers.SelectItem) RowType {
} else {
destinationName = fmt.Sprintf("$%d", index+1)
}
if destinationName[0] == '@' {
destinationName = r.parameters[destinationName].(string)
}
}
row[destinationName] = r.resolveSelectItem(selectItem)
@@ -347,7 +351,7 @@ func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{}
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
}
@@ -375,7 +379,7 @@ func (r rowContext) selectItem_SelectItemTypeConstant(selectItem parsers.SelectI
var ok bool
if typedValue, ok = selectItem.Value.(parsers.Constant); !ok {
// TODO: Handle error
logger.Error("parsers.Constant has incorrect Value type")
logger.ErrorLn("parsers.Constant has incorrect Value type")
}
if typedValue.Type == parsers.ConstantTypeParameterConstant &&
@@ -466,6 +470,12 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
case parsers.FunctionCallArrayConcat:
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:
return r.array_Length(functionCall.Arguments)
case parsers.FunctionCallArraySlice:
@@ -572,6 +582,9 @@ func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem
if len(selectItem.Path) > 1 {
for _, pathSegment := range selectItem.Path[1:] {
if pathSegment[0] == '@' {
pathSegment = r.parameters[pathSegment].(string)
}
switch nestedValue := value.(type) {
case map[string]interface{}:

View File

@@ -6,6 +6,7 @@ import (
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
)
func testQueryExecute(
@@ -111,20 +112,8 @@ func Test_Execute(t *testing.T) {
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "456",
},
},
testutils.SelectItem_Constant_String("123"),
testutils.SelectItem_Constant_String("456"),
},
},
},

View File

@@ -35,6 +35,29 @@ func Test_Execute_Select(t *testing.T) {
)
})
t.Run("Should execute SELECT with query parameters as accessor", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{Path: []string{"c", "@param"}},
},
Table: parsers.Table{Value: "c"},
Parameters: map[string]interface{}{
"@param": "pk",
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "12345", "pk": 123},
map[string]interface{}{"id": "67890", "pk": 456},
map[string]interface{}{"id": "456", "pk": 456},
map[string]interface{}{"id": "123", "pk": 456},
},
)
})
t.Run("Should execute SELECT DISTINCT", func(t *testing.T) {
testQueryExecute(
t,

View File

@@ -119,7 +119,7 @@ func (r rowContext) strings_Left(arguments []interface{}) string {
lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
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 ""
}
@@ -158,7 +158,7 @@ func (r rowContext) strings_Replicate(arguments []interface{}) string {
timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
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 ""
}
@@ -191,7 +191,7 @@ func (r rowContext) strings_Right(arguments []interface{}) string {
lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
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 ""
}
@@ -220,11 +220,11 @@ func (r rowContext) strings_Substring(arguments []interface{}) string {
lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
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 ""
}
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 ""
}
@@ -264,7 +264,7 @@ func (r rowContext) parseString(argument interface{}) string {
return str1
}
logger.Error("StringEquals got parameters of wrong type")
logger.ErrorLn("StringEquals got parameters of wrong type")
return ""
}

View File

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

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
)
func Test_Execute_Where(t *testing.T) {
@@ -26,10 +27,7 @@ func Test_Execute_Where(t *testing.T) {
Filters: parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
Right: testutils.SelectItem_Constant_Bool(true),
},
},
mockData,
@@ -52,10 +50,7 @@ func Test_Execute_Where(t *testing.T) {
Filters: parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: "@param_id"},
},
Right: testutils.SelectItem_Constant_Parameter("@param_id"),
},
Parameters: map[string]interface{}{
"@param_id": "456",
@@ -83,18 +78,12 @@ func Test_Execute_Where(t *testing.T) {
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "67890"},
},
Right: testutils.SelectItem_Constant_String("67890"),
},
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "pk"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 456},
},
Right: testutils.SelectItem_Constant_Int(456),
},
},
},
@@ -120,10 +109,7 @@ func Test_Execute_Where(t *testing.T) {
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "isCool"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
},
Right: testutils.SelectItem_Constant_Bool(true),
},
parsers.LogicalExpression{
Operation: parsers.LogicalExpressionTypeOr,
@@ -131,18 +117,12 @@ func Test_Execute_Where(t *testing.T) {
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "123"},
},
Right: testutils.SelectItem_Constant_String("123"),
},
parsers.ComparisonExpression{
Operation: "=",
Left: parsers.SelectItem{Path: []string{"c", "id"}},
Right: parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{Type: parsers.ConstantTypeString, Value: "456"},
},
Right: testutils.SelectItem_Constant_String("456"),
},
},
},

View File

@@ -0,0 +1,89 @@
package main
import "C"
import (
"encoding/json"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
)
//export CreateCollection
func CreateCollection(serverName *C.char, databaseId *C.char, collectionJson *C.char) int {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionStr := C.GoString(collectionJson)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
var collection repositorymodels.Collection
err := json.Unmarshal([]byte(collectionStr), &collection)
if err != nil {
return ResponseFailedToParseRequest
}
_, code := serverInstance.repository.CreateCollection(databaseIdStr, collection)
return repositoryStatusToResponseCode(code)
}
//export GetCollection
func GetCollection(serverName *C.char, databaseId *C.char, collectionId *C.char) *C.char {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return C.CString("")
}
collection, code := serverInstance.repository.GetCollection(databaseIdStr, collectionIdStr)
if code != repositorymodels.StatusOk {
return C.CString("")
}
collectionJson, _ := json.Marshal(collection)
return C.CString(string(collectionJson))
}
//export GetAllCollections
func GetAllCollections(serverName *C.char, databaseId *C.char) *C.char {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return C.CString("")
}
collections, code := serverInstance.repository.GetAllCollections(databaseIdStr)
if code != repositorymodels.StatusOk {
return C.CString("")
}
collectionsJson, _ := json.Marshal(collections)
return C.CString(string(collectionsJson))
}
//export DeleteCollection
func DeleteCollection(serverName *C.char, databaseId *C.char, collectionId *C.char) int {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
code := serverInstance.repository.DeleteCollection(databaseIdStr, collectionIdStr)
return repositoryStatusToResponseCode(code)
}

View File

@@ -0,0 +1,89 @@
package main
import "C"
import (
"encoding/json"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
)
//export CreateDatabase
func CreateDatabase(serverName *C.char, databaseJson *C.char) int {
serverNameStr := C.GoString(serverName)
databaseStr := C.GoString(databaseJson)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
var database repositorymodels.Database
err := json.Unmarshal([]byte(databaseStr), &database)
if err != nil {
return ResponseFailedToParseRequest
}
_, code := serverInstance.repository.CreateDatabase(database)
return repositoryStatusToResponseCode(code)
}
//export GetDatabase
func GetDatabase(serverName *C.char, databaseId *C.char) *C.char {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return C.CString("")
}
database, code := serverInstance.repository.GetDatabase(databaseIdStr)
if code != repositorymodels.StatusOk {
return C.CString("")
}
databaseJson, _ := json.Marshal(database)
return C.CString(string(databaseJson))
}
//export GetAllDatabases
func GetAllDatabases(serverName *C.char) *C.char {
serverNameStr := C.GoString(serverName)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return C.CString("")
}
databases, code := serverInstance.repository.GetAllDatabases()
if code != repositorymodels.StatusOk {
return C.CString("")
}
databasesJson, err := json.Marshal(databases)
if err != nil {
return C.CString("")
}
return C.CString(string(databasesJson))
}
//export DeleteDatabase
func DeleteDatabase(serverName *C.char, databaseId *C.char) int {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
code := serverInstance.repository.DeleteDatabase(databaseIdStr)
return repositoryStatusToResponseCode(code)
}

122
sharedlibrary/documents.go Normal file
View File

@@ -0,0 +1,122 @@
package main
import "C"
import (
"encoding/json"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
)
//export CreateDocument
func CreateDocument(serverName *C.char, databaseId *C.char, collectionId *C.char, documentJson *C.char) int {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
documentStr := C.GoString(documentJson)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
var document repositorymodels.Document
err := json.Unmarshal([]byte(documentStr), &document)
if err != nil {
return ResponseFailedToParseRequest
}
_, code := serverInstance.repository.CreateDocument(databaseIdStr, collectionIdStr, document)
return repositoryStatusToResponseCode(code)
}
//export GetDocument
func GetDocument(serverName *C.char, databaseId *C.char, collectionId *C.char, documentId *C.char) *C.char {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
documentIdStr := C.GoString(documentId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return C.CString("")
}
document, code := serverInstance.repository.GetDocument(databaseIdStr, collectionIdStr, documentIdStr)
if code != repositorymodels.StatusOk {
return C.CString("")
}
documentJson, _ := json.Marshal(document)
return C.CString(string(documentJson))
}
//export GetAllDocuments
func GetAllDocuments(serverName *C.char, databaseId *C.char, collectionId *C.char) *C.char {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return C.CString("")
}
documents, code := serverInstance.repository.GetAllDocuments(databaseIdStr, collectionIdStr)
if code != repositorymodels.StatusOk {
return C.CString("")
}
documentsJson, _ := json.Marshal(documents)
return C.CString(string(documentsJson))
}
//export UpdateDocument
func UpdateDocument(serverName *C.char, databaseId *C.char, collectionId *C.char, documentId *C.char, documentJson *C.char) int {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
documentIdStr := C.GoString(documentId)
documentStr := C.GoString(documentJson)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
var document repositorymodels.Document
err := json.Unmarshal([]byte(documentStr), &document)
if err != nil {
return ResponseFailedToParseRequest
}
code := serverInstance.repository.DeleteDocument(databaseIdStr, collectionIdStr, documentIdStr)
if code != repositorymodels.StatusOk {
return repositoryStatusToResponseCode(code)
}
_, code = serverInstance.repository.CreateDocument(databaseIdStr, collectionIdStr, document)
return repositoryStatusToResponseCode(code)
}
//export DeleteDocument
func DeleteDocument(serverName *C.char, databaseId *C.char, collectionId *C.char, documentId *C.char) int {
serverNameStr := C.GoString(serverName)
databaseIdStr := C.GoString(databaseId)
collectionIdStr := C.GoString(collectionId)
documentIdStr := C.GoString(documentId)
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = getInstance(serverNameStr); !ok {
return ResponseServerInstanceNotFound
}
code := serverInstance.repository.DeleteDocument(databaseIdStr, collectionIdStr, documentIdStr)
return repositoryStatusToResponseCode(code)
}

86
sharedlibrary/shared.go Normal file
View File

@@ -0,0 +1,86 @@
package main
import (
"sync"
"github.com/pikami/cosmium/api"
"github.com/pikami/cosmium/internal/repositories"
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
)
type ServerInstance struct {
server *api.ApiServer
repository *repositories.DataRepository
}
var serverInstances map[string]*ServerInstance
var mutex sync.RWMutex
const (
ResponseSuccess = 0
ResponseUnknown = 100
ResponseFailedToParseConfiguration = 101
ResponseFailedToLoadState = 102
ResponseFailedToParseRequest = 103
ResponseServerInstanceAlreadyExists = 104
ResponseServerInstanceNotFound = 105
ResponseRepositoryNotFound = 200
ResponseRepositoryConflict = 201
ResponseRepositoryBadRequest = 202
)
func getInstance(serverName string) (*ServerInstance, bool) {
mutex.RLock()
defer mutex.RUnlock()
if serverInstances == nil {
serverInstances = make(map[string]*ServerInstance)
}
var ok bool
var serverInstance *ServerInstance
if serverInstance, ok = serverInstances[serverName]; !ok {
return nil, false
}
return serverInstance, true
}
func addInstance(serverName string, serverInstance *ServerInstance) {
mutex.Lock()
defer mutex.Unlock()
if serverInstances == nil {
serverInstances = make(map[string]*ServerInstance)
}
serverInstances[serverName] = serverInstance
}
func removeInstance(serverName string) {
mutex.Lock()
defer mutex.Unlock()
if serverInstances == nil {
return
}
delete(serverInstances, serverName)
}
func repositoryStatusToResponseCode(status repositorymodels.RepositoryStatus) int {
switch status {
case repositorymodels.StatusOk:
return ResponseSuccess
case repositorymodels.StatusNotFound:
return ResponseRepositoryNotFound
case repositorymodels.Conflict:
return ResponseRepositoryConflict
case repositorymodels.BadRequest:
return ResponseRepositoryBadRequest
default:
return ResponseUnknown
}
}

View File

@@ -9,32 +9,12 @@ import (
"github.com/pikami/cosmium/internal/repositories"
)
type ServerInstance struct {
server *api.ApiServer
repository *repositories.DataRepository
}
var serverInstances map[string]*ServerInstance
const (
ResponseSuccess = 0
ResponseUnknown = 1
ResponseServerInstanceAlreadyExists = 2
ResponseFailedToParseConfiguration = 3
ResponseServerInstanceNotFound = 4
ResponseFailedToLoadState = 5
)
//export CreateServerInstance
func CreateServerInstance(serverName *C.char, configurationJSON *C.char) int {
configStr := C.GoString(configurationJSON)
serverNameStr := C.GoString(serverName)
if serverInstances == nil {
serverInstances = make(map[string]*ServerInstance)
}
if _, ok := serverInstances[serverNameStr]; ok {
if _, ok := getInstance(serverNameStr); ok {
return ResponseServerInstanceAlreadyExists
}
@@ -55,19 +35,21 @@ func CreateServerInstance(serverName *C.char, configurationJSON *C.char) int {
server := api.NewApiServer(repository, configuration)
server.Start()
serverInstances[serverNameStr] = &ServerInstance{
addInstance(serverNameStr, &ServerInstance{
server: server,
repository: repository,
}
})
return ResponseSuccess
}
//export StopServerInstance
func StopServerInstance(serverName *C.char) int {
if serverInstance, ok := serverInstances[C.GoString(serverName)]; ok {
serverNameStr := C.GoString(serverName)
if serverInstance, ok := getInstance(serverNameStr); ok {
serverInstance.server.Stop()
delete(serverInstances, C.GoString(serverName))
removeInstance(serverNameStr)
return ResponseSuccess
}
@@ -76,7 +58,9 @@ func StopServerInstance(serverName *C.char) int {
//export GetServerInstanceState
func GetServerInstanceState(serverName *C.char) *C.char {
if serverInstance, ok := serverInstances[C.GoString(serverName)]; ok {
serverNameStr := C.GoString(serverName)
if serverInstance, ok := getInstance(serverNameStr); ok {
stateJSON, err := serverInstance.repository.GetState()
if err != nil {
return nil
@@ -92,7 +76,7 @@ func LoadServerInstanceState(serverName *C.char, stateJSON *C.char) int {
serverNameStr := C.GoString(serverName)
stateJSONStr := C.GoString(stateJSON)
if serverInstance, ok := serverInstances[serverNameStr]; ok {
if serverInstance, ok := getInstance(serverNameStr); ok {
err := serverInstance.repository.LoadStateJSON(stateJSONStr)
if err != nil {
return ResponseFailedToLoadState

View File

@@ -1,8 +1,9 @@
#include "shared.h"
void test_CreateServerInstance();
void test_StopServerInstance();
void test_ServerInstanceStateMethods();
int test_CreateServerInstance();
int test_StopServerInstance();
int test_ServerInstanceStateMethods();
int test_Databases();
int main(int argc, char *argv[])
{
@@ -21,9 +22,24 @@ int main(int argc, char *argv[])
}
printf("Running tests for library: %s\n", libPath);
test_CreateServerInstance();
test_ServerInstanceStateMethods();
test_StopServerInstance();
int results[] = {
test_CreateServerInstance(),
test_Databases(),
test_ServerInstanceStateMethods(),
test_StopServerInstance(),
};
int numTests = sizeof(results) / sizeof(results[0]);
int numPassed = 0;
for (int i = 0; i < numTests; i++)
{
if (results[i])
{
numPassed++;
}
}
printf("Tests passed: %d/%d\n", numPassed, numTests);
dlclose(handle);
return EXIT_SUCCESS;

View File

@@ -1,6 +1,6 @@
#include "shared.h"
void test_CreateServerInstance()
int test_CreateServerInstance()
{
typedef int (*CreateServerInstanceFn)(char *, char *);
CreateServerInstanceFn CreateServerInstance = (CreateServerInstanceFn)load_function("CreateServerInstance");
@@ -8,7 +8,7 @@ void test_CreateServerInstance()
if (!CreateServerInstance)
{
fprintf(stderr, "Failed to find CreateServerInstance function\n");
return;
return 0;
}
char *serverName = "TestServer";
@@ -22,5 +22,8 @@ void test_CreateServerInstance()
else
{
printf("CreateServerInstance: FAILED (result = %d)\n", result);
return 0;
}
return 1;
}

View File

@@ -0,0 +1,47 @@
#include "shared.h"
int test_Databases()
{
typedef int (*CreateDatabaseFn)(char *, char *);
CreateDatabaseFn CreateDatabase = (CreateDatabaseFn)load_function("CreateDatabase");
if (!CreateDatabase)
{
fprintf(stderr, "Failed to find CreateDatabase function\n");
return 0;
}
char *serverName = "TestServer";
char *configJSON = "{\"id\":\"test-db\"}";
int result = CreateDatabase(serverName, configJSON);
if (result == 0)
{
printf("CreateDatabase: SUCCESS\n");
}
else
{
printf("CreateDatabase: FAILED (result = %d)\n", result);
return 0;
}
typedef char *(*GetDatabaseFn)(char *, char *);
GetDatabaseFn GetDatabase = (GetDatabaseFn)load_function("GetDatabase");
if (!GetDatabase)
{
fprintf(stderr, "Failed to find GetDatabase function\n");
return 0;
}
char *database = GetDatabase(serverName, "test-db");
if (database)
{
printf("GetDatabase: SUCCESS (database = %s)\n", database);
}
else
{
printf("GetDatabase: FAILED\n");
return 0;
}
return 1;
}

View File

@@ -1,13 +1,13 @@
#include "shared.h"
void test_ServerInstanceStateMethods()
int test_ServerInstanceStateMethods()
{
typedef int (*LoadServerInstanceStateFn)(char *, char *);
LoadServerInstanceStateFn LoadServerInstanceState = (LoadServerInstanceStateFn)load_function("LoadServerInstanceState");
if (!LoadServerInstanceState)
{
fprintf(stderr, "Failed to find LoadServerInstanceState function\n");
return;
return 0;
}
char *serverName = "TestServer";
@@ -20,6 +20,7 @@ void test_ServerInstanceStateMethods()
else
{
printf("LoadServerInstanceState: FAILED (result = %d)\n", result);
return 0;
}
typedef char *(*GetServerInstanceStateFn)(char *);
@@ -27,7 +28,7 @@ void test_ServerInstanceStateMethods()
if (!GetServerInstanceState)
{
fprintf(stderr, "Failed to find GetServerInstanceState function\n");
return;
return 0;
}
char *state = GetServerInstanceState(serverName);
@@ -38,6 +39,7 @@ void test_ServerInstanceStateMethods()
else
{
printf("GetServerInstanceState: FAILED\n");
return 0;
}
const char *expected_state = "{\"databases\":{\"test-db\":{\"id\":\"test-db\",\"_ts\":0,\"_rid\":\"\",\"_etag\":\"\",\"_self\":\"\"}},\"collections\":{\"test-db\":{}},\"documents\":{\"test-db\":{}}}";
@@ -45,7 +47,7 @@ void test_ServerInstanceStateMethods()
if (!compact_state)
{
free(state);
return;
return 0;
}
if (strcmp(compact_state, expected_state) == 0)
@@ -57,8 +59,10 @@ void test_ServerInstanceStateMethods()
printf("GetServerInstanceState: State does not match expected value.\n");
printf("Expected: %s\n", expected_state);
printf("Actual: %s\n", compact_state);
return 0;
}
free(state);
free(compact_state);
return 1;
}

View File

@@ -1,6 +1,6 @@
#include "shared.h"
void test_StopServerInstance()
int test_StopServerInstance()
{
typedef int (*StopServerInstanceFn)(char *);
StopServerInstanceFn StopServerInstance = (StopServerInstanceFn)load_function("StopServerInstance");
@@ -8,7 +8,7 @@ void test_StopServerInstance()
if (!StopServerInstance)
{
fprintf(stderr, "Failed to find StopServerInstance function\n");
return;
return 0;
}
char *serverName = "TestServer";
@@ -20,5 +20,8 @@ void test_StopServerInstance()
else
{
printf("StopServerInstance: FAILED (result = %d)\n", result);
return 0;
}
return 1;
}

View File

@@ -0,0 +1,53 @@
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,
},
}
}