mirror of
https://github.com/pikami/cosmium.git
synced 2026-06-14 16:27:17 +01:00
Compare commits
6 Commits
30195fae96
...
0.1.24
| Author | SHA1 | Date | |
|---|---|---|---|
| fba9b3df5f | |||
| b743e23ff9 | |||
| 11851297f5 | |||
| 560ea5296d | |||
| e20a6ca7cd | |||
| 7e0c10479b |
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/constants"
|
||||||
"github.com/pikami/cosmium/internal/datastore"
|
"github.com/pikami/cosmium/internal/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ func (h *Handlers) GetAllCollections(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) GetCollection(c *gin.Context) {
|
func (h *Handlers) GetCollection(c *gin.Context) {
|
||||||
@@ -38,11 +39,11 @@ func (h *Handlers) GetCollection(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DeleteCollection(c *gin.Context) {
|
func (h *Handlers) DeleteCollection(c *gin.Context) {
|
||||||
@@ -56,11 +57,11 @@ func (h *Handlers) DeleteCollection(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) CreateCollection(c *gin.Context) {
|
func (h *Handlers) CreateCollection(c *gin.Context) {
|
||||||
@@ -73,13 +74,13 @@ func (h *Handlers) CreateCollection(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newCollection.ID == "" {
|
if newCollection.ID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"message": "BadRequest"})
|
c.JSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdCollection, status := h.dataStore.CreateCollection(databaseId, newCollection)
|
createdCollection, status := h.dataStore.CreateCollection(databaseId, newCollection)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,5 +89,5 @@ func (h *Handlers) CreateCollection(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/constants"
|
||||||
"github.com/pikami/cosmium/internal/datastore"
|
"github.com/pikami/cosmium/internal/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ func (h *Handlers) GetAllDatabases(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) GetDatabase(c *gin.Context) {
|
func (h *Handlers) GetDatabase(c *gin.Context) {
|
||||||
@@ -33,11 +34,11 @@ func (h *Handlers) GetDatabase(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DeleteDatabase(c *gin.Context) {
|
func (h *Handlers) DeleteDatabase(c *gin.Context) {
|
||||||
@@ -50,11 +51,11 @@ func (h *Handlers) DeleteDatabase(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) CreateDatabase(c *gin.Context) {
|
func (h *Handlers) CreateDatabase(c *gin.Context) {
|
||||||
@@ -66,13 +67,13 @@ func (h *Handlers) CreateDatabase(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newDatabase.ID == "" {
|
if newDatabase.ID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"message": "BadRequest"})
|
c.JSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdDatabase, status := h.dataStore.CreateDatabase(newDatabase)
|
createdDatabase, status := h.dataStore.CreateDatabase(newDatabase)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,5 +82,5 @@ func (h *Handlers) CreateDatabase(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -35,7 +35,7 @@ func (h *Handlers) GetAllDocuments(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) GetDocument(c *gin.Context) {
|
func (h *Handlers) GetDocument(c *gin.Context) {
|
||||||
@@ -50,11 +50,11 @@ func (h *Handlers) GetDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DeleteDocument(c *gin.Context) {
|
func (h *Handlers) DeleteDocument(c *gin.Context) {
|
||||||
@@ -69,11 +69,11 @@ func (h *Handlers) DeleteDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Maybe move "replace" logic to data store
|
// TODO: Maybe move "replace" logic to data store
|
||||||
@@ -90,13 +90,13 @@ func (h *Handlers) ReplaceDocument(c *gin.Context) {
|
|||||||
|
|
||||||
status := h.dataStore.DeleteDocument(databaseId, collectionId, documentId)
|
status := h.dataStore.DeleteDocument(databaseId, collectionId, documentId)
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, requestBody)
|
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, requestBody)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ func (h *Handlers) ReplaceDocument(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) PatchDocument(c *gin.Context) {
|
func (h *Handlers) PatchDocument(c *gin.Context) {
|
||||||
@@ -115,7 +115,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
|
|||||||
|
|
||||||
document, status := h.dataStore.GetDocument(databaseId, collectionId, documentId)
|
document, status := h.dataStore.GetDocument(databaseId, collectionId, documentId)
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,13 +166,13 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
|
|||||||
|
|
||||||
status = h.dataStore.DeleteDocument(databaseId, collectionId, documentId)
|
status = h.dataStore.DeleteDocument(databaseId, collectionId, documentId)
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, modifiedDocument)
|
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, modifiedDocument)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DocumentsPost(c *gin.Context) {
|
func (h *Handlers) DocumentsPost(c *gin.Context) {
|
||||||
@@ -208,7 +208,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if requestBody["id"] == "" {
|
if requestBody["id"] == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"message": "BadRequest"})
|
c.JSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
|
|||||||
|
|
||||||
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, requestBody)
|
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, requestBody)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parametersToMap(pairs []interface{}) map[string]interface{} {
|
func parametersToMap(pairs []interface{}) map[string]interface{} {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/constants"
|
||||||
"github.com/pikami/cosmium/internal/datastore"
|
"github.com/pikami/cosmium/internal/datastore"
|
||||||
"github.com/pikami/cosmium/internal/resourceid"
|
"github.com/pikami/cosmium/internal/resourceid"
|
||||||
)
|
)
|
||||||
@@ -42,9 +43,9 @@ func (h *Handlers) GetPartitionKeyRanges(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/constants"
|
||||||
"github.com/pikami/cosmium/internal/datastore"
|
"github.com/pikami/cosmium/internal/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ func (h *Handlers) GetAllStoredProcedures(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) GetStoredProcedure(c *gin.Context) {
|
func (h *Handlers) GetStoredProcedure(c *gin.Context) {
|
||||||
@@ -36,11 +37,11 @@ func (h *Handlers) GetStoredProcedure(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DeleteStoredProcedure(c *gin.Context) {
|
func (h *Handlers) DeleteStoredProcedure(c *gin.Context) {
|
||||||
@@ -55,11 +56,11 @@ func (h *Handlers) DeleteStoredProcedure(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
||||||
@@ -69,19 +70,19 @@ func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
|||||||
|
|
||||||
var sp datastore.StoredProcedure
|
var sp datastore.StoredProcedure
|
||||||
if err := c.BindJSON(&sp); err != nil {
|
if err := c.BindJSON(&sp); err != nil {
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status := h.dataStore.DeleteStoredProcedure(databaseId, collectionId, spId)
|
status := h.dataStore.DeleteStoredProcedure(databaseId, collectionId, spId)
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdSP, status := h.dataStore.CreateStoredProcedure(databaseId, collectionId, sp)
|
createdSP, status := h.dataStore.CreateStoredProcedure(databaseId, collectionId, sp)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
||||||
@@ -99,13 +100,13 @@ func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
|||||||
|
|
||||||
var sp datastore.StoredProcedure
|
var sp datastore.StoredProcedure
|
||||||
if err := c.BindJSON(&sp); err != nil {
|
if err := c.BindJSON(&sp); err != nil {
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdSP, status := h.dataStore.CreateStoredProcedure(databaseId, collectionId, sp)
|
createdSP, status := h.dataStore.CreateStoredProcedure(databaseId, collectionId, sp)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,5 +115,5 @@ func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-12
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/constants"
|
||||||
"github.com/pikami/cosmium/internal/datastore"
|
"github.com/pikami/cosmium/internal/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ func (h *Handlers) GetAllTriggers(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) GetTrigger(c *gin.Context) {
|
func (h *Handlers) GetTrigger(c *gin.Context) {
|
||||||
@@ -36,11 +37,11 @@ func (h *Handlers) GetTrigger(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DeleteTrigger(c *gin.Context) {
|
func (h *Handlers) DeleteTrigger(c *gin.Context) {
|
||||||
@@ -55,11 +56,11 @@ func (h *Handlers) DeleteTrigger(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
||||||
@@ -69,19 +70,19 @@ func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
|||||||
|
|
||||||
var trigger datastore.Trigger
|
var trigger datastore.Trigger
|
||||||
if err := c.BindJSON(&trigger); err != nil {
|
if err := c.BindJSON(&trigger); err != nil {
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status := h.dataStore.DeleteTrigger(databaseId, collectionId, triggerId)
|
status := h.dataStore.DeleteTrigger(databaseId, collectionId, triggerId)
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdTrigger, status := h.dataStore.CreateTrigger(databaseId, collectionId, trigger)
|
createdTrigger, status := h.dataStore.CreateTrigger(databaseId, collectionId, trigger)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) CreateTrigger(c *gin.Context) {
|
func (h *Handlers) CreateTrigger(c *gin.Context) {
|
||||||
@@ -99,13 +100,13 @@ func (h *Handlers) CreateTrigger(c *gin.Context) {
|
|||||||
|
|
||||||
var trigger datastore.Trigger
|
var trigger datastore.Trigger
|
||||||
if err := c.BindJSON(&trigger); err != nil {
|
if err := c.BindJSON(&trigger); err != nil {
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdTrigger, status := h.dataStore.CreateTrigger(databaseId, collectionId, trigger)
|
createdTrigger, status := h.dataStore.CreateTrigger(databaseId, collectionId, trigger)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,5 +115,5 @@ func (h *Handlers) CreateTrigger(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/constants"
|
||||||
"github.com/pikami/cosmium/internal/datastore"
|
"github.com/pikami/cosmium/internal/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ func (h *Handlers) GetAllUserDefinedFunctions(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) GetUserDefinedFunction(c *gin.Context) {
|
func (h *Handlers) GetUserDefinedFunction(c *gin.Context) {
|
||||||
@@ -36,11 +37,11 @@ func (h *Handlers) GetUserDefinedFunction(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) DeleteUserDefinedFunction(c *gin.Context) {
|
func (h *Handlers) DeleteUserDefinedFunction(c *gin.Context) {
|
||||||
@@ -55,11 +56,11 @@ func (h *Handlers) DeleteUserDefinedFunction(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
||||||
@@ -69,19 +70,19 @@ func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
|||||||
|
|
||||||
var udf datastore.UserDefinedFunction
|
var udf datastore.UserDefinedFunction
|
||||||
if err := c.BindJSON(&udf); err != nil {
|
if err := c.BindJSON(&udf); err != nil {
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status := h.dataStore.DeleteUserDefinedFunction(databaseId, collectionId, udfId)
|
status := h.dataStore.DeleteUserDefinedFunction(databaseId, collectionId, udfId)
|
||||||
if status == datastore.StatusNotFound {
|
if status == datastore.StatusNotFound {
|
||||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdUdf, status := h.dataStore.CreateUserDefinedFunction(databaseId, collectionId, udf)
|
createdUdf, status := h.dataStore.CreateUserDefinedFunction(databaseId, collectionId, udf)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
||||||
@@ -99,13 +100,13 @@ func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
|||||||
|
|
||||||
var udf datastore.UserDefinedFunction
|
var udf datastore.UserDefinedFunction
|
||||||
if err := c.BindJSON(&udf); err != nil {
|
if err := c.BindJSON(&udf); err != nil {
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdUdf, status := h.dataStore.CreateUserDefinedFunction(databaseId, collectionId, udf)
|
createdUdf, status := h.dataStore.CreateUserDefinedFunction(databaseId, collectionId, udf)
|
||||||
if status == datastore.Conflict {
|
if status == datastore.Conflict {
|
||||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,5 +115,5 @@ func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func Test_Authentication(t *testing.T) {
|
|||||||
t.Run("Should get 200 when correct account key is used", func(t *testing.T) {
|
t.Run("Should get 200 when correct account key is used", func(t *testing.T) {
|
||||||
ts.DataStore.DeleteDatabase(testDatabaseName)
|
ts.DataStore.DeleteDatabase(testDatabaseName)
|
||||||
client, err := azcosmos.NewClientFromConnectionString(
|
client, err := azcosmos.NewClientFromConnectionString(
|
||||||
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, config.DefaultAccountKey),
|
formatConnectionString(ts.URL, config.DefaultAccountKey),
|
||||||
&azcosmos.ClientOptions{},
|
&azcosmos.ClientOptions{},
|
||||||
)
|
)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -35,7 +35,7 @@ func Test_Authentication(t *testing.T) {
|
|||||||
t.Run("Should get 401 when wrong account key is used", func(t *testing.T) {
|
t.Run("Should get 401 when wrong account key is used", func(t *testing.T) {
|
||||||
ts.DataStore.DeleteDatabase(testDatabaseName)
|
ts.DataStore.DeleteDatabase(testDatabaseName)
|
||||||
client, err := azcosmos.NewClientFromConnectionString(
|
client, err := azcosmos.NewClientFromConnectionString(
|
||||||
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, "AAAA"),
|
formatConnectionString(ts.URL, "AAAA"),
|
||||||
&azcosmos.ClientOptions{},
|
&azcosmos.ClientOptions{},
|
||||||
)
|
)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -72,7 +72,7 @@ func Test_Authentication_Disabled(t *testing.T) {
|
|||||||
t.Run("Should get 200 when wrong account key is used, but authentication is dissabled", func(t *testing.T) {
|
t.Run("Should get 200 when wrong account key is used, but authentication is dissabled", func(t *testing.T) {
|
||||||
ts.DataStore.DeleteDatabase(testDatabaseName)
|
ts.DataStore.DeleteDatabase(testDatabaseName)
|
||||||
client, err := azcosmos.NewClientFromConnectionString(
|
client, err := azcosmos.NewClientFromConnectionString(
|
||||||
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, "AAAA"),
|
formatConnectionString(ts.URL, "AAAA"),
|
||||||
&azcosmos.ClientOptions{},
|
&azcosmos.ClientOptions{},
|
||||||
)
|
)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -85,3 +85,7 @@ func Test_Authentication_Disabled(t *testing.T) {
|
|||||||
assert.Equal(t, createResponse.DatabaseProperties.ID, testDatabaseName)
|
assert.Equal(t, createResponse.DatabaseProperties.ID, testDatabaseName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatConnectionString(endpoint, key string) string {
|
||||||
|
return fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", endpoint, key)
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ Cosmium strives to support the core features of Cosmos DB, including:
|
|||||||
|
|
||||||
| Function | Implemented |
|
| Function | Implemented |
|
||||||
| -------- | ----------- |
|
| -------- | ----------- |
|
||||||
| IIF | No |
|
| IIF | Yes |
|
||||||
|
|
||||||
### Date and time Functions
|
### Date and time Functions
|
||||||
|
|
||||||
|
|||||||
@@ -30,3 +30,8 @@ var QueryPlanResponse = gin.H{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var UnknownErrorResponse = gin.H{"message": "Unknown error"}
|
||||||
|
var NotFoundResponse = gin.H{"message": "NotFound"}
|
||||||
|
var ConflictResponse = gin.H{"message": "Conflict"}
|
||||||
|
var BadRequestResponse = gin.H{"message": "BadRequest"}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package badgerdatastore
|
package badgerdatastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
"github.com/pikami/cosmium/internal/logger"
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BadgerDataStore struct {
|
type BadgerDataStore struct {
|
||||||
db *badger.DB
|
db *badger.DB
|
||||||
|
gcTicker *time.Ticker
|
||||||
}
|
}
|
||||||
|
|
||||||
type BadgerDataStoreOptions struct {
|
type BadgerDataStoreOptions struct {
|
||||||
@@ -25,12 +28,24 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BadgerDataStore{
|
gcTicker := time.NewTicker(5 * time.Minute)
|
||||||
|
|
||||||
|
ds := &BadgerDataStore{
|
||||||
db: db,
|
db: db,
|
||||||
|
gcTicker: gcTicker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go ds.runGarbageCollector()
|
||||||
|
|
||||||
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BadgerDataStore) Close() {
|
func (r *BadgerDataStore) Close() {
|
||||||
|
if r.gcTicker != nil {
|
||||||
|
r.gcTicker.Stop()
|
||||||
|
r.gcTicker = nil
|
||||||
|
}
|
||||||
|
|
||||||
r.db.Close()
|
r.db.Close()
|
||||||
r.db = nil
|
r.db = nil
|
||||||
}
|
}
|
||||||
@@ -39,3 +54,13 @@ func (r *BadgerDataStore) DumpToJson() (string, error) {
|
|||||||
logger.ErrorLn("Badger datastore does not support state export currently.")
|
logger.ErrorLn("Badger datastore does not support state export currently.")
|
||||||
return "{}", nil
|
return "{}", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *BadgerDataStore) runGarbageCollector() {
|
||||||
|
for range r.gcTicker.C {
|
||||||
|
again:
|
||||||
|
err := r.db.RunValueLogGC(0.7)
|
||||||
|
if err == nil {
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const (
|
|||||||
SelectItemTypeConstant
|
SelectItemTypeConstant
|
||||||
SelectItemTypeFunctionCall
|
SelectItemTypeFunctionCall
|
||||||
SelectItemTypeSubQuery
|
SelectItemTypeSubQuery
|
||||||
|
SelectItemTypeExpression
|
||||||
|
SelectItemTypeBinaryExpression
|
||||||
)
|
)
|
||||||
|
|
||||||
type SelectItem struct {
|
type SelectItem struct {
|
||||||
@@ -64,6 +66,12 @@ type ComparisonExpression struct {
|
|||||||
Operation string
|
Operation string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BinaryExpression struct {
|
||||||
|
Left interface{}
|
||||||
|
Right interface{}
|
||||||
|
Operation string
|
||||||
|
}
|
||||||
|
|
||||||
type ConstantType int
|
type ConstantType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -134,6 +142,8 @@ const (
|
|||||||
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
|
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
|
||||||
FunctionCallSetUnion FunctionCallType = "SetUnion"
|
FunctionCallSetUnion FunctionCallType = "SetUnion"
|
||||||
|
|
||||||
|
FunctionCallIif FunctionCallType = "Iif"
|
||||||
|
|
||||||
FunctionCallMathAbs FunctionCallType = "MathAbs"
|
FunctionCallMathAbs FunctionCallType = "MathAbs"
|
||||||
FunctionCallMathAcos FunctionCallType = "MathAcos"
|
FunctionCallMathAcos FunctionCallType = "MathAcos"
|
||||||
FunctionCallMathAsin FunctionCallType = "MathAsin"
|
FunctionCallMathAsin FunctionCallType = "MathAsin"
|
||||||
|
|||||||
@@ -0,0 +1,366 @@
|
|||||||
|
package nosql_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
testutils "github.com/pikami/cosmium/test_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Parse_Arithmetics(t *testing.T) {
|
||||||
|
t.Run("Should parse multiplication before addition", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a + c.b * c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "b"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse division before subtraction", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.x - c.y / c.z FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: testutils.SelectItem_Path("c", "x"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "/",
|
||||||
|
Left: testutils.SelectItem_Path("c", "y"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle complex mixed operations", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a + c.b * c.c - c.d / c.e FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "b"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "/",
|
||||||
|
Left: testutils.SelectItem_Path("c", "d"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "e"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should respect parentheses overriding precedence", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT (c.a + c.b) * c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle nested parentheses", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT ((c.a + c.b) * c.c) - c.d FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "d"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should be left associative for same precedence operators", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a - c.b - c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should be left associative with multiplication and division", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a * c.b / c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "/",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle math with constants", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT 10 + 20 * 5 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Constant_Int(10),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Constant_Int(20),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle math with floating point numbers", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.price * 1.08 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "price"),
|
||||||
|
Right: testutils.SelectItem_Constant_Float(1.08),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle parentheses around single value", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT (c.value) FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "value"),
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle function calls in math expressions", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT LENGTH(c.name) * 2 + 10 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeFunctionCall,
|
||||||
|
Value: parsers.FunctionCall{
|
||||||
|
Type: parsers.FunctionCallLength,
|
||||||
|
Arguments: []interface{}{testutils.SelectItem_Path("c", "name")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle multiple select items with math", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a + c.b, c.x * c.y FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "x"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle math in WHERE clause", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.id FROM c WHERE c.price * 1.08 > 100`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "id"),
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
Filters: parsers.ComparisonExpression{
|
||||||
|
Operation: ">",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "price"),
|
||||||
|
Right: testutils.SelectItem_Constant_Float(1.08),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -163,4 +163,27 @@ func Test_Parse(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse IIF function", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT IIF(true, c.pk, c.id) FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeFunctionCall,
|
||||||
|
Value: parsers.FunctionCall{
|
||||||
|
Type: parsers.FunctionCallIif,
|
||||||
|
Arguments: []interface{}{
|
||||||
|
testutils.SelectItem_Constant_Bool(true),
|
||||||
|
testutils.SelectItem_Path("c", "pk"),
|
||||||
|
testutils.SelectItem_Path("c", "id"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+2383
-1820
File diff suppressed because it is too large
Load Diff
+92
-13
@@ -178,6 +178,32 @@ func combineExpressions(ex1 interface{}, exs interface{}, operation parsers.Logi
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeMathExpression(left interface{}, operations interface{}) (interface{}, error) {
|
||||||
|
if operations == nil || len(operations.([]interface{})) == 0 {
|
||||||
|
return left, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := left.(parsers.SelectItem)
|
||||||
|
ops := operations.([]interface{})
|
||||||
|
|
||||||
|
for _, op := range ops {
|
||||||
|
opData := op.([]interface{})
|
||||||
|
operation := opData[0].(string)
|
||||||
|
right := opData[1].(parsers.SelectItem)
|
||||||
|
|
||||||
|
result = parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Left: result,
|
||||||
|
Right: right,
|
||||||
|
Operation: operation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Input <- selectStmt:SelectStmt {
|
Input <- selectStmt:SelectStmt {
|
||||||
@@ -204,7 +230,7 @@ TopClause <- Top ws count:Integer {
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
FromClause <- From ws table:TableName selectItem:(ws In ws column:SelectItem { return column, nil }) {
|
FromClause <- From ws table:TableName selectItem:(ws In ws column:SelectItemWithAlias { return column, nil }) {
|
||||||
tableTyped := table.(parsers.Table)
|
tableTyped := table.(parsers.Table)
|
||||||
|
|
||||||
if selectItem != nil {
|
if selectItem != nil {
|
||||||
@@ -213,7 +239,7 @@ FromClause <- From ws table:TableName selectItem:(ws In ws column:SelectItem { r
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tableTyped, nil
|
return tableTyped, nil
|
||||||
} / From ws column:SelectItem {
|
} / From ws column:SelectItemWithAlias {
|
||||||
tableSelectItem := column.(parsers.SelectItem)
|
tableSelectItem := column.(parsers.SelectItem)
|
||||||
table := parsers.Table{
|
table := parsers.Table{
|
||||||
Value: tableSelectItem.Alias,
|
Value: tableSelectItem.Alias,
|
||||||
@@ -251,7 +277,7 @@ SubQuerySelectItem <- subQuery:SubQuery asClause:(ws alias:AsClause { return ali
|
|||||||
return selectItem, nil
|
return selectItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
JoinClause <- Join ws table:TableName ws In ws column:SelectItem {
|
JoinClause <- Join ws table:TableName ws In ws column:SelectItemWithAlias {
|
||||||
return makeJoin(table, column)
|
return makeJoin(table, column)
|
||||||
} / Join ws subQuery:SubQuerySelectItem {
|
} / Join ws subQuery:SubQuerySelectItem {
|
||||||
return makeJoin(nil, subQuery)
|
return makeJoin(nil, subQuery)
|
||||||
@@ -269,11 +295,34 @@ SelectAsterisk <- "*" {
|
|||||||
return makeColumnList(selectItem, make([]interface{}, 0))
|
return makeColumnList(selectItem, make([]interface{}, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnList <- column:SelectItem other_columns:(ws "," ws coll:SelectItem {return coll, nil })* {
|
ColumnList <- column:ExpressionOrSelectItem other_columns:(ws "," ws coll:ExpressionOrSelectItem {return coll, nil })* {
|
||||||
return makeColumnList(column, other_columns)
|
return makeColumnList(column, other_columns)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectValueSpec <- "VALUE"i ws column:SelectItem {
|
ExpressionOrSelectItem <- expression:OrExpression asClause:AsClause? {
|
||||||
|
switch typedValue := expression.(type) {
|
||||||
|
case parsers.ComparisonExpression, parsers.LogicalExpression:
|
||||||
|
selectItem := parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: typedValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
if aliasValue, ok := asClause.(string); ok {
|
||||||
|
selectItem.Alias = aliasValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectItem, nil
|
||||||
|
case parsers.SelectItem:
|
||||||
|
if aliasValue, ok := asClause.(string); ok {
|
||||||
|
typedValue.Alias = aliasValue
|
||||||
|
}
|
||||||
|
return typedValue, nil
|
||||||
|
default:
|
||||||
|
return typedValue, nil
|
||||||
|
}
|
||||||
|
} / item:SelectItemWithAlias { return item, nil }
|
||||||
|
|
||||||
|
SelectValueSpec <- "VALUE"i ws column:SelectItemWithAlias {
|
||||||
selectItem := column.(parsers.SelectItem)
|
selectItem := column.(parsers.SelectItem)
|
||||||
selectItem.IsTopLevel = true
|
selectItem.IsTopLevel = true
|
||||||
return makeColumnList(selectItem, make([]interface{}, 0))
|
return makeColumnList(selectItem, make([]interface{}, 0))
|
||||||
@@ -289,6 +338,11 @@ SelectArray <- "[" ws columns:ColumnList ws "]" {
|
|||||||
|
|
||||||
SelectObject <- "{" ws field:SelectObjectField ws other_fields:(ws "," ws coll:SelectObjectField {return coll, nil })* ws "}" {
|
SelectObject <- "{" ws field:SelectObjectField ws other_fields:(ws "," ws coll:SelectObjectField {return coll, nil })* ws "}" {
|
||||||
return makeSelectObject(field, other_fields)
|
return makeSelectObject(field, other_fields)
|
||||||
|
} / "{" ws "}" {
|
||||||
|
return parsers.SelectItem{
|
||||||
|
SelectItems: []parsers.SelectItem{},
|
||||||
|
Type: parsers.SelectItemTypeObject,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectObjectField <- name:(Identifier / "\"" key:Identifier "\"" { return key, nil }) ws ":" ws selectItem:SelectItem {
|
SelectObjectField <- name:(Identifier / "\"" key:Identifier "\"" { return key, nil }) ws ":" ws selectItem:SelectItem {
|
||||||
@@ -301,7 +355,15 @@ SelectProperty <- name:Identifier path:(DotFieldAccess / ArrayFieldAccess)* {
|
|||||||
return makeSelectItem(name, path, parsers.SelectItemTypeField)
|
return makeSelectItem(name, path, parsers.SelectItemTypeField)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
|
SelectItemWithAlias <- selectItem:SelectItem asClause:AsClause? {
|
||||||
|
item := selectItem.(parsers.SelectItem)
|
||||||
|
if aliasValue, ok := asClause.(string); ok {
|
||||||
|
item.Alias = aliasValue
|
||||||
|
}
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) {
|
||||||
var itemResult parsers.SelectItem
|
var itemResult parsers.SelectItem
|
||||||
switch typedValue := selectItem.(type) {
|
switch typedValue := selectItem.(type) {
|
||||||
case parsers.SelectItem:
|
case parsers.SelectItem:
|
||||||
@@ -318,10 +380,6 @@ SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectAr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliasValue, ok := asClause.(string); ok {
|
|
||||||
itemResult.Alias = aliasValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemResult, nil
|
return itemResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,10 +413,20 @@ AndExpression <- ex1:ComparisonExpression ex2:(ws And ws ex:ComparisonExpression
|
|||||||
return combineExpressions(ex1, ex2, parsers.LogicalExpressionTypeAnd)
|
return combineExpressions(ex1, ex2, parsers.LogicalExpressionTypeAnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil }
|
ComparisonExpression <- left:AddSubExpression ws op:ComparisonOperator ws right:AddSubExpression {
|
||||||
/ left:SelectItem ws op:ComparisonOperator ws right:SelectItem {
|
|
||||||
return parsers.ComparisonExpression{Left:left,Right:right,Operation:op.(string)}, nil
|
return parsers.ComparisonExpression{Left:left,Right:right,Operation:op.(string)}, nil
|
||||||
} / inv:(Not ws)? ex:SelectItem {
|
} / ex:AddSubExpression { return ex, nil }
|
||||||
|
|
||||||
|
AddSubExpression <- left:MulDivExpression operations:(ws op:AddOrSubtractOperation ws right:MulDivExpression { return []interface{}{op, right}, nil })* {
|
||||||
|
return makeMathExpression(left, operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
MulDivExpression <- left:SelectItemWithParentheses operations:(ws op:MultiplyOrDivideOperation ws right:SelectItemWithParentheses { return []interface{}{op, right}, nil })* {
|
||||||
|
return makeMathExpression(left, operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectItemWithParentheses <- "(" ws ex:OrExpression ws ")" { return ex, nil }
|
||||||
|
/ inv:(Not ws)? ex:SelectItem {
|
||||||
if inv != nil {
|
if inv != nil {
|
||||||
ex1 := ex.(parsers.SelectItem)
|
ex1 := ex.(parsers.SelectItem)
|
||||||
ex1.Invert = true
|
ex1.Invert = true
|
||||||
@@ -415,6 +483,10 @@ ComparisonOperator <- ("<=" / ">=" / "=" / "!=" / "<" / ">") {
|
|||||||
return string(c.text), nil
|
return string(c.text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddOrSubtractOperation <- ("+" / "-") { return string(c.text), nil }
|
||||||
|
|
||||||
|
MultiplyOrDivideOperation <- ("*" / "/") { return string(c.text), nil }
|
||||||
|
|
||||||
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
|
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
|
||||||
|
|
||||||
ParameterConstant <- "@" Identifier {
|
ParameterConstant <- "@" Identifier {
|
||||||
@@ -442,6 +514,7 @@ BooleanLiteral <- ("true"i / "false"i) {
|
|||||||
FunctionCall <- StringFunctions
|
FunctionCall <- StringFunctions
|
||||||
/ TypeCheckingFunctions
|
/ TypeCheckingFunctions
|
||||||
/ ArrayFunctions
|
/ ArrayFunctions
|
||||||
|
/ ConditionalFunctions
|
||||||
/ InFunction
|
/ InFunction
|
||||||
/ AggregateFunctions
|
/ AggregateFunctions
|
||||||
/ MathFunctions
|
/ MathFunctions
|
||||||
@@ -489,6 +562,8 @@ ArrayFunctions <- ArrayConcatExpression
|
|||||||
/ SetIntersectExpression
|
/ SetIntersectExpression
|
||||||
/ SetUnionExpression
|
/ SetUnionExpression
|
||||||
|
|
||||||
|
ConditionalFunctions <- IifExpression
|
||||||
|
|
||||||
MathFunctions <- MathAbsExpression
|
MathFunctions <- MathAbsExpression
|
||||||
/ MathAcosExpression
|
/ MathAcosExpression
|
||||||
/ MathAsinExpression
|
/ MathAsinExpression
|
||||||
@@ -681,6 +756,10 @@ SetUnionExpression <- "SetUnion"i ws "(" ws set1:SelectItem ws "," ws set2:Selec
|
|||||||
return createFunctionCall(parsers.FunctionCallSetUnion, []interface{}{set1, set2})
|
return createFunctionCall(parsers.FunctionCallSetUnion, []interface{}{set1, set2})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IifExpression <- "IIF"i ws "(" ws condition:SelectItem ws "," ws trueValue:SelectItem ws "," ws falseValue:SelectItem ws ")" {
|
||||||
|
return createFunctionCall(parsers.FunctionCallIif, []interface{}{condition, trueValue, falseValue})
|
||||||
|
}
|
||||||
|
|
||||||
MathAbsExpression <- "ABS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAbs, []interface{}{ex}) }
|
MathAbsExpression <- "ABS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAbs, []interface{}{ex}) }
|
||||||
MathAcosExpression <- "ACOS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAcos, []interface{}{ex}) }
|
MathAcosExpression <- "ACOS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAcos, []interface{}{ex}) }
|
||||||
MathAsinExpression <- "ASIN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAsin, []interface{}{ex}) }
|
MathAsinExpression <- "ASIN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAsin, []interface{}{ex}) }
|
||||||
|
|||||||
@@ -178,4 +178,90 @@ func Test_Parse_Select(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse SELECT empty object", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT {} AS obj FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Alias: "obj",
|
||||||
|
Type: parsers.SelectItemTypeObject,
|
||||||
|
SelectItems: []parsers.SelectItem{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse comparison expressions in SELECT", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c["id"] = "123", c["pk"] > 456 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: parsers.ComparisonExpression{
|
||||||
|
Operation: "=",
|
||||||
|
Left: testutils.SelectItem_Path("c", "id"),
|
||||||
|
Right: testutils.SelectItem_Constant_String("123"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: parsers.ComparisonExpression{
|
||||||
|
Operation: ">",
|
||||||
|
Left: testutils.SelectItem_Path("c", "pk"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(456),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse logical expressions in SELECT", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c["id"] = "123" OR c["pk"] > 456, c["isCool"] AND c["hasRizz"] AS isRizzler FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: parsers.LogicalExpression{
|
||||||
|
Operation: parsers.LogicalExpressionTypeOr,
|
||||||
|
Expressions: []interface{}{
|
||||||
|
parsers.ComparisonExpression{
|
||||||
|
Operation: "=",
|
||||||
|
Left: testutils.SelectItem_Path("c", "id"),
|
||||||
|
Right: testutils.SelectItem_Constant_String("123"),
|
||||||
|
},
|
||||||
|
parsers.ComparisonExpression{
|
||||||
|
Operation: ">",
|
||||||
|
Left: testutils.SelectItem_Path("c", "pk"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(456),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Alias: "isRizzler",
|
||||||
|
Value: parsers.LogicalExpression{
|
||||||
|
Operation: parsers.LogicalExpressionTypeAnd,
|
||||||
|
Expressions: []interface{}{
|
||||||
|
testutils.SelectItem_Path("c", "isCool"),
|
||||||
|
testutils.SelectItem_Path("c", "hasRizz"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package memoryexecutor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
|
||||||
|
testutils "github.com/pikami/cosmium/test_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Execute_Arithmetics(t *testing.T) {
|
||||||
|
mockData := []memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": 1, "a": 420},
|
||||||
|
map[string]interface{}{"id": 2, "a": 6.9},
|
||||||
|
map[string]interface{}{"id": 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Should execute simple arithmetics", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Path: []string{"c", "id"},
|
||||||
|
Type: parsers.SelectItemTypeField,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Alias: "result",
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Constant_Float(2.0),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": 1, "result": 426.0},
|
||||||
|
map[string]interface{}{"id": 2, "result": 12.9},
|
||||||
|
map[string]interface{}{"id": 3, "result": nil},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute arithmetics in WHERE clause", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "id"),
|
||||||
|
{
|
||||||
|
Alias: "result",
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
Filters: parsers.ComparisonExpression{
|
||||||
|
Operation: ">",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(500),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": 1, "result": 840.0},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -69,6 +69,28 @@ func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{}
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selectItem.Type == parsers.SelectItemTypeExpression {
|
||||||
|
if typedExpression, ok := selectItem.Value.(parsers.ComparisonExpression); ok {
|
||||||
|
return r.filters_ComparisonExpression(typedExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
if typedExpression, ok := selectItem.Value.(parsers.LogicalExpression); ok {
|
||||||
|
return r.filters_LogicalExpression(typedExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.ErrorLn("parsers.SelectItem has incorrect Value type (expected parsers.ComparisonExpression)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectItem.Type == parsers.SelectItemTypeBinaryExpression {
|
||||||
|
if typedSelectItem, ok := selectItem.Value.(parsers.BinaryExpression); ok {
|
||||||
|
return r.selectItem_SelectItemTypeBinaryExpression(typedSelectItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.ErrorLn("parsers.SelectItem has incorrect Value type (expected parsers.BinaryExpression)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return r.selectItem_SelectItemTypeField(selectItem)
|
return r.selectItem_SelectItemTypeField(selectItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +231,9 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
|
|||||||
case parsers.FunctionCallSetUnion:
|
case parsers.FunctionCallSetUnion:
|
||||||
return r.set_Union(functionCall.Arguments)
|
return r.set_Union(functionCall.Arguments)
|
||||||
|
|
||||||
|
case parsers.FunctionCallIif:
|
||||||
|
return r.misc_Iif(functionCall.Arguments)
|
||||||
|
|
||||||
case parsers.FunctionCallMathAbs:
|
case parsers.FunctionCallMathAbs:
|
||||||
return r.math_Abs(functionCall.Arguments)
|
return r.math_Abs(functionCall.Arguments)
|
||||||
case parsers.FunctionCallMathAcos:
|
case parsers.FunctionCallMathAcos:
|
||||||
@@ -301,6 +326,45 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r rowContext) selectItem_SelectItemTypeBinaryExpression(binaryExpression parsers.BinaryExpression) interface{} {
|
||||||
|
if binaryExpression.Left == nil || binaryExpression.Right == nil {
|
||||||
|
logger.Debug("parsers.BinaryExpression has nil Left or Right value")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leftValue := r.resolveSelectItem(binaryExpression.Left.(parsers.SelectItem))
|
||||||
|
rightValue := r.resolveSelectItem(binaryExpression.Right.(parsers.SelectItem))
|
||||||
|
|
||||||
|
if leftValue == nil || rightValue == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leftNumber, leftIsNumber := numToFloat64(leftValue)
|
||||||
|
rightNumber, rightIsNumber := numToFloat64(rightValue)
|
||||||
|
|
||||||
|
if !leftIsNumber || !rightIsNumber {
|
||||||
|
logger.Debug("Binary expression operands are not numbers, returning nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch binaryExpression.Operation {
|
||||||
|
case "+":
|
||||||
|
return leftNumber + rightNumber
|
||||||
|
case "-":
|
||||||
|
return leftNumber - rightNumber
|
||||||
|
case "*":
|
||||||
|
return leftNumber * rightNumber
|
||||||
|
case "/":
|
||||||
|
if rightNumber == 0 {
|
||||||
|
logger.Debug("Division by zero in binary expression")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return leftNumber / rightNumber
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem) interface{} {
|
func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem) interface{} {
|
||||||
value := r.tables[selectItem.Path[0]]
|
value := r.tables[selectItem.Path[0]]
|
||||||
|
|
||||||
@@ -336,6 +400,7 @@ func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
func compareValues(val1, val2 interface{}) int {
|
func compareValues(val1, val2 interface{}) int {
|
||||||
|
// Handle nil values
|
||||||
if val1 == nil && val2 == nil {
|
if val1 == nil && val2 == nil {
|
||||||
return 0
|
return 0
|
||||||
} else if val1 == nil {
|
} else if val1 == nil {
|
||||||
@@ -344,27 +409,24 @@ func compareValues(val1, val2 interface{}) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle number values
|
||||||
|
val1Number, val1IsNumber := numToFloat64(val1)
|
||||||
|
val2Number, val2IsNumber := numToFloat64(val2)
|
||||||
|
if val1IsNumber && val2IsNumber {
|
||||||
|
if val1Number < val2Number {
|
||||||
|
return -1
|
||||||
|
} else if val1Number > val2Number {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different types
|
||||||
if reflect.TypeOf(val1) != reflect.TypeOf(val2) {
|
if reflect.TypeOf(val1) != reflect.TypeOf(val2) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
switch val1 := val1.(type) {
|
switch val1 := val1.(type) {
|
||||||
case int:
|
|
||||||
val2 := val2.(int)
|
|
||||||
if val1 < val2 {
|
|
||||||
return -1
|
|
||||||
} else if val1 > val2 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case float64:
|
|
||||||
val2 := val2.(float64)
|
|
||||||
if val1 < val2 {
|
|
||||||
return -1
|
|
||||||
} else if val1 > val2 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case string:
|
case string:
|
||||||
val2 := val2.(string)
|
val2 := val2.(string)
|
||||||
return strings.Compare(val1, val2)
|
return strings.Compare(val1, val2)
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package memoryexecutor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
|
||||||
|
testutils "github.com/pikami/cosmium/test_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Execute_Expressions(t *testing.T) {
|
||||||
|
mockData := []memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "123", "age": 10, "isCool": true},
|
||||||
|
map[string]interface{}{"id": "456", "age": 20, "isCool": false},
|
||||||
|
map[string]interface{}{"id": "789", "age": 30, "isCool": true},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Should execute comparison expressions in SELECT", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Path: []string{"c", "id"},
|
||||||
|
Type: parsers.SelectItemTypeField,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Alias: "isAdult",
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: parsers.ComparisonExpression{
|
||||||
|
Operation: ">=",
|
||||||
|
Left: testutils.SelectItem_Path("c", "age"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(18),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Alias: "isNotCool",
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: parsers.ComparisonExpression{
|
||||||
|
Operation: "!=",
|
||||||
|
Left: testutils.SelectItem_Path("c", "isCool"),
|
||||||
|
Right: testutils.SelectItem_Constant_Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "123", "isAdult": false, "isNotCool": false},
|
||||||
|
map[string]interface{}{"id": "456", "isAdult": true, "isNotCool": true},
|
||||||
|
map[string]interface{}{"id": "789", "isAdult": true, "isNotCool": false},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute logical expressions in SELECT", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Path: []string{"c", "id"},
|
||||||
|
Type: parsers.SelectItemTypeField,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Alias: "isCoolAndAdult",
|
||||||
|
Type: parsers.SelectItemTypeExpression,
|
||||||
|
Value: parsers.LogicalExpression{
|
||||||
|
Operation: parsers.LogicalExpressionTypeAnd,
|
||||||
|
Expressions: []interface{}{
|
||||||
|
testutils.SelectItem_Path("c", "isCool"),
|
||||||
|
parsers.ComparisonExpression{
|
||||||
|
Operation: ">=",
|
||||||
|
Left: testutils.SelectItem_Path("c", "age"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(18),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "123", "isCoolAndAdult": false},
|
||||||
|
map[string]interface{}{"id": "456", "isCoolAndAdult": false},
|
||||||
|
map[string]interface{}{"id": "789", "isCoolAndAdult": true},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -605,10 +605,30 @@ func numToInt(ex interface{}) (int, bool) {
|
|||||||
|
|
||||||
func numToFloat64(num interface{}) (float64, bool) {
|
func numToFloat64(num interface{}) (float64, bool) {
|
||||||
switch val := num.(type) {
|
switch val := num.(type) {
|
||||||
case float64:
|
|
||||||
return val, true
|
|
||||||
case int:
|
case int:
|
||||||
return float64(val), true
|
return float64(val), true
|
||||||
|
case int8:
|
||||||
|
return float64(val), true
|
||||||
|
case int16:
|
||||||
|
return float64(val), true
|
||||||
|
case int32:
|
||||||
|
return float64(val), true
|
||||||
|
case int64:
|
||||||
|
return float64(val), true
|
||||||
|
case uint:
|
||||||
|
return float64(val), true
|
||||||
|
case uint8:
|
||||||
|
return float64(val), true
|
||||||
|
case uint16:
|
||||||
|
return float64(val), true
|
||||||
|
case uint32:
|
||||||
|
return float64(val), true
|
||||||
|
case uint64:
|
||||||
|
return float64(val), true
|
||||||
|
case float32:
|
||||||
|
return float64(val), true
|
||||||
|
case float64:
|
||||||
|
return val, true
|
||||||
default:
|
default:
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,16 @@ func (r rowContext) misc_In(arguments []interface{}) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r rowContext) misc_Iif(arguments []interface{}) interface{} {
|
||||||
|
if len(arguments) != 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
|
||||||
|
if condition != nil && condition == true {
|
||||||
|
return r.resolveSelectItem(arguments[1].(parsers.SelectItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.resolveSelectItem(arguments[2].(parsers.SelectItem))
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,4 +210,35 @@ func Test_Execute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute function IIF()", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "id"),
|
||||||
|
{
|
||||||
|
Alias: "coolness",
|
||||||
|
Type: parsers.SelectItemTypeFunctionCall,
|
||||||
|
Value: parsers.FunctionCall{
|
||||||
|
Type: parsers.FunctionCallIif,
|
||||||
|
Arguments: []interface{}{
|
||||||
|
testutils.SelectItem_Path("c", "isCool"),
|
||||||
|
testutils.SelectItem_Constant_String("real cool"),
|
||||||
|
testutils.SelectItem_Constant_String("not cool"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "12345", "coolness": "not cool"},
|
||||||
|
map[string]interface{}{"id": "67890", "coolness": "real cool"},
|
||||||
|
map[string]interface{}{"id": "456", "coolness": "real cool"},
|
||||||
|
map[string]interface{}{"id": "123", "coolness": "real cool"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,4 +205,27 @@ func Test_Execute_Select(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute SELECT empty object", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Alias: "obj",
|
||||||
|
Type: parsers.SelectItemTypeObject,
|
||||||
|
SelectItems: []parsers.SelectItem{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"obj": map[string]interface{}{}},
|
||||||
|
map[string]interface{}{"obj": map[string]interface{}{}},
|
||||||
|
map[string]interface{}{"obj": map[string]interface{}{}},
|
||||||
|
map[string]interface{}{"obj": map[string]interface{}{}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user