mirror of
https://github.com/pikami/cosmium.git
synced 2025-06-07 16:10:23 +01:00
Compare commits
9 Commits
v0.1.21-rc
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
fba9b3df5f | ||
|
b743e23ff9 | ||
|
11851297f5 | ||
|
560ea5296d | ||
|
e20a6ca7cd | ||
|
7e0c10479b | ||
|
30195fae96 | ||
|
598f2837af | ||
|
28e3c0c3d8 |
13
README.md
13
README.md
@ -86,6 +86,7 @@ To disable SSL and run Cosmium on HTTP instead, you can use the `-DisableTls` fl
|
||||
- **-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)
|
||||
- **-LogLevel**: Sets the logging level (one of: debug, info, error, silent) (default info)
|
||||
- **-DataStore**: Allows selecting [storage backend](#data-storage-backends) (default "json")
|
||||
|
||||
These arguments allow you to configure various aspects of Cosmium's behavior according to your requirements.
|
||||
|
||||
@ -99,6 +100,18 @@ All mentioned arguments can also be set using environment variables:
|
||||
- **COSMIUM_PORT** for `-Port`
|
||||
- **COSMIUM_LOGLEVEL** for `-LogLevel`
|
||||
|
||||
### Data Storage Backends
|
||||
|
||||
Cosmium supports multiple storage backends for saving, loading, and managing data at runtime.
|
||||
|
||||
| Backend | Storage Location | Write Behavior | Memory Usage | Supports Initial JSON Load |
|
||||
|----------|--------------------------|--------------------------|----------------------|----------------------------|
|
||||
| `json` (default) | JSON file on disk 📄 | On application exit ⏳ | 🛑 More than Badger | ✅ Yes |
|
||||
| `badger` | BadgerDB database on disk ⚡ | Immediately on write 🚀 | ✅ Less than JSON | ❌ No |
|
||||
|
||||
|
||||
The `badger` backend is generally recommended as it uses less memory and writes data to disk immediately. However, if you need to load initial data from a JSON file, use the `json` backend.
|
||||
|
||||
# License
|
||||
|
||||
This project is [MIT licensed](./LICENSE).
|
||||
|
@ -16,7 +16,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
DataStoreMap = "map"
|
||||
DataStoreJson = "json"
|
||||
DataStoreBadger = "badger"
|
||||
)
|
||||
|
||||
@ -33,8 +33,8 @@ func ParseFlags() ServerConfig {
|
||||
persistDataPath := flag.String("Persist", "", "Saves data to given path on application exit")
|
||||
logLevel := NewEnumValue("info", []string{"debug", "info", "error", "silent"})
|
||||
flag.Var(logLevel, "LogLevel", fmt.Sprintf("Sets the logging level %s", logLevel.AllowedValuesList()))
|
||||
dataStore := NewEnumValue("map", []string{DataStoreMap, DataStoreBadger})
|
||||
flag.Var(dataStore, "DataStore", fmt.Sprintf("Sets the data store %s, (badger is currently in the experimental phase)", dataStore.AllowedValuesList()))
|
||||
dataStore := NewEnumValue("json", []string{DataStoreJson, DataStoreBadger})
|
||||
flag.Var(dataStore, "DataStore", fmt.Sprintf("Sets the data store %s", dataStore.AllowedValuesList()))
|
||||
|
||||
flag.Parse()
|
||||
setFlagsFromEnvironment()
|
||||
@ -77,10 +77,15 @@ func (c *ServerConfig) PopulateCalculatedFields() {
|
||||
logger.SetLogLevel(logger.LogLevelInfo)
|
||||
}
|
||||
|
||||
if c.PersistDataFilePath != "" {
|
||||
fileInfo, _ := os.Stat(c.PersistDataFilePath)
|
||||
if c.DataStore == DataStoreMap && fileInfo.IsDir() {
|
||||
logger.ErrorLn("--Persist cannot be a directory when using default data store")
|
||||
fileInfo, err := os.Stat(c.PersistDataFilePath)
|
||||
if c.PersistDataFilePath != "" && !os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
logger.ErrorLn("Failed to get file info for persist path:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if c.DataStore == DataStoreJson && fileInfo.IsDir() {
|
||||
logger.ErrorLn("--Persist cannot be a directory when using json data store")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
)
|
||||
|
||||
@ -24,7 +25,7 @@ func (h *Handlers) GetAllCollections(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) GetCollection(c *gin.Context) {
|
||||
@ -38,11 +39,11 @@ func (h *Handlers) GetCollection(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DeleteCollection(c *gin.Context) {
|
||||
@ -56,11 +57,11 @@ func (h *Handlers) DeleteCollection(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) CreateCollection(c *gin.Context) {
|
||||
@ -73,13 +74,13 @@ func (h *Handlers) CreateCollection(c *gin.Context) {
|
||||
}
|
||||
|
||||
if newCollection.ID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": "BadRequest"})
|
||||
c.JSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdCollection, status := h.dataStore.CreateCollection(databaseId, newCollection)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -88,5 +89,5 @@ func (h *Handlers) CreateCollection(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
)
|
||||
|
||||
@ -20,7 +21,7 @@ func (h *Handlers) GetAllDatabases(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) GetDatabase(c *gin.Context) {
|
||||
@ -33,11 +34,11 @@ func (h *Handlers) GetDatabase(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DeleteDatabase(c *gin.Context) {
|
||||
@ -50,11 +51,11 @@ func (h *Handlers) DeleteDatabase(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) CreateDatabase(c *gin.Context) {
|
||||
@ -66,13 +67,13 @@ func (h *Handlers) CreateDatabase(c *gin.Context) {
|
||||
}
|
||||
|
||||
if newDatabase.ID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": "BadRequest"})
|
||||
c.JSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdDatabase, status := h.dataStore.CreateDatabase(newDatabase)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -81,5 +82,5 @@ func (h *Handlers) CreateDatabase(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func (h *Handlers) GetAllDocuments(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) GetDocument(c *gin.Context) {
|
||||
@ -50,11 +50,11 @@ func (h *Handlers) GetDocument(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DeleteDocument(c *gin.Context) {
|
||||
@ -69,11 +69,11 @@ func (h *Handlers) DeleteDocument(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, requestBody)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ func (h *Handlers) ReplaceDocument(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
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)
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -166,13 +166,13 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
|
||||
|
||||
status = h.dataStore.DeleteDocument(databaseId, collectionId, documentId)
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, modifiedDocument)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@ func (h *Handlers) PatchDocument(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DocumentsPost(c *gin.Context) {
|
||||
@ -208,7 +208,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
|
||||
}
|
||||
|
||||
if requestBody["id"] == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": "BadRequest"})
|
||||
c.JSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
|
||||
|
||||
createdDocument, status := h.dataStore.CreateDocument(databaseId, collectionId, requestBody)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ func (h *Handlers) DocumentsPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func parametersToMap(pairs []interface{}) map[string]interface{} {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
"github.com/pikami/cosmium/internal/resourceid"
|
||||
)
|
||||
@ -42,9 +43,9 @@ func (h *Handlers) GetPartitionKeyRanges(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
)
|
||||
|
||||
@ -20,7 +21,7 @@ func (h *Handlers) GetAllStoredProcedures(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) GetStoredProcedure(c *gin.Context) {
|
||||
@ -36,11 +37,11 @@ func (h *Handlers) GetStoredProcedure(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DeleteStoredProcedure(c *gin.Context) {
|
||||
@ -55,11 +56,11 @@ func (h *Handlers) DeleteStoredProcedure(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
||||
@ -69,19 +70,19 @@ func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
||||
|
||||
var sp datastore.StoredProcedure
|
||||
if err := c.BindJSON(&sp); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
||||
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
status := h.dataStore.DeleteStoredProcedure(databaseId, collectionId, spId)
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdSP, status := h.dataStore.CreateStoredProcedure(databaseId, collectionId, sp)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -90,7 +91,7 @@ func (h *Handlers) ReplaceStoredProcedure(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
||||
@ -99,13 +100,13 @@ func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
||||
|
||||
var sp datastore.StoredProcedure
|
||||
if err := c.BindJSON(&sp); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
||||
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdSP, status := h.dataStore.CreateStoredProcedure(databaseId, collectionId, sp)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -114,5 +115,5 @@ func (h *Handlers) CreateStoredProcedure(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
)
|
||||
|
||||
@ -20,7 +21,7 @@ func (h *Handlers) GetAllTriggers(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) GetTrigger(c *gin.Context) {
|
||||
@ -36,11 +37,11 @@ func (h *Handlers) GetTrigger(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DeleteTrigger(c *gin.Context) {
|
||||
@ -55,11 +56,11 @@ func (h *Handlers) DeleteTrigger(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
||||
@ -69,19 +70,19 @@ func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
||||
|
||||
var trigger datastore.Trigger
|
||||
if err := c.BindJSON(&trigger); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
||||
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
status := h.dataStore.DeleteTrigger(databaseId, collectionId, triggerId)
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdTrigger, status := h.dataStore.CreateTrigger(databaseId, collectionId, trigger)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -90,7 +91,7 @@ func (h *Handlers) ReplaceTrigger(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) CreateTrigger(c *gin.Context) {
|
||||
@ -99,13 +100,13 @@ func (h *Handlers) CreateTrigger(c *gin.Context) {
|
||||
|
||||
var trigger datastore.Trigger
|
||||
if err := c.BindJSON(&trigger); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
||||
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdTrigger, status := h.dataStore.CreateTrigger(databaseId, collectionId, trigger)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -114,5 +115,5 @@ func (h *Handlers) CreateTrigger(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
)
|
||||
|
||||
@ -20,7 +21,7 @@ func (h *Handlers) GetAllUserDefinedFunctions(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) GetUserDefinedFunction(c *gin.Context) {
|
||||
@ -36,11 +37,11 @@ func (h *Handlers) GetUserDefinedFunction(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) DeleteUserDefinedFunction(c *gin.Context) {
|
||||
@ -55,11 +56,11 @@ func (h *Handlers) DeleteUserDefinedFunction(c *gin.Context) {
|
||||
}
|
||||
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
||||
@ -69,19 +70,19 @@ func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
||||
|
||||
var udf datastore.UserDefinedFunction
|
||||
if err := c.BindJSON(&udf); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
||||
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
status := h.dataStore.DeleteUserDefinedFunction(databaseId, collectionId, udfId)
|
||||
if status == datastore.StatusNotFound {
|
||||
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "NotFound"})
|
||||
c.IndentedJSON(http.StatusNotFound, constants.NotFoundResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdUdf, status := h.dataStore.CreateUserDefinedFunction(databaseId, collectionId, udf)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -90,7 +91,7 @@ func (h *Handlers) ReplaceUserDefinedFunction(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"})
|
||||
c.IndentedJSON(http.StatusInternalServerError, constants.UnknownErrorResponse)
|
||||
}
|
||||
|
||||
func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
||||
@ -99,13 +100,13 @@ func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
||||
|
||||
var udf datastore.UserDefinedFunction
|
||||
if err := c.BindJSON(&udf); err != nil {
|
||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Invalid body"})
|
||||
c.IndentedJSON(http.StatusBadRequest, constants.BadRequestResponse)
|
||||
return
|
||||
}
|
||||
|
||||
createdUdf, status := h.dataStore.CreateUserDefinedFunction(databaseId, collectionId, udf)
|
||||
if status == datastore.Conflict {
|
||||
c.IndentedJSON(http.StatusConflict, gin.H{"message": "Conflict"})
|
||||
c.IndentedJSON(http.StatusConflict, constants.ConflictResponse)
|
||||
return
|
||||
}
|
||||
|
||||
@ -114,5 +115,5 @@ func (h *Handlers) CreateUserDefinedFunction(c *gin.Context) {
|
||||
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) {
|
||||
ts.DataStore.DeleteDatabase(testDatabaseName)
|
||||
client, err := azcosmos.NewClientFromConnectionString(
|
||||
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, config.DefaultAccountKey),
|
||||
formatConnectionString(ts.URL, config.DefaultAccountKey),
|
||||
&azcosmos.ClientOptions{},
|
||||
)
|
||||
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) {
|
||||
ts.DataStore.DeleteDatabase(testDatabaseName)
|
||||
client, err := azcosmos.NewClientFromConnectionString(
|
||||
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, "AAAA"),
|
||||
formatConnectionString(ts.URL, "AAAA"),
|
||||
&azcosmos.ClientOptions{},
|
||||
)
|
||||
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) {
|
||||
ts.DataStore.DeleteDatabase(testDatabaseName)
|
||||
client, err := azcosmos.NewClientFromConnectionString(
|
||||
fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", ts.URL, "AAAA"),
|
||||
formatConnectionString(ts.URL, "AAAA"),
|
||||
&azcosmos.ClientOptions{},
|
||||
)
|
||||
assert.Nil(t, err)
|
||||
@ -85,3 +85,7 @@ func Test_Authentication_Disabled(t *testing.T) {
|
||||
assert.Equal(t, createResponse.DatabaseProperties.ID, testDatabaseName)
|
||||
})
|
||||
}
|
||||
|
||||
func formatConnectionString(endpoint, key string) string {
|
||||
return fmt.Sprintf("AccountEndpoint=%s;AccountKey=%s", endpoint, key)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_Collections(t *testing.T) {
|
||||
presets := []testPreset{PresetMapStore, PresetBadgerStore}
|
||||
presets := []testPreset{PresetJsonStore, PresetBadgerStore}
|
||||
|
||||
setUp := func(ts *TestServer, client *azcosmos.Client) *azcosmos.DatabaseClient {
|
||||
ts.DataStore.CreateDatabase(datastore.Database{ID: testDatabaseName})
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
badgerdatastore "github.com/pikami/cosmium/internal/datastore/badger_datastore"
|
||||
mapdatastore "github.com/pikami/cosmium/internal/datastore/map_datastore"
|
||||
jsondatastore "github.com/pikami/cosmium/internal/datastore/json_datastore"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -26,7 +26,7 @@ func getDefaultTestServerConfig() *config.ServerConfig {
|
||||
AccountKey: config.DefaultAccountKey,
|
||||
ExplorerPath: "/tmp/nothing",
|
||||
ExplorerBaseUrlLocation: config.ExplorerBaseUrlLocation,
|
||||
DataStore: "map",
|
||||
DataStore: "json",
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,9 +34,9 @@ func runTestServerCustomConfig(configuration *config.ServerConfig) *TestServer {
|
||||
var dataStore datastore.DataStore
|
||||
switch configuration.DataStore {
|
||||
case config.DataStoreBadger:
|
||||
dataStore = badgerdatastore.NewBadgerDataStore()
|
||||
dataStore = badgerdatastore.NewBadgerDataStore(badgerdatastore.BadgerDataStoreOptions{})
|
||||
default:
|
||||
dataStore = mapdatastore.NewMapDataStore(mapdatastore.MapDataStoreOptions{})
|
||||
dataStore = jsondatastore.NewJsonDataStore(jsondatastore.JsonDataStoreOptions{})
|
||||
}
|
||||
|
||||
api := api.NewApiServer(dataStore, configuration)
|
||||
@ -71,7 +71,7 @@ type testFunc func(t *testing.T, ts *TestServer, cosmosClient *azcosmos.Client)
|
||||
type testPreset string
|
||||
|
||||
const (
|
||||
PresetMapStore testPreset = "MapDS"
|
||||
PresetJsonStore testPreset = "JsonDS"
|
||||
PresetBadgerStore testPreset = "BadgerDS"
|
||||
)
|
||||
|
||||
@ -84,8 +84,8 @@ func runTestsWithPreset(t *testing.T, name string, testPreset testPreset, f test
|
||||
switch testPreset {
|
||||
case PresetBadgerStore:
|
||||
serverConfig.DataStore = config.DataStoreBadger
|
||||
case PresetMapStore:
|
||||
serverConfig.DataStore = config.DataStoreMap
|
||||
case PresetJsonStore:
|
||||
serverConfig.DataStore = config.DataStoreJson
|
||||
}
|
||||
|
||||
ts := runTestServerCustomConfig(serverConfig)
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_Databases(t *testing.T) {
|
||||
presets := []testPreset{PresetMapStore, PresetBadgerStore}
|
||||
presets := []testPreset{PresetJsonStore, PresetBadgerStore}
|
||||
|
||||
runTestsWithPresets(t, "Database Create", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
|
||||
t.Run("Should create database", func(t *testing.T) {
|
||||
|
@ -81,7 +81,7 @@ func documents_InitializeDb(t *testing.T, ts *TestServer) *azcosmos.ContainerCli
|
||||
}
|
||||
|
||||
func Test_Documents(t *testing.T) {
|
||||
presets := []testPreset{PresetMapStore, PresetBadgerStore}
|
||||
presets := []testPreset{PresetJsonStore, PresetBadgerStore}
|
||||
|
||||
runTestsWithPresets(t, "Test_Documents", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) {
|
||||
collectionClient := documents_InitializeDb(t, ts)
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
badgerdatastore "github.com/pikami/cosmium/internal/datastore/badger_datastore"
|
||||
mapdatastore "github.com/pikami/cosmium/internal/datastore/map_datastore"
|
||||
jsondatastore "github.com/pikami/cosmium/internal/datastore/json_datastore"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
)
|
||||
|
||||
@ -24,7 +24,7 @@ func main() {
|
||||
})
|
||||
logger.InfoLn("Using Badger data store")
|
||||
default:
|
||||
dataStore = mapdatastore.NewMapDataStore(mapdatastore.MapDataStoreOptions{
|
||||
dataStore = jsondatastore.NewJsonDataStore(jsondatastore.JsonDataStoreOptions{
|
||||
InitialDataFilePath: configuration.InitialDataFilePath,
|
||||
PersistDataFilePath: configuration.PersistDataFilePath,
|
||||
})
|
||||
|
@ -79,7 +79,7 @@ Cosmium strives to support the core features of Cosmos DB, including:
|
||||
|
||||
| Function | Implemented |
|
||||
| -------- | ----------- |
|
||||
| IIF | No |
|
||||
| IIF | Yes |
|
||||
|
||||
### Date and time Functions
|
||||
|
||||
|
36
go.mod
36
go.mod
@ -3,33 +3,34 @@ module github.com/pikami/cosmium
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.3.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.0
|
||||
github.com/cosmiumdev/json-patch/v5 v5.9.11
|
||||
github.com/dgraph-io/badger/v4 v4.6.0
|
||||
github.com/dgraph-io/badger/v4 v4.7.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/bytedance/sonic v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
@ -39,22 +40,21 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
golang.org/x/arch v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
38
go.sum
38
go.sum
@ -2,16 +2,26 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.3.0 h1:RGcdpSElvcXCwxydI0xzOBu1Gvp88OoiTGfbtO/z1m0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.3.0/go.mod h1:YwUyrNUtcZcibA99JcfCP6UUp95VVQKO2MJfBzgJDwA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.0 h1:TSaH6Lj0m8bDr4vX1+LC1KLQTnLzZb3tOxrx/PLqw+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.0/go.mod h1:Krtog/7tz27z75TwM5cIS8bxEH4dcBUezcq+kGVeZEo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
|
||||
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
@ -27,16 +37,25 @@ 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/dgraph-io/badger/v4 v4.6.0 h1:acOwfOOZ4p1dPRnYzvkVm7rUk2Y21TgPVepCy5dJdFQ=
|
||||
github.com/dgraph-io/badger/v4 v4.6.0/go.mod h1:KSJ5VTuZNC3Sd+YhvVjk2nYua9UZnnTr/SkXvdtiPgI=
|
||||
github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y=
|
||||
github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA=
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I=
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -52,10 +71,13 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@ -88,6 +110,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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@ -126,19 +150,33 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU=
|
||||
golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
@ -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
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
)
|
||||
|
||||
type BadgerDataStore struct {
|
||||
db *badger.DB
|
||||
db *badger.DB
|
||||
gcTicker *time.Ticker
|
||||
}
|
||||
|
||||
type BadgerDataStoreOptions struct {
|
||||
@ -15,6 +18,7 @@ type BadgerDataStoreOptions struct {
|
||||
|
||||
func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
|
||||
badgerOpts := badger.DefaultOptions(options.PersistDataFilePath)
|
||||
badgerOpts = badgerOpts.WithLogger(newBadgerLogger())
|
||||
if options.PersistDataFilePath == "" {
|
||||
badgerOpts = badgerOpts.WithInMemory(true)
|
||||
}
|
||||
@ -24,12 +28,24 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &BadgerDataStore{
|
||||
db: db,
|
||||
gcTicker := time.NewTicker(5 * time.Minute)
|
||||
|
||||
ds := &BadgerDataStore{
|
||||
db: db,
|
||||
gcTicker: gcTicker,
|
||||
}
|
||||
|
||||
go ds.runGarbageCollector()
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
func (r *BadgerDataStore) Close() {
|
||||
if r.gcTicker != nil {
|
||||
r.gcTicker.Stop()
|
||||
r.gcTicker = nil
|
||||
}
|
||||
|
||||
r.db.Close()
|
||||
r.db = nil
|
||||
}
|
||||
@ -38,3 +54,13 @@ func (r *BadgerDataStore) DumpToJson() (string, error) {
|
||||
logger.ErrorLn("Badger datastore does not support state export currently.")
|
||||
return "{}", nil
|
||||
}
|
||||
|
||||
func (r *BadgerDataStore) runGarbageCollector() {
|
||||
for range r.gcTicker.C {
|
||||
again:
|
||||
err := r.db.RunValueLogGC(0.7)
|
||||
if err == nil {
|
||||
goto again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
internal/datastore/badger_datastore/badger_logger.go
Normal file
28
internal/datastore/badger_datastore/badger_logger.go
Normal file
@ -0,0 +1,28 @@
|
||||
package badgerdatastore
|
||||
|
||||
import (
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
)
|
||||
|
||||
type badgerLogger struct{}
|
||||
|
||||
func newBadgerLogger() badger.Logger {
|
||||
return &badgerLogger{}
|
||||
}
|
||||
|
||||
func (l *badgerLogger) Errorf(format string, v ...interface{}) {
|
||||
logger.Errorf(format, v...)
|
||||
}
|
||||
|
||||
func (l *badgerLogger) Warningf(format string, v ...interface{}) {
|
||||
logger.Infof(format, v...)
|
||||
}
|
||||
|
||||
func (l *badgerLogger) Infof(format string, v ...interface{}) {
|
||||
logger.Infof(format, v...)
|
||||
}
|
||||
|
||||
func (l *badgerLogger) Debugf(format string, v ...interface{}) {
|
||||
logger.Debugf(format, v...)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import "github.com/pikami/cosmium/internal/datastore"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (r *MapDataStore) GetAllCollections(databaseId string) ([]datastore.Collection, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetAllCollections(databaseId string) ([]datastore.Collection, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -22,7 +22,7 @@ func (r *MapDataStore) GetAllCollections(databaseId string) ([]datastore.Collect
|
||||
return maps.Values(r.storeState.Collections[databaseId]), datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetCollection(databaseId string, collectionId string) (datastore.Collection, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetCollection(databaseId string, collectionId string) (datastore.Collection, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -37,7 +37,7 @@ func (r *MapDataStore) GetCollection(databaseId string, collectionId string) (da
|
||||
return r.storeState.Collections[databaseId][collectionId], datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DeleteCollection(databaseId string, collectionId string) datastore.DataStoreStatus {
|
||||
func (r *JsonDataStore) DeleteCollection(databaseId string, collectionId string) datastore.DataStoreStatus {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -58,7 +58,7 @@ func (r *MapDataStore) DeleteCollection(databaseId string, collectionId string)
|
||||
return datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) CreateCollection(databaseId string, newCollection datastore.Collection) (datastore.Collection, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) CreateCollection(databaseId string, newCollection datastore.Collection) (datastore.Collection, datastore.DataStoreStatus) {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -10,14 +10,14 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (r *MapDataStore) GetAllDatabases() ([]datastore.Database, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetAllDatabases() ([]datastore.Database, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
return maps.Values(r.storeState.Databases), datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetDatabase(id string) (datastore.Database, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetDatabase(id string) (datastore.Database, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -28,7 +28,7 @@ func (r *MapDataStore) GetDatabase(id string) (datastore.Database, datastore.Dat
|
||||
return datastore.Database{}, datastore.StatusNotFound
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DeleteDatabase(id string) datastore.DataStoreStatus {
|
||||
func (r *JsonDataStore) DeleteDatabase(id string) datastore.DataStoreStatus {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -46,7 +46,7 @@ func (r *MapDataStore) DeleteDatabase(id string) datastore.DataStoreStatus {
|
||||
return datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) CreateDatabase(newDatabase datastore.Database) (datastore.Database, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) CreateDatabase(newDatabase datastore.Database) (datastore.Database, datastore.DataStoreStatus) {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (r *MapDataStore) GetAllDocuments(databaseId string, collectionId string) ([]datastore.Document, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetAllDocuments(databaseId string, collectionId string) ([]datastore.Document, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -25,7 +25,7 @@ func (r *MapDataStore) GetAllDocuments(databaseId string, collectionId string) (
|
||||
return maps.Values(r.storeState.Documents[databaseId][collectionId]), datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetDocument(databaseId string, collectionId string, documentId string) (datastore.Document, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetDocument(databaseId string, collectionId string, documentId string) (datastore.Document, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -44,7 +44,7 @@ func (r *MapDataStore) GetDocument(databaseId string, collectionId string, docum
|
||||
return r.storeState.Documents[databaseId][collectionId][documentId], datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DeleteDocument(databaseId string, collectionId string, documentId string) datastore.DataStoreStatus {
|
||||
func (r *JsonDataStore) DeleteDocument(databaseId string, collectionId string, documentId string) datastore.DataStoreStatus {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -65,7 +65,7 @@ func (r *MapDataStore) DeleteDocument(databaseId string, collectionId string, do
|
||||
return datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) CreateDocument(databaseId string, collectionId string, document map[string]interface{}) (datastore.Document, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) CreateDocument(databaseId string, collectionId string, document map[string]interface{}) (datastore.Document, datastore.DataStoreStatus) {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -100,7 +100,7 @@ func (r *MapDataStore) CreateDocument(databaseId string, collectionId string, do
|
||||
return document, datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetDocumentIterator(databaseId string, collectionId string) (datastore.DocumentIterator, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetDocumentIterator(databaseId string, collectionId string) (datastore.DocumentIterator, datastore.DataStoreStatus) {
|
||||
documents, status := r.GetAllDocuments(databaseId, collectionId)
|
||||
if status != datastore.StatusOk {
|
||||
return nil, status
|
@ -1,21 +1,21 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import "github.com/pikami/cosmium/internal/datastore"
|
||||
|
||||
type MapDataStore struct {
|
||||
type JsonDataStore struct {
|
||||
storeState State
|
||||
|
||||
initialDataFilePath string
|
||||
persistDataFilePath string
|
||||
}
|
||||
|
||||
type MapDataStoreOptions struct {
|
||||
type JsonDataStoreOptions struct {
|
||||
InitialDataFilePath string
|
||||
PersistDataFilePath string
|
||||
}
|
||||
|
||||
func NewMapDataStore(options MapDataStoreOptions) *MapDataStore {
|
||||
dataStore := &MapDataStore{
|
||||
func NewJsonDataStore(options JsonDataStoreOptions) *JsonDataStore {
|
||||
dataStore := &JsonDataStore{
|
||||
storeState: State{
|
||||
Databases: make(map[string]datastore.Database),
|
||||
Collections: make(map[string]map[string]datastore.Collection),
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// I have no idea what this is tbh
|
||||
func (r *MapDataStore) GetPartitionKeyRanges(databaseId string, collectionId string) ([]datastore.PartitionKeyRange, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetPartitionKeyRanges(databaseId string, collectionId string) ([]datastore.PartitionKeyRange, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -33,7 +33,7 @@ type State struct {
|
||||
UserDefinedFunctions map[string]map[string]map[string]datastore.UserDefinedFunction `json:"udfs"`
|
||||
}
|
||||
|
||||
func (r *MapDataStore) InitializeDataStore() {
|
||||
func (r *JsonDataStore) InitializeDataStore() {
|
||||
if r.initialDataFilePath != "" {
|
||||
r.LoadStateFS(r.initialDataFilePath)
|
||||
return
|
||||
@ -55,7 +55,7 @@ func (r *MapDataStore) InitializeDataStore() {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MapDataStore) LoadStateFS(filePath string) {
|
||||
func (r *JsonDataStore) LoadStateFS(filePath string) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading state JSON file: %v", err)
|
||||
@ -68,7 +68,7 @@ func (r *MapDataStore) LoadStateFS(filePath string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MapDataStore) LoadStateJSON(jsonData string) error {
|
||||
func (r *JsonDataStore) LoadStateJSON(jsonData string) error {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -94,7 +94,7 @@ func (r *MapDataStore) LoadStateJSON(jsonData string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MapDataStore) SaveStateFS(filePath string) {
|
||||
func (r *JsonDataStore) SaveStateFS(filePath string) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -115,7 +115,7 @@ func (r *MapDataStore) SaveStateFS(filePath string) {
|
||||
logger.Infof("User defined functions: %d\n", getLength(r.storeState.UserDefinedFunctions))
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DumpToJson() (string, error) {
|
||||
func (r *JsonDataStore) DumpToJson() (string, error) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -129,7 +129,7 @@ func (r *MapDataStore) DumpToJson() (string, error) {
|
||||
|
||||
}
|
||||
|
||||
func (r *MapDataStore) Close() {
|
||||
func (r *JsonDataStore) Close() {
|
||||
if r.persistDataFilePath != "" {
|
||||
r.SaveStateFS(r.persistDataFilePath)
|
||||
}
|
||||
@ -163,7 +163,7 @@ func getLength(v interface{}) int {
|
||||
return count
|
||||
}
|
||||
|
||||
func (r *MapDataStore) ensureStoreStateNoNullReferences() {
|
||||
func (r *JsonDataStore) ensureStoreStateNoNullReferences() {
|
||||
if r.storeState.Databases == nil {
|
||||
r.storeState.Databases = make(map[string]datastore.Database)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -10,14 +10,14 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (r *MapDataStore) GetAllStoredProcedures(databaseId string, collectionId string) ([]datastore.StoredProcedure, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetAllStoredProcedures(databaseId string, collectionId string) ([]datastore.StoredProcedure, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
return maps.Values(r.storeState.StoredProcedures[databaseId][collectionId]), datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetStoredProcedure(databaseId string, collectionId string, spId string) (datastore.StoredProcedure, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetStoredProcedure(databaseId string, collectionId string, spId string) (datastore.StoredProcedure, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -36,7 +36,7 @@ func (r *MapDataStore) GetStoredProcedure(databaseId string, collectionId string
|
||||
return datastore.StoredProcedure{}, datastore.StatusNotFound
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DeleteStoredProcedure(databaseId string, collectionId string, spId string) datastore.DataStoreStatus {
|
||||
func (r *JsonDataStore) DeleteStoredProcedure(databaseId string, collectionId string, spId string) datastore.DataStoreStatus {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -57,7 +57,7 @@ func (r *MapDataStore) DeleteStoredProcedure(databaseId string, collectionId str
|
||||
return datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) CreateStoredProcedure(databaseId string, collectionId string, sp datastore.StoredProcedure) (datastore.StoredProcedure, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) CreateStoredProcedure(databaseId string, collectionId string, sp datastore.StoredProcedure) (datastore.StoredProcedure, datastore.DataStoreStatus) {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -10,14 +10,14 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (r *MapDataStore) GetAllTriggers(databaseId string, collectionId string) ([]datastore.Trigger, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetAllTriggers(databaseId string, collectionId string) ([]datastore.Trigger, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
return maps.Values(r.storeState.Triggers[databaseId][collectionId]), datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetTrigger(databaseId string, collectionId string, triggerId string) (datastore.Trigger, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetTrigger(databaseId string, collectionId string, triggerId string) (datastore.Trigger, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -36,7 +36,7 @@ func (r *MapDataStore) GetTrigger(databaseId string, collectionId string, trigge
|
||||
return datastore.Trigger{}, datastore.StatusNotFound
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DeleteTrigger(databaseId string, collectionId string, triggerId string) datastore.DataStoreStatus {
|
||||
func (r *JsonDataStore) DeleteTrigger(databaseId string, collectionId string, triggerId string) datastore.DataStoreStatus {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -57,7 +57,7 @@ func (r *MapDataStore) DeleteTrigger(databaseId string, collectionId string, tri
|
||||
return datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) CreateTrigger(databaseId string, collectionId string, trigger datastore.Trigger) (datastore.Trigger, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) CreateTrigger(databaseId string, collectionId string, trigger datastore.Trigger) (datastore.Trigger, datastore.DataStoreStatus) {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
@ -1,4 +1,4 @@
|
||||
package mapdatastore
|
||||
package jsondatastore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -10,14 +10,14 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func (r *MapDataStore) GetAllUserDefinedFunctions(databaseId string, collectionId string) ([]datastore.UserDefinedFunction, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetAllUserDefinedFunctions(databaseId string, collectionId string) ([]datastore.UserDefinedFunction, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
return maps.Values(r.storeState.UserDefinedFunctions[databaseId][collectionId]), datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) GetUserDefinedFunction(databaseId string, collectionId string, udfId string) (datastore.UserDefinedFunction, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) GetUserDefinedFunction(databaseId string, collectionId string, udfId string) (datastore.UserDefinedFunction, datastore.DataStoreStatus) {
|
||||
r.storeState.RLock()
|
||||
defer r.storeState.RUnlock()
|
||||
|
||||
@ -36,7 +36,7 @@ func (r *MapDataStore) GetUserDefinedFunction(databaseId string, collectionId st
|
||||
return datastore.UserDefinedFunction{}, datastore.StatusNotFound
|
||||
}
|
||||
|
||||
func (r *MapDataStore) DeleteUserDefinedFunction(databaseId string, collectionId string, udfId string) datastore.DataStoreStatus {
|
||||
func (r *JsonDataStore) DeleteUserDefinedFunction(databaseId string, collectionId string, udfId string) datastore.DataStoreStatus {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
||||
@ -57,7 +57,7 @@ func (r *MapDataStore) DeleteUserDefinedFunction(databaseId string, collectionId
|
||||
return datastore.StatusOk
|
||||
}
|
||||
|
||||
func (r *MapDataStore) CreateUserDefinedFunction(databaseId string, collectionId string, udf datastore.UserDefinedFunction) (datastore.UserDefinedFunction, datastore.DataStoreStatus) {
|
||||
func (r *JsonDataStore) CreateUserDefinedFunction(databaseId string, collectionId string, udf datastore.UserDefinedFunction) (datastore.UserDefinedFunction, datastore.DataStoreStatus) {
|
||||
r.storeState.Lock()
|
||||
defer r.storeState.Unlock()
|
||||
|
@ -34,6 +34,8 @@ const (
|
||||
SelectItemTypeConstant
|
||||
SelectItemTypeFunctionCall
|
||||
SelectItemTypeSubQuery
|
||||
SelectItemTypeExpression
|
||||
SelectItemTypeBinaryExpression
|
||||
)
|
||||
|
||||
type SelectItem struct {
|
||||
@ -64,6 +66,12 @@ type ComparisonExpression struct {
|
||||
Operation string
|
||||
}
|
||||
|
||||
type BinaryExpression struct {
|
||||
Left interface{}
|
||||
Right interface{}
|
||||
Operation string
|
||||
}
|
||||
|
||||
type ConstantType int
|
||||
|
||||
const (
|
||||
@ -134,6 +142,8 @@ const (
|
||||
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
|
||||
FunctionCallSetUnion FunctionCallType = "SetUnion"
|
||||
|
||||
FunctionCallIif FunctionCallType = "Iif"
|
||||
|
||||
FunctionCallMathAbs FunctionCallType = "MathAbs"
|
||||
FunctionCallMathAcos FunctionCallType = "MathAcos"
|
||||
FunctionCallMathAsin FunctionCallType = "MathAsin"
|
||||
|
366
parsers/nosql/arithmetics_test.go
Normal file
366
parsers/nosql/arithmetics_test.go
Normal file
@ -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")},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,137 +4,137 @@ package nosql
|
||||
import "github.com/pikami/cosmium/parsers"
|
||||
|
||||
func makeSelectStmt(
|
||||
columns, fromClause, joinItems,
|
||||
whereClause interface{}, distinctClause interface{},
|
||||
count interface{}, groupByClause interface{}, orderList interface{},
|
||||
offsetClause interface{},
|
||||
columns, fromClause, joinItems,
|
||||
whereClause interface{}, distinctClause interface{},
|
||||
count interface{}, groupByClause interface{}, orderList interface{},
|
||||
offsetClause interface{},
|
||||
) (parsers.SelectStmt, error) {
|
||||
selectStmt := parsers.SelectStmt{
|
||||
SelectItems: columns.([]parsers.SelectItem),
|
||||
}
|
||||
selectStmt := parsers.SelectStmt{
|
||||
SelectItems: columns.([]parsers.SelectItem),
|
||||
}
|
||||
|
||||
if fromTable, ok := fromClause.(parsers.Table); ok {
|
||||
selectStmt.Table = fromTable
|
||||
}
|
||||
|
||||
if joinItemsArray, ok := joinItems.([]interface{}); ok && len(joinItemsArray) > 0 {
|
||||
selectStmt.JoinItems = make([]parsers.JoinItem, len(joinItemsArray))
|
||||
for i, joinItem := range joinItemsArray {
|
||||
selectStmt.JoinItems[i] = joinItem.(parsers.JoinItem)
|
||||
}
|
||||
}
|
||||
selectStmt.JoinItems = make([]parsers.JoinItem, len(joinItemsArray))
|
||||
for i, joinItem := range joinItemsArray {
|
||||
selectStmt.JoinItems[i] = joinItem.(parsers.JoinItem)
|
||||
}
|
||||
}
|
||||
|
||||
switch v := whereClause.(type) {
|
||||
case parsers.ComparisonExpression, parsers.LogicalExpression, parsers.Constant, parsers.SelectItem:
|
||||
selectStmt.Filters = v
|
||||
}
|
||||
switch v := whereClause.(type) {
|
||||
case parsers.ComparisonExpression, parsers.LogicalExpression, parsers.Constant, parsers.SelectItem:
|
||||
selectStmt.Filters = v
|
||||
}
|
||||
|
||||
if distinctClause != nil {
|
||||
selectStmt.Distinct = true
|
||||
}
|
||||
if distinctClause != nil {
|
||||
selectStmt.Distinct = true
|
||||
}
|
||||
|
||||
if n, ok := count.(int); ok {
|
||||
selectStmt.Count = n
|
||||
}
|
||||
if n, ok := count.(int); ok {
|
||||
selectStmt.Count = n
|
||||
}
|
||||
|
||||
if offsetArr, ok := offsetClause.([]interface{}); ok && len(offsetArr) == 2 {
|
||||
if n, ok := offsetArr[0].(int); ok {
|
||||
selectStmt.Offset = n
|
||||
}
|
||||
if offsetArr, ok := offsetClause.([]interface{}); ok && len(offsetArr) == 2 {
|
||||
if n, ok := offsetArr[0].(int); ok {
|
||||
selectStmt.Offset = n
|
||||
}
|
||||
|
||||
if n, ok := offsetArr[1].(int); ok {
|
||||
selectStmt.Count = n
|
||||
}
|
||||
}
|
||||
if n, ok := offsetArr[1].(int); ok {
|
||||
selectStmt.Count = n
|
||||
}
|
||||
}
|
||||
|
||||
if orderExpressions, ok := orderList.([]parsers.OrderExpression); ok {
|
||||
selectStmt.OrderExpressions = orderExpressions
|
||||
}
|
||||
if orderExpressions, ok := orderList.([]parsers.OrderExpression); ok {
|
||||
selectStmt.OrderExpressions = orderExpressions
|
||||
}
|
||||
|
||||
if groupByClause != nil {
|
||||
selectStmt.GroupBy = groupByClause.([]parsers.SelectItem)
|
||||
}
|
||||
if groupByClause != nil {
|
||||
selectStmt.GroupBy = groupByClause.([]parsers.SelectItem)
|
||||
}
|
||||
|
||||
return selectStmt, nil
|
||||
return selectStmt, nil
|
||||
}
|
||||
|
||||
func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) {
|
||||
joinItem := parsers.JoinItem{}
|
||||
joinItem := parsers.JoinItem{}
|
||||
|
||||
if selectItem, isSelectItem := column.(parsers.SelectItem); isSelectItem {
|
||||
joinItem.SelectItem = selectItem
|
||||
joinItem.Table.Value = selectItem.Alias
|
||||
}
|
||||
if selectItem, isSelectItem := column.(parsers.SelectItem); isSelectItem {
|
||||
joinItem.SelectItem = selectItem
|
||||
joinItem.Table.Value = selectItem.Alias
|
||||
}
|
||||
|
||||
if tableTyped, isTable := table.(parsers.Table); isTable {
|
||||
joinItem.Table = tableTyped
|
||||
}
|
||||
if tableTyped, isTable := table.(parsers.Table); isTable {
|
||||
joinItem.Table = tableTyped
|
||||
}
|
||||
|
||||
return joinItem, nil
|
||||
return joinItem, nil
|
||||
}
|
||||
|
||||
func makeSelectItem(name interface{}, path interface{}, selectItemType parsers.SelectItemType) (parsers.SelectItem, error) {
|
||||
ps := path.([]interface{})
|
||||
ps := path.([]interface{})
|
||||
|
||||
paths := make([]string, 1)
|
||||
paths := make([]string, 1)
|
||||
paths[0] = name.(string)
|
||||
for _, p := range ps {
|
||||
for _, p := range ps {
|
||||
paths = append(paths, p.(string))
|
||||
}
|
||||
}
|
||||
|
||||
return parsers.SelectItem{Path: paths, Type: selectItemType}, nil
|
||||
return parsers.SelectItem{Path: paths, Type: selectItemType}, nil
|
||||
}
|
||||
|
||||
func makeColumnList(column interface{}, other_columns interface{}) ([]parsers.SelectItem, error) {
|
||||
collsAsArray := other_columns.([]interface{})
|
||||
columnList := make([]parsers.SelectItem, len(collsAsArray) + 1)
|
||||
columnList[0] = column.(parsers.SelectItem)
|
||||
columnList := make([]parsers.SelectItem, len(collsAsArray) + 1)
|
||||
columnList[0] = column.(parsers.SelectItem)
|
||||
|
||||
for i, v := range collsAsArray {
|
||||
for i, v := range collsAsArray {
|
||||
if col, ok := v.(parsers.SelectItem); ok {
|
||||
columnList[i+1] = col
|
||||
columnList[i+1] = col
|
||||
}
|
||||
}
|
||||
|
||||
return columnList, nil
|
||||
return columnList, nil
|
||||
}
|
||||
|
||||
func makeSelectArray(columns interface{}) (parsers.SelectItem, error) {
|
||||
return parsers.SelectItem{
|
||||
SelectItems: columns.([]parsers.SelectItem),
|
||||
Type: parsers.SelectItemTypeArray,
|
||||
}, nil
|
||||
return parsers.SelectItem{
|
||||
SelectItems: columns.([]parsers.SelectItem),
|
||||
Type: parsers.SelectItemTypeArray,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeSelectObject(field interface{}, other_fields interface{}) (parsers.SelectItem, error) {
|
||||
fieldsAsArray := other_fields.([]interface{})
|
||||
fieldsList := make([]parsers.SelectItem, len(fieldsAsArray)+1)
|
||||
fieldsList[0] = field.(parsers.SelectItem)
|
||||
fieldsList := make([]parsers.SelectItem, len(fieldsAsArray)+1)
|
||||
fieldsList[0] = field.(parsers.SelectItem)
|
||||
|
||||
for i, v := range fieldsAsArray {
|
||||
if col, ok := v.(parsers.SelectItem); ok {
|
||||
fieldsList[i+1] = col
|
||||
}
|
||||
}
|
||||
for i, v := range fieldsAsArray {
|
||||
if col, ok := v.(parsers.SelectItem); ok {
|
||||
fieldsList[i+1] = col
|
||||
}
|
||||
}
|
||||
|
||||
return parsers.SelectItem{
|
||||
SelectItems: fieldsList,
|
||||
Type: parsers.SelectItemTypeObject,
|
||||
}, nil
|
||||
return parsers.SelectItem{
|
||||
SelectItems: fieldsList,
|
||||
Type: parsers.SelectItemTypeObject,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeOrderByClause(ex1 interface{}, others interface{}) ([]parsers.OrderExpression, error) {
|
||||
othersArray := others.([]interface{})
|
||||
orderList := make([]parsers.OrderExpression, len(othersArray)+1)
|
||||
orderList[0] = ex1.(parsers.OrderExpression)
|
||||
orderList := make([]parsers.OrderExpression, len(othersArray)+1)
|
||||
orderList[0] = ex1.(parsers.OrderExpression)
|
||||
|
||||
for i, v := range othersArray {
|
||||
if col, ok := v.(parsers.OrderExpression); ok {
|
||||
orderList[i+1] = col
|
||||
}
|
||||
}
|
||||
for i, v := range othersArray {
|
||||
if col, ok := v.(parsers.OrderExpression); ok {
|
||||
orderList[i+1] = col
|
||||
}
|
||||
}
|
||||
|
||||
return orderList, nil
|
||||
return orderList, nil
|
||||
}
|
||||
|
||||
func makeOrderExpression(field interface{}, order interface{}) (parsers.OrderExpression, error) {
|
||||
@ -144,8 +144,8 @@ func makeOrderExpression(field interface{}, order interface{}) (parsers.OrderExp
|
||||
}
|
||||
|
||||
if orderValue, ok := order.(parsers.OrderDirection); ok {
|
||||
value.Direction = orderValue
|
||||
}
|
||||
value.Direction = orderValue
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
@ -169,13 +169,39 @@ func joinStrings(array []interface{}) string {
|
||||
|
||||
func combineExpressions(ex1 interface{}, exs interface{}, operation parsers.LogicalExpressionType) (interface{}, error) {
|
||||
if exs == nil || len(exs.([]interface{})) < 1 {
|
||||
return ex1, nil
|
||||
}
|
||||
return ex1, nil
|
||||
}
|
||||
|
||||
return parsers.LogicalExpression{
|
||||
Expressions: append([]interface{}{ex1}, exs.([]interface{})...),
|
||||
Operation: operation,
|
||||
}, nil
|
||||
return parsers.LogicalExpression{
|
||||
Expressions: append([]interface{}{ex1}, exs.([]interface{})...),
|
||||
Operation: operation,
|
||||
}, 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
|
||||
}
|
||||
|
||||
}
|
||||
@ -204,16 +230,16 @@ TopClause <- Top ws count:Integer {
|
||||
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)
|
||||
|
||||
if selectItem != nil {
|
||||
tableTyped.SelectItem = selectItem.(parsers.SelectItem)
|
||||
tableTyped.SelectItem = selectItem.(parsers.SelectItem)
|
||||
tableTyped.IsInSelect = true
|
||||
}
|
||||
}
|
||||
|
||||
return tableTyped, nil
|
||||
} / From ws column:SelectItem {
|
||||
return tableTyped, nil
|
||||
} / From ws column:SelectItemWithAlias {
|
||||
tableSelectItem := column.(parsers.SelectItem)
|
||||
table := parsers.Table{
|
||||
Value: tableSelectItem.Alias,
|
||||
@ -222,11 +248,11 @@ FromClause <- From ws table:TableName selectItem:(ws In ws column:SelectItem { r
|
||||
return table, nil
|
||||
} / From ws subQuery:SubQuerySelectItem {
|
||||
subQueryTyped := subQuery.(parsers.SelectItem)
|
||||
table := parsers.Table{
|
||||
Value: subQueryTyped.Alias,
|
||||
SelectItem: subQueryTyped,
|
||||
}
|
||||
return table, nil
|
||||
table := parsers.Table{
|
||||
Value: subQueryTyped.Alias,
|
||||
SelectItem: subQueryTyped,
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
|
||||
SubQuery <- exists:(exists:Exists ws { return exists, nil })? "(" ws selectStmt:SelectStmt ws ")" {
|
||||
@ -251,7 +277,7 @@ SubQuerySelectItem <- subQuery:SubQuery asClause:(ws alias:AsClause { return ali
|
||||
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)
|
||||
} / Join ws subQuery:SubQuerySelectItem {
|
||||
return makeJoin(nil, subQuery)
|
||||
@ -265,17 +291,40 @@ Selection <- SelectValueSpec / ColumnList / SelectAsterisk
|
||||
|
||||
SelectAsterisk <- "*" {
|
||||
selectItem, _ := makeSelectItem("c", make([]interface{}, 0), parsers.SelectItemTypeField)
|
||||
selectItem.IsTopLevel = true
|
||||
selectItem.IsTopLevel = true
|
||||
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)
|
||||
}
|
||||
|
||||
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.IsTopLevel = true
|
||||
selectItem.IsTopLevel = true
|
||||
return makeColumnList(selectItem, make([]interface{}, 0))
|
||||
}
|
||||
|
||||
@ -289,19 +338,32 @@ SelectArray <- "[" ws columns:ColumnList ws "]" {
|
||||
|
||||
SelectObject <- "{" ws field:SelectObjectField ws other_fields:(ws "," ws coll:SelectObjectField {return coll, nil })* ws "}" {
|
||||
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 {
|
||||
item := selectItem.(parsers.SelectItem)
|
||||
item.Alias = name.(string)
|
||||
return item, nil
|
||||
item.Alias = name.(string)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
SelectProperty <- name:Identifier path:(DotFieldAccess / ArrayFieldAccess)* {
|
||||
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
|
||||
switch typedValue := selectItem.(type) {
|
||||
case parsers.SelectItem:
|
||||
@ -318,11 +380,7 @@ SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectAr
|
||||
}
|
||||
}
|
||||
|
||||
if aliasValue, ok := asClause.(string); ok {
|
||||
itemResult.Alias = aliasValue
|
||||
}
|
||||
|
||||
return itemResult, nil
|
||||
return itemResult, nil
|
||||
}
|
||||
|
||||
AsClause <- (ws As)? ws !ExcludedKeywords alias:Identifier {
|
||||
@ -355,15 +413,25 @@ AndExpression <- ex1:ComparisonExpression ex2:(ws And ws ex:ComparisonExpression
|
||||
return combineExpressions(ex1, ex2, parsers.LogicalExpressionTypeAnd)
|
||||
}
|
||||
|
||||
ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil }
|
||||
/ left:SelectItem ws op:ComparisonOperator ws right:SelectItem {
|
||||
ComparisonExpression <- left:AddSubExpression ws op:ComparisonOperator ws right:AddSubExpression {
|
||||
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 {
|
||||
ex1 := ex.(parsers.SelectItem)
|
||||
ex1.Invert = true
|
||||
return ex1, nil
|
||||
}
|
||||
ex1 := ex.(parsers.SelectItem)
|
||||
ex1.Invert = true
|
||||
return ex1, nil
|
||||
}
|
||||
return ex, nil
|
||||
} / ex:BooleanLiteral { return ex, nil }
|
||||
|
||||
@ -377,10 +445,10 @@ OrderExpression <- field:SelectProperty ws order:OrderDirection? {
|
||||
|
||||
OrderDirection <- ("ASC"i / "DESC"i) {
|
||||
if strings.EqualFold(string(c.text), "DESC") {
|
||||
return parsers.OrderDirectionDesc, nil
|
||||
return parsers.OrderDirectionDesc, nil
|
||||
}
|
||||
|
||||
return parsers.OrderDirectionAsc, nil
|
||||
return parsers.OrderDirectionAsc, nil
|
||||
}
|
||||
|
||||
Select <- "SELECT"i
|
||||
@ -415,6 +483,10 @@ ComparisonOperator <- ("<=" / ">=" / "=" / "!=" / "<" / ">") {
|
||||
return string(c.text), nil
|
||||
}
|
||||
|
||||
AddOrSubtractOperation <- ("+" / "-") { return string(c.text), nil }
|
||||
|
||||
MultiplyOrDivideOperation <- ("*" / "/") { return string(c.text), nil }
|
||||
|
||||
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
|
||||
|
||||
ParameterConstant <- "@" Identifier {
|
||||
@ -442,6 +514,7 @@ BooleanLiteral <- ("true"i / "false"i) {
|
||||
FunctionCall <- StringFunctions
|
||||
/ TypeCheckingFunctions
|
||||
/ ArrayFunctions
|
||||
/ ConditionalFunctions
|
||||
/ InFunction
|
||||
/ AggregateFunctions
|
||||
/ MathFunctions
|
||||
@ -489,6 +562,8 @@ ArrayFunctions <- ArrayConcatExpression
|
||||
/ SetIntersectExpression
|
||||
/ SetUnionExpression
|
||||
|
||||
ConditionalFunctions <- IifExpression
|
||||
|
||||
MathFunctions <- MathAbsExpression
|
||||
/ MathAcosExpression
|
||||
/ MathAsinExpression
|
||||
@ -681,6 +756,10 @@ SetUnionExpression <- "SetUnion"i ws "(" ws set1:SelectItem ws "," ws set2:Selec
|
||||
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}) }
|
||||
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}) }
|
||||
|
@ -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")},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
91
query_executors/memory_executor/arithmetics_test.go
Normal file
91
query_executors/memory_executor/arithmetics_test.go
Normal file
@ -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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -209,6 +231,9 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
|
||||
case parsers.FunctionCallSetUnion:
|
||||
return r.set_Union(functionCall.Arguments)
|
||||
|
||||
case parsers.FunctionCallIif:
|
||||
return r.misc_Iif(functionCall.Arguments)
|
||||
|
||||
case parsers.FunctionCallMathAbs:
|
||||
return r.math_Abs(functionCall.Arguments)
|
||||
case parsers.FunctionCallMathAcos:
|
||||
@ -301,6 +326,45 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
|
||||
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{} {
|
||||
value := r.tables[selectItem.Path[0]]
|
||||
|
||||
@ -336,6 +400,7 @@ func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem
|
||||
}
|
||||
|
||||
func compareValues(val1, val2 interface{}) int {
|
||||
// Handle nil values
|
||||
if val1 == nil && val2 == nil {
|
||||
return 0
|
||||
} else if val1 == nil {
|
||||
@ -344,27 +409,24 @@ func compareValues(val1, val2 interface{}) int {
|
||||
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) {
|
||||
return 1
|
||||
}
|
||||
|
||||
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:
|
||||
val2 := val2.(string)
|
||||
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) {
|
||||
switch val := num.(type) {
|
||||
case float64:
|
||||
return val, true
|
||||
case int:
|
||||
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:
|
||||
return 0, false
|
||||
}
|
||||
|
@ -16,3 +16,16 @@ func (r rowContext) misc_In(arguments []interface{}) bool {
|
||||
|
||||
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{}{}},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
"github.com/pikami/cosmium/internal/datastore"
|
||||
badgerdatastore "github.com/pikami/cosmium/internal/datastore/badger_datastore"
|
||||
mapdatastore "github.com/pikami/cosmium/internal/datastore/map_datastore"
|
||||
jsondatastore "github.com/pikami/cosmium/internal/datastore/json_datastore"
|
||||
)
|
||||
|
||||
//export CreateServerInstance
|
||||
@ -41,7 +41,7 @@ func CreateServerInstance(serverName *C.char, configurationJSON *C.char) int {
|
||||
PersistDataFilePath: configuration.PersistDataFilePath,
|
||||
})
|
||||
default:
|
||||
dataStore = mapdatastore.NewMapDataStore(mapdatastore.MapDataStoreOptions{
|
||||
dataStore = jsondatastore.NewJsonDataStore(jsondatastore.JsonDataStoreOptions{
|
||||
InitialDataFilePath: configuration.InitialDataFilePath,
|
||||
PersistDataFilePath: configuration.PersistDataFilePath,
|
||||
})
|
||||
@ -96,8 +96,8 @@ func LoadServerInstanceState(serverName *C.char, stateJSON *C.char) int {
|
||||
stateJSONStr := C.GoString(stateJSON)
|
||||
|
||||
if serverInstance, ok := getInstance(serverNameStr); ok {
|
||||
if mapDS, ok := serverInstance.dataStore.(*mapdatastore.MapDataStore); ok {
|
||||
err := mapDS.LoadStateJSON(stateJSONStr)
|
||||
if jsonDS, ok := serverInstance.dataStore.(*jsondatastore.JsonDataStore); ok {
|
||||
err := jsonDS.LoadStateJSON(stateJSONStr)
|
||||
if err != nil {
|
||||
return ResponseFailedToLoadState
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user