mirror of
https://github.com/pikami/cosmium.git
synced 2025-06-06 23:50:27 +01:00
Compare commits
7 Commits
d426dc23c0
...
18edb925bf
Author | SHA1 | Date | |
---|---|---|---|
|
18edb925bf | ||
|
6ccb7c4bdd | ||
|
b9e38575bc | ||
|
3aeae98404 | ||
|
5ff923ce2c | ||
|
f3f3966dd5 | ||
|
19f62f8173 |
@ -26,7 +26,7 @@ brews:
|
|||||||
homepage: 'https://github.com/pikami/cosmium'
|
homepage: 'https://github.com/pikami/cosmium'
|
||||||
repository:
|
repository:
|
||||||
owner: pikami
|
owner: pikami
|
||||||
name: homebrew-pikami
|
name: homebrew-brew
|
||||||
commit_author:
|
commit_author:
|
||||||
name: pikami
|
name: pikami
|
||||||
email: git@pikami.org
|
email: git@pikami.org
|
||||||
|
25
README.md
25
README.md
@ -5,7 +5,17 @@ Cosmium is a lightweight Cosmos DB emulator designed to facilitate local develop
|
|||||||
One of Cosmium's notable features is its ability to save and load state to a single JSON file. This feature makes it easy to load different test cases or share state with other developers, enhancing collaboration and efficiency in development workflows.
|
One of Cosmium's notable features is its ability to save and load state to a single JSON file. This feature makes it easy to load different test cases or share state with other developers, enhancing collaboration and efficiency in development workflows.
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
### Downloading Cosmium
|
### Installation via Homebrew
|
||||||
|
You can install Cosmium using Homebrew by adding the `pikami/brew` tap and then installing the package.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew tap pikami/brew
|
||||||
|
brew install cosmium
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download and install Cosmium on your system, making it easy to manage and update using Homebrew.
|
||||||
|
|
||||||
|
### Downloading Cosmium Binaries
|
||||||
|
|
||||||
You can download the latest version of Cosmium from the [GitHub Releases page](https://github.com/pikami/cosmium/releases). Choose the appropriate release for your operating system and architecture.
|
You can download the latest version of Cosmium from the [GitHub Releases page](https://github.com/pikami/cosmium/releases). Choose the appropriate release for your operating system and architecture.
|
||||||
|
|
||||||
@ -23,11 +33,7 @@ Cosmium is available for the following platforms:
|
|||||||
Once downloaded, you can launch Cosmium using the following command:
|
Once downloaded, you can launch Cosmium using the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./cosmium-linux-amd64 \
|
cosmium -Persist "./save.json"
|
||||||
-Cert "cert.crt" \
|
|
||||||
-CertKey "cert.key" \
|
|
||||||
-Persist "./save.json" \
|
|
||||||
-InitialData "./save.json"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Connection String Example:
|
Connection String Example:
|
||||||
@ -43,7 +49,9 @@ Once running, the explorer can be reached by navigating following URL: `https://
|
|||||||
|
|
||||||
### SSL Certificate
|
### SSL Certificate
|
||||||
|
|
||||||
By default, Cosmium runs on HTTP. However, if you provide an SSL certificate, it will use HTTPS. Most applications will require HTTPS, so you can specify paths to the SSL certificate and key (PEM format) using the `-Cert` and `-CertKey` arguments, respectively.
|
By default, Cosmium uses a pre-generated SSL certificate. You can provide your own certificates by specifying paths to the SSL certificate and key (PEM format) using the `-Cert` and `-CertKey` arguments, respectively.
|
||||||
|
|
||||||
|
To disable SSL and run Cosmium on HTTP instead, you can use the `-DisableTls` flag. However most applications will require HTTPS.
|
||||||
|
|
||||||
### Other Available Arguments
|
### Other Available Arguments
|
||||||
|
|
||||||
@ -51,8 +59,9 @@ By default, Cosmium runs on HTTP. However, if you provide an SSL certificate, it
|
|||||||
* **-DisableAuth**: Disable authentication
|
* **-DisableAuth**: Disable authentication
|
||||||
* **-Host**: Hostname (default "localhost")
|
* **-Host**: Hostname (default "localhost")
|
||||||
* **-InitialData**: Path to JSON containing initial state
|
* **-InitialData**: Path to JSON containing initial state
|
||||||
* **-Persist**: Saves data to the given path on application exit
|
* **-Persist**: Saves data to the given path on application exit (When `-InitialData` argument is not supplied, it will try to load data from path supplied in `-Persist`)
|
||||||
* **-Port**: Listen port (default 8081)
|
* **-Port**: Listen port (default 8081)
|
||||||
|
* **-Debug**: Runs application in debug mode, this provides additional logging
|
||||||
|
|
||||||
These arguments allow you to configure various aspects of Cosmium's behavior according to your requirements.
|
These arguments allow you to configure various aspects of Cosmium's behavior according to your requirements.
|
||||||
|
|
||||||
|
@ -20,7 +20,9 @@ func ParseFlags() {
|
|||||||
initialDataPath := flag.String("InitialData", "", "Path to JSON containing initial state")
|
initialDataPath := flag.String("InitialData", "", "Path to JSON containing initial state")
|
||||||
accountKey := flag.String("AccountKey", DefaultAccountKey, "Account key for authentication")
|
accountKey := flag.String("AccountKey", DefaultAccountKey, "Account key for authentication")
|
||||||
disableAuthentication := flag.Bool("DisableAuth", false, "Disable authentication")
|
disableAuthentication := flag.Bool("DisableAuth", false, "Disable authentication")
|
||||||
|
disableTls := flag.Bool("DisableTls", false, "Disable TLS, serve over HTTP")
|
||||||
persistDataPath := flag.String("Persist", "", "Saves data to given path on application exit")
|
persistDataPath := flag.String("Persist", "", "Saves data to given path on application exit")
|
||||||
|
debug := flag.Bool("Debug", false, "Runs application in debug mode, this provides additional logging")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -32,6 +34,8 @@ func ParseFlags() {
|
|||||||
Config.InitialDataFilePath = *initialDataPath
|
Config.InitialDataFilePath = *initialDataPath
|
||||||
Config.PersistDataFilePath = *persistDataPath
|
Config.PersistDataFilePath = *persistDataPath
|
||||||
Config.DisableAuth = *disableAuthentication
|
Config.DisableAuth = *disableAuthentication
|
||||||
|
Config.DisableTls = *disableTls
|
||||||
|
Config.Debug = *debug
|
||||||
|
|
||||||
Config.DatabaseAccount = Config.Host
|
Config.DatabaseAccount = Config.Host
|
||||||
Config.DatabaseDomain = Config.Host
|
Config.DatabaseDomain = Config.Host
|
||||||
|
@ -14,4 +14,6 @@ type ServerConfig struct {
|
|||||||
InitialDataFilePath string
|
InitialDataFilePath string
|
||||||
PersistDataFilePath string
|
PersistDataFilePath string
|
||||||
DisableAuth bool
|
DisableAuth bool
|
||||||
|
DisableTls bool
|
||||||
|
Debug bool
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pikami/cosmium/api/config"
|
"github.com/pikami/cosmium/api/config"
|
||||||
"github.com/pikami/cosmium/internal/authentication"
|
"github.com/pikami/cosmium/internal/authentication"
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Authentication() gin.HandlerFunc {
|
func Authentication() gin.HandlerFunc {
|
||||||
@ -53,7 +53,7 @@ func Authentication() gin.HandlerFunc {
|
|||||||
params, _ := url.ParseQuery(decoded)
|
params, _ := url.ParseQuery(decoded)
|
||||||
clientSignature := strings.Replace(params.Get("sig"), " ", "+", -1)
|
clientSignature := strings.Replace(params.Get("sig"), " ", "+", -1)
|
||||||
if clientSignature != expectedSignature {
|
if clientSignature != expectedSignature {
|
||||||
fmt.Printf("Got wrong signature from client.\n- Expected: %s\n- Got: %s\n", expectedSignature, clientSignature)
|
logger.Errorf("Got wrong signature from client.\n- Expected: %s\n- Got: %s\n", expectedSignature, clientSignature)
|
||||||
c.IndentedJSON(401, gin.H{
|
c.IndentedJSON(401, gin.H{
|
||||||
"code": "Unauthorized",
|
"code": "Unauthorized",
|
||||||
"message": "Wrong signature.",
|
"message": "Wrong signature.",
|
||||||
|
@ -2,10 +2,10 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RequestLogger() gin.HandlerFunc {
|
func RequestLogger() gin.HandlerFunc {
|
||||||
@ -16,7 +16,7 @@ func RequestLogger() gin.HandlerFunc {
|
|||||||
|
|
||||||
bodyStr := readBody(rdr1)
|
bodyStr := readBody(rdr1)
|
||||||
if bodyStr != "" {
|
if bodyStr != "" {
|
||||||
fmt.Println(bodyStr)
|
logger.Debug(bodyStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Request.Body = rdr2
|
c.Request.Body = rdr2
|
||||||
|
@ -26,9 +26,14 @@ func GetPartitionKeyRanges(c *gin.Context) {
|
|||||||
c.Header("x-ms-global-committed-lsn", "420")
|
c.Header("x-ms-global-committed-lsn", "420")
|
||||||
c.Header("x-ms-item-count", fmt.Sprintf("%d", len(partitionKeyRanges)))
|
c.Header("x-ms-item-count", fmt.Sprintf("%d", len(partitionKeyRanges)))
|
||||||
|
|
||||||
|
collectionRid := collectionId
|
||||||
collection, _ := repositories.GetCollection(databaseId, collectionId)
|
collection, _ := repositories.GetCollection(databaseId, collectionId)
|
||||||
|
if collection.ResourceID != "" {
|
||||||
|
collectionRid = collection.ResourceID
|
||||||
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, gin.H{
|
c.IndentedJSON(http.StatusOK, gin.H{
|
||||||
"_rid": collection.ResourceID,
|
"_rid": collectionRid,
|
||||||
"_count": len(partitionKeyRanges),
|
"_count": len(partitionKeyRanges),
|
||||||
"PartitionKeyRanges": partitionKeyRanges,
|
"PartitionKeyRanges": partitionKeyRanges,
|
||||||
})
|
})
|
||||||
|
@ -1,12 +1,42 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pikami/cosmium/internal/constants"
|
"github.com/pikami/cosmium/api/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetServerInfo(c *gin.Context) {
|
func GetServerInfo(c *gin.Context) {
|
||||||
c.IndentedJSON(http.StatusOK, constants.ServerInfoResponse)
|
c.IndentedJSON(http.StatusOK, gin.H{
|
||||||
|
"_self": "",
|
||||||
|
"id": config.Config.DatabaseAccount,
|
||||||
|
"_rid": fmt.Sprintf("%s.%s", config.Config.DatabaseAccount, config.Config.DatabaseDomain),
|
||||||
|
"media": "//media/",
|
||||||
|
"addresses": "//addresses/",
|
||||||
|
"_dbs": "//dbs/",
|
||||||
|
"writableLocations": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"name": "South Central US",
|
||||||
|
"databaseAccountEndpoint": config.Config.DatabaseEndpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"readableLocations": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"name": "South Central US",
|
||||||
|
"databaseAccountEndpoint": config.Config.DatabaseEndpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"enableMultipleWriteLocations": false,
|
||||||
|
"userReplicationPolicy": map[string]interface{}{
|
||||||
|
"asyncReplication": false,
|
||||||
|
"minReplicaSetSize": 1,
|
||||||
|
"maxReplicasetSize": 4,
|
||||||
|
},
|
||||||
|
"userConsistencyPolicy": map[string]interface{}{"defaultConsistencyLevel": "Session"},
|
||||||
|
"systemReplicationPolicy": map[string]interface{}{"minReplicaSetSize": 1, "maxReplicasetSize": 4},
|
||||||
|
"readPolicy": map[string]interface{}{"primaryReadCoefficient": 1, "secondaryReadCoefficient": 1},
|
||||||
|
"queryEngineConfiguration": "{\"allowNewKeywords\":true,\"maxJoinsPerSqlQuery\":10,\"maxQueryRequestTimeoutFraction\":0.9,\"maxSqlQueryInputLength\":524288,\"maxUdfRefPerSqlQuery\":10,\"queryMaxInMemorySortDocumentCount\":-1000,\"spatialMaxGeometryPointCount\":256,\"sqlAllowNonFiniteNumbers\":false,\"sqlDisableOptimizationFlags\":0,\"enableSpatialIndexing\":true,\"maxInExpressionItemsCount\":2147483647,\"maxLogicalAndPerSqlQuery\":2147483647,\"maxLogicalOrPerSqlQuery\":2147483647,\"maxSpatialQueryCells\":2147483647,\"sqlAllowAggregateFunctions\":true,\"sqlAllowGroupByClause\":true,\"sqlAllowLike\":true,\"sqlAllowSubQuery\":true,\"sqlAllowScalarSubQuery\":true,\"sqlAllowTop\":true}",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pikami/cosmium/api/config"
|
||||||
"github.com/pikami/cosmium/api/handlers"
|
"github.com/pikami/cosmium/api/handlers"
|
||||||
"github.com/pikami/cosmium/api/handlers/middleware"
|
"github.com/pikami/cosmium/api/handlers/middleware"
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
|
tlsprovider "github.com/pikami/cosmium/internal/tls_provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateRouter() *gin.Engine {
|
func CreateRouter() *gin.Engine {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.Use(middleware.RequestLogger())
|
if config.Config.Debug {
|
||||||
|
router.Use(middleware.RequestLogger())
|
||||||
|
}
|
||||||
|
|
||||||
router.Use(middleware.Authentication())
|
router.Use(middleware.Authentication())
|
||||||
|
|
||||||
router.GET("/dbs/:databaseId/colls/:collId/pkranges", handlers.GetPartitionKeyRanges)
|
router.GET("/dbs/:databaseId/colls/:collId/pkranges", handlers.GetPartitionKeyRanges)
|
||||||
@ -43,3 +52,43 @@ func CreateRouter() *gin.Engine {
|
|||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartAPI() {
|
||||||
|
if !config.Config.Debug {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
router := CreateRouter()
|
||||||
|
listenAddress := fmt.Sprintf(":%d", config.Config.Port)
|
||||||
|
|
||||||
|
if config.Config.TLS_CertificatePath != "" && config.Config.TLS_CertificateKey != "" {
|
||||||
|
err := router.RunTLS(
|
||||||
|
listenAddress,
|
||||||
|
config.Config.TLS_CertificatePath,
|
||||||
|
config.Config.TLS_CertificateKey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to start HTTPS server:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Config.DisableTls {
|
||||||
|
router.Run(listenAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := tlsprovider.GetDefaultTlsConfig()
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: listenAddress,
|
||||||
|
Handler: router.Handler(),
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Listening and serving HTTPS on %s\n", server.Addr)
|
||||||
|
err := server.ListenAndServeTLS("", "")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to start HTTPS server:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Run()
|
||||||
|
}
|
||||||
|
@ -1,43 +1,9 @@
|
|||||||
package constants
|
package constants
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pikami/cosmium/api/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServerInfoResponse = gin.H{
|
|
||||||
"_self": "",
|
|
||||||
"id": config.Config.DatabaseAccount,
|
|
||||||
"_rid": fmt.Sprintf("%s.%s", config.Config.DatabaseAccount, config.Config.DatabaseDomain),
|
|
||||||
"media": "//media/",
|
|
||||||
"addresses": "//addresses/",
|
|
||||||
"_dbs": "//dbs/",
|
|
||||||
"writableLocations": []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"name": "South Central US",
|
|
||||||
"databaseAccountEndpoint": config.Config.DatabaseEndpoint,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"readableLocations": []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"name": "South Central US",
|
|
||||||
"databaseAccountEndpoint": config.Config.DatabaseEndpoint,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"enableMultipleWriteLocations": false,
|
|
||||||
"userReplicationPolicy": map[string]interface{}{
|
|
||||||
"asyncReplication": false,
|
|
||||||
"minReplicaSetSize": 1,
|
|
||||||
"maxReplicasetSize": 4,
|
|
||||||
},
|
|
||||||
"userConsistencyPolicy": map[string]interface{}{"defaultConsistencyLevel": "Session"},
|
|
||||||
"systemReplicationPolicy": map[string]interface{}{"minReplicaSetSize": 1, "maxReplicasetSize": 4},
|
|
||||||
"readPolicy": map[string]interface{}{"primaryReadCoefficient": 1, "secondaryReadCoefficient": 1},
|
|
||||||
"queryEngineConfiguration": "{\"allowNewKeywords\":true,\"maxJoinsPerSqlQuery\":10,\"maxQueryRequestTimeoutFraction\":0.9,\"maxSqlQueryInputLength\":524288,\"maxUdfRefPerSqlQuery\":10,\"queryMaxInMemorySortDocumentCount\":-1000,\"spatialMaxGeometryPointCount\":256,\"sqlAllowNonFiniteNumbers\":false,\"sqlDisableOptimizationFlags\":0,\"enableSpatialIndexing\":true,\"maxInExpressionItemsCount\":2147483647,\"maxLogicalAndPerSqlQuery\":2147483647,\"maxLogicalOrPerSqlQuery\":2147483647,\"maxSpatialQueryCells\":2147483647,\"sqlAllowAggregateFunctions\":true,\"sqlAllowGroupByClause\":true,\"sqlAllowLike\":true,\"sqlAllowSubQuery\":true,\"sqlAllowScalarSubQuery\":true,\"sqlAllowTop\":true}",
|
|
||||||
}
|
|
||||||
|
|
||||||
var QueryPlanResponse = gin.H{
|
var QueryPlanResponse = gin.H{
|
||||||
"partitionedQueryExecutionInfoVersion": 2,
|
"partitionedQueryExecutionInfoVersion": 2,
|
||||||
"queryInfo": map[string]interface{}{
|
"queryInfo": map[string]interface{}{
|
||||||
|
40
internal/logger/logger.go
Normal file
40
internal/logger/logger.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/api/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DebugLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
var InfoLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||||
|
var ErrorLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
|
func Debug(v ...any) {
|
||||||
|
if config.Config.Debug {
|
||||||
|
DebugLogger.Println(v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, v ...any) {
|
||||||
|
if config.Config.Debug {
|
||||||
|
DebugLogger.Printf(format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(v ...any) {
|
||||||
|
InfoLogger.Println(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(format string, v ...any) {
|
||||||
|
InfoLogger.Printf(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(v ...any) {
|
||||||
|
ErrorLogger.Println(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, v ...any) {
|
||||||
|
ErrorLogger.Printf(format, v...)
|
||||||
|
}
|
@ -10,19 +10,21 @@ import (
|
|||||||
|
|
||||||
// I have no idea what this is tbh
|
// I have no idea what this is tbh
|
||||||
func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repositorymodels.PartitionKeyRange, repositorymodels.RepositoryStatus) {
|
func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repositorymodels.PartitionKeyRange, repositorymodels.RepositoryStatus) {
|
||||||
var ok bool
|
databaseRid := databaseId
|
||||||
var database repositorymodels.Database
|
collectionRid := collectionId
|
||||||
var collection repositorymodels.Collection
|
var timestamp int64 = 0
|
||||||
if database, ok = storeState.Databases[databaseId]; !ok {
|
|
||||||
return make([]repositorymodels.PartitionKeyRange, 0), repositorymodels.StatusNotFound
|
if database, ok := storeState.Databases[databaseId]; !ok {
|
||||||
|
databaseRid = database.ResourceID
|
||||||
}
|
}
|
||||||
|
|
||||||
if collection, ok = storeState.Collections[databaseId][collectionId]; !ok {
|
if collection, ok := storeState.Collections[databaseId][collectionId]; !ok {
|
||||||
return make([]repositorymodels.PartitionKeyRange, 0), repositorymodels.StatusNotFound
|
collectionRid = collection.ResourceID
|
||||||
|
timestamp = collection.TimeStamp
|
||||||
}
|
}
|
||||||
|
|
||||||
pkrResourceId := resourceid.NewCombined(database.ResourceID, collection.ResourceID, resourceid.New())
|
pkrResourceId := resourceid.NewCombined(databaseRid, collectionRid, resourceid.New())
|
||||||
pkrSelf := fmt.Sprintf("dbs/%s/colls/%s/pkranges/%s/", database.ResourceID, collection.ResourceID, pkrResourceId)
|
pkrSelf := fmt.Sprintf("dbs/%s/colls/%s/pkranges/%s/", databaseRid, collectionRid, pkrResourceId)
|
||||||
etag := fmt.Sprintf("\"%s\"", uuid.New())
|
etag := fmt.Sprintf("\"%s\"", uuid.New())
|
||||||
|
|
||||||
return []repositorymodels.PartitionKeyRange{
|
return []repositorymodels.PartitionKeyRange{
|
||||||
@ -37,7 +39,7 @@ func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repository
|
|||||||
ThroughputFraction: 1,
|
ThroughputFraction: 1,
|
||||||
Status: "online",
|
Status: "online",
|
||||||
Parents: []interface{}{},
|
Parents: []interface{}{},
|
||||||
TimeStamp: collection.TimeStamp,
|
TimeStamp: timestamp,
|
||||||
Lsn: 17,
|
Lsn: 17,
|
||||||
},
|
},
|
||||||
}, repositorymodels.StatusOk
|
}, repositorymodels.StatusOk
|
||||||
|
@ -2,11 +2,12 @@ package repositories
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/api/config"
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
|
repositorymodels "github.com/pikami/cosmium/internal/repository_models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,21 +20,45 @@ var storeState = repositorymodels.State{
|
|||||||
Documents: make(map[string]map[string]map[string]repositorymodels.Document),
|
Documents: make(map[string]map[string]map[string]repositorymodels.Document),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitializeRepository() {
|
||||||
|
if config.Config.InitialDataFilePath != "" {
|
||||||
|
LoadStateFS(config.Config.InitialDataFilePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Config.PersistDataFilePath != "" {
|
||||||
|
stat, err := os.Stat(config.Config.PersistDataFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.IsDir() {
|
||||||
|
logger.Error("Argument '-Persist' must be a path to file, not a directory.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadStateFS(config.Config.PersistDataFilePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func LoadStateFS(filePath string) {
|
func LoadStateFS(filePath string) {
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error reading state JSON file: %v", err)
|
log.Fatalf("Error reading state JSON file: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var state repositorymodels.State
|
var state repositorymodels.State
|
||||||
if err := json.Unmarshal(data, &state); err != nil {
|
if err := json.Unmarshal(data, &state); err != nil {
|
||||||
log.Fatalf("Error unmarshalling state JSON: %v", err)
|
log.Fatalf("Error unmarshalling state JSON: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Loaded state:")
|
logger.Info("Loaded state:")
|
||||||
fmt.Printf("Databases: %d\n", getLength(state.Databases))
|
logger.Infof("Databases: %d\n", getLength(state.Databases))
|
||||||
fmt.Printf("Collections: %d\n", getLength(state.Collections))
|
logger.Infof("Collections: %d\n", getLength(state.Collections))
|
||||||
fmt.Printf("Documents: %d\n", getLength(state.Documents))
|
logger.Infof("Documents: %d\n", getLength(state.Documents))
|
||||||
|
|
||||||
storeState = state
|
storeState = state
|
||||||
|
|
||||||
@ -43,16 +68,16 @@ func LoadStateFS(filePath string) {
|
|||||||
func SaveStateFS(filePath string) {
|
func SaveStateFS(filePath string) {
|
||||||
data, err := json.MarshalIndent(storeState, "", "\t")
|
data, err := json.MarshalIndent(storeState, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to save state: %v\n", err)
|
logger.Errorf("Failed to save state: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
os.WriteFile(filePath, data, os.ModePerm)
|
os.WriteFile(filePath, data, os.ModePerm)
|
||||||
|
|
||||||
fmt.Println("Saved state:")
|
logger.Info("Saved state:")
|
||||||
fmt.Printf("Databases: %d\n", getLength(storeState.Databases))
|
logger.Infof("Databases: %d\n", getLength(storeState.Databases))
|
||||||
fmt.Printf("Collections: %d\n", getLength(storeState.Collections))
|
logger.Infof("Collections: %d\n", getLength(storeState.Collections))
|
||||||
fmt.Printf("Documents: %d\n", getLength(storeState.Documents))
|
logger.Infof("Documents: %d\n", getLength(storeState.Documents))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetState() repositorymodels.State {
|
func GetState() repositorymodels.State {
|
||||||
|
61
internal/tls_provider/constants.go
Normal file
61
internal/tls_provider/constants.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package tlsprovider
|
||||||
|
|
||||||
|
const certificate = `
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEaDCCAlCgAwIBAgIUAY7ito1IQfbIi52C0evhqHWgEvQwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwMzELMAkGA1UEBhMCTFQxEjAQBgNVBAgMCUxpdGh1YW5pYTEQMA4GA1UECgwH
|
||||||
|
Q29zbWl1bTAeFw0yNDAyMjcxOTE4NThaFw0zNDAyMjYxOTE4NThaMD8xCzAJBgNV
|
||||||
|
BAYTAkxUMRIwEAYDVQQIDAlMaXRodWFuaWExEDAOBgNVBAoMB0Nvc21pdW0xCjAI
|
||||||
|
BgNVBAMMASowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZxGz5clcf
|
||||||
|
fvE6wS9Q2xPsUjeKdwotRCfKRu9kT7o1cZOSRBp7DgdeLvZ7BzqU1tk5wiLLiZwB
|
||||||
|
gI6amQAd6z6EwUcUH0mHtFiWU0y/FROz0QUojbbYp0PMUhWjlPAxAGaiwgF/82z7
|
||||||
|
/lmgMjf5v32XsMfa4U+FaaNYs7gu7aCQBQTAHmOIPnEAeFk9xQ2VzntRUWwzDYOV
|
||||||
|
SimtPZk2O2X18V8KTgTLMQF1KErIyznIwEPB/BLi+ihLkh/8BaaxoIeOPIhRLNFr
|
||||||
|
ecZrc/8+S4dUSUQDfmV3JFYFFheG0XIPEwXIaXiDAphpkCGhMIC2pDL8r14sntvn
|
||||||
|
juHFZxmSP4V5AgMBAAGjaDBmMB8GA1UdIwQYMBaAFEbQ/7hV4FWrptdOk540R2lF
|
||||||
|
SB1BMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMAwGA1UdEQQFMAOCASowHQYDVR0O
|
||||||
|
BBYEFGv5XvoFFzrG54GQ+WMFm6UO36BJMA0GCSqGSIb3DQEBCwUAA4ICAQBZh/vZ
|
||||||
|
PBamebTEpiQz6cgf8+GcTi++ebYUGQ3YJj82pqVBdipOhYQOZJ0fOlT1qRGNglut
|
||||||
|
+m5zn0iuXsNucP/32xdf1aJBnsU/aGrlf5ohJpGNxYfNPsewxeqQI23Yj22ec1gy
|
||||||
|
WL2pFDYNyTZMM7Wgys7m3i9lb6TYOF2lNO3WbNuuuETsDAPa0rD0R8QsQOfYOSNJ
|
||||||
|
YuWE4qZu+ySvTWsMZwlcqs7QL3Sd91UjItIS/AgqbnLvgt4z5ckGCIvickUfAZuQ
|
||||||
|
6x592hTz4OZ+WIYDejtb5MMXRaKEXgfF6o1idrD7YgVutm+2+mYpN1v9aLbCs7QW
|
||||||
|
9RkJoTXFQRNGq6j/cO0ZrCKFkttduziMWRz5X9QWADME1NsL53DfDkaxp9Nh+CCu
|
||||||
|
0S9OF9nVLJVigdXe4O1cQ0Qh633O6k+F/xWYcmMyVt3V2bs7FPfygGUx60tfIbpi
|
||||||
|
cBK3BsuzUrId3ozvYPsmfxYlzmyspyS6G+f7zLFOakm3fuqDJpnFNXmRY2Ljd3Cp
|
||||||
|
punuMT6zSctHAxpgJm1g9R6PcaGr+b/n6zkbxyK9+SFzwN3Lb18WFj5OcslNM/g5
|
||||||
|
ERE5Ws+Vae6MleSmsxSytgH4qn0ormPWuouBLaW0Rv2ZHdkt3myq8kTqtqdw3LRR
|
||||||
|
ogcLQ3cL6I5FKGjm2TOF72DQHvOol8ck0uMz/w==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
|
||||||
|
const certificateKey = `
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCZxGz5clcffvE6
|
||||||
|
wS9Q2xPsUjeKdwotRCfKRu9kT7o1cZOSRBp7DgdeLvZ7BzqU1tk5wiLLiZwBgI6a
|
||||||
|
mQAd6z6EwUcUH0mHtFiWU0y/FROz0QUojbbYp0PMUhWjlPAxAGaiwgF/82z7/lmg
|
||||||
|
Mjf5v32XsMfa4U+FaaNYs7gu7aCQBQTAHmOIPnEAeFk9xQ2VzntRUWwzDYOVSimt
|
||||||
|
PZk2O2X18V8KTgTLMQF1KErIyznIwEPB/BLi+ihLkh/8BaaxoIeOPIhRLNFrecZr
|
||||||
|
c/8+S4dUSUQDfmV3JFYFFheG0XIPEwXIaXiDAphpkCGhMIC2pDL8r14sntvnjuHF
|
||||||
|
ZxmSP4V5AgMBAAECgf89wcgjpZnzoWoiM3Z6QDJnkiUdXQumHQracBnRFXnMy8p9
|
||||||
|
wCd4ecnu9ptd8OArXgVMiaILWZeGXlqtW872m6Lej6DrJkpOt3NG9CvscdaHdthW
|
||||||
|
9dzv8d7IEtuRN4/WWOm7Tke7eD7763ta9i9/niR2q7DazPVw8vYhkyoNe864qVrq
|
||||||
|
Vw6+MMetz3TDHZ68p17yJJ9FJ0z0vHj3KJFrxnJonMe+/LcQX490y4zZw+zeyCkh
|
||||||
|
y/bsgvFGhnUhJ+mOz+qv0KL7HyUR69p9/+mjQH+AQH+j24xgd1IL0Dror9Cy1kxY
|
||||||
|
uKmi8pN1y288GmjkWosGMb0p3Pse1OkOyYFIbxECgYEA2ED3PSPoHWLHfKhg2BFw
|
||||||
|
yMPtern06rjKuwMNlD+mKS66Z+OsQi2EBsqomGnr1HGvYgQik0jwMcx0+Sup9/Zp
|
||||||
|
az8ebH6S4Tdxmnlwn34lhTIAF1KJS19AYvbhOydV+M+hq7Y7QxTqYsJAgEYwsozQ
|
||||||
|
0XeAzRBIiRxdcMFHP40zZIkCgYEAtgdiwo5d5iyvXEqx/5+NdM4b/ImrbaFIAb0v
|
||||||
|
MqiPpOA/+7EKlx72gJKVKh2iv4jvEUfduNEUXt77Yqo66HhfiTBVYxYwThK8E0Mq
|
||||||
|
TSKKdJsdPSThLS3qjeARpzQpWLiBZH90GxbfFL3ogIOa/UcgwRrqPc5a/yq8adSs
|
||||||
|
KGrfvXECgYEAmSMAMbqgn1aY32y5D6jiDjm4jMTsa98qKN5TmlysRNODSxhNnptu
|
||||||
|
uASA+VVgnBNZV/aHqXboKMuZNe22shI7uqd62ueTCYtiljpTB46j8TtkFx/qe4Zb
|
||||||
|
KPmcq3ACkGwwF1G3i5xfEkputKd/yqCvKvYOLqjORNHiVXt5Acby0skCgYBYkZ9s
|
||||||
|
KvllVbi9n1qclnWtr9vONO5EmYT/051zeLDr+HEpditA/L/UL36Ez4awy2AHeIBZ
|
||||||
|
vOG8h6Kpj0q6cleJ2Qqy+8jlNBhvBu8+OOBFfHPtnFQ0N3M5NR1hze+QS7YpwBou
|
||||||
|
VCKXZRAL9/0h38oAK6huCkocfh7PH7vkrpvPAQKBgCFDDtk7aBJsNcOW+aq4IEvf
|
||||||
|
nZ5hhhdelNLeN29RrJ71GwJrCG3NbhopWlCDqZ/Dd6QoEUpebqvlMGvQJBuz/QKb
|
||||||
|
ilcZlmaCS9pqIXAFK9GQ89V/xa8OibOuJUiBgShnfSQqAwQrfX1vYjtKErnjoRFs
|
||||||
|
9+zaWugLCC47Hw6QlMDa
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
`
|
19
internal/tls_provider/tls_provider.go
Normal file
19
internal/tls_provider/tls_provider.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package tlsprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDefaultTlsConfig() *tls.Config {
|
||||||
|
cert, err := tls.X509KeyPair([]byte(certificate), []byte(certificateKey))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to parse certificate and key:", err)
|
||||||
|
return &tls.Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
}
|
16
main.go
16
main.go
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -14,20 +13,9 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
config.ParseFlags()
|
config.ParseFlags()
|
||||||
|
|
||||||
if config.Config.InitialDataFilePath != "" {
|
repositories.InitializeRepository()
|
||||||
repositories.LoadStateFS(config.Config.InitialDataFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
router := api.CreateRouter()
|
go api.StartAPI()
|
||||||
if config.Config.TLS_CertificatePath == "" ||
|
|
||||||
config.Config.TLS_CertificateKey == "" {
|
|
||||||
go router.Run(fmt.Sprintf(":%d", config.Config.Port))
|
|
||||||
} else {
|
|
||||||
go router.RunTLS(
|
|
||||||
fmt.Sprintf(":%d", config.Config.Port),
|
|
||||||
config.Config.TLS_CertificatePath,
|
|
||||||
config.Config.TLS_CertificateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForExit()
|
waitForExit()
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ type SelectStmt struct {
|
|||||||
SelectItems []SelectItem
|
SelectItems []SelectItem
|
||||||
Table Table
|
Table Table
|
||||||
Filters interface{}
|
Filters interface{}
|
||||||
|
Distinct bool
|
||||||
Count int
|
Count int
|
||||||
Parameters map[string]interface{}
|
Parameters map[string]interface{}
|
||||||
OrderExpressions []OrderExpression
|
OrderExpressions []OrderExpression
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3,24 +3,32 @@ package nosql
|
|||||||
|
|
||||||
import "github.com/pikami/cosmium/parsers"
|
import "github.com/pikami/cosmium/parsers"
|
||||||
|
|
||||||
func makeSelectStmt(columns, table, whereClause interface{}, count interface{}, orderList interface{}) (parsers.SelectStmt, error) {
|
func makeSelectStmt(
|
||||||
|
columns, table,
|
||||||
|
whereClause interface{}, distinctClause interface{},
|
||||||
|
count interface{}, orderList interface{},
|
||||||
|
) (parsers.SelectStmt, error) {
|
||||||
selectStmt := parsers.SelectStmt{
|
selectStmt := parsers.SelectStmt{
|
||||||
SelectItems: columns.([]parsers.SelectItem),
|
SelectItems: columns.([]parsers.SelectItem),
|
||||||
Table: table.(parsers.Table),
|
Table: table.(parsers.Table),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := whereClause.(type) {
|
switch v := whereClause.(type) {
|
||||||
case parsers.ComparisonExpression, parsers.LogicalExpression, parsers.Constant, parsers.SelectItem:
|
case parsers.ComparisonExpression, parsers.LogicalExpression, parsers.Constant, parsers.SelectItem:
|
||||||
selectStmt.Filters = v
|
selectStmt.Filters = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if distinctClause != nil {
|
||||||
|
selectStmt.Distinct = true
|
||||||
|
}
|
||||||
|
|
||||||
if n, ok := count.(int); ok {
|
if n, ok := count.(int); ok {
|
||||||
selectStmt.Count = n
|
selectStmt.Count = n
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderExpressions, ok := orderList.([]parsers.OrderExpression); ok {
|
if orderExpressions, ok := orderList.([]parsers.OrderExpression); ok {
|
||||||
selectStmt.OrderExpressions = orderExpressions
|
selectStmt.OrderExpressions = orderExpressions
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectStmt, nil
|
return selectStmt, nil
|
||||||
}
|
}
|
||||||
@ -136,13 +144,17 @@ Input <- selectStmt:SelectStmt {
|
|||||||
return selectStmt, nil
|
return selectStmt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectStmt <- Select ws topClause:TopClause? ws columns:Selection ws
|
SelectStmt <- Select ws
|
||||||
|
distinctClause:DistinctClause? ws
|
||||||
|
topClause:TopClause? ws columns:Selection ws
|
||||||
From ws table:TableName ws
|
From ws table:TableName ws
|
||||||
whereClause:(ws Where ws condition:Condition { return condition, nil })?
|
whereClause:(ws Where ws condition:Condition { return condition, nil })?
|
||||||
orderByClause:OrderByClause? {
|
orderByClause:OrderByClause? {
|
||||||
return makeSelectStmt(columns, table, whereClause, topClause, orderByClause)
|
return makeSelectStmt(columns, table, whereClause, distinctClause, topClause, orderByClause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DistinctClause <- "DISTINCT"i
|
||||||
|
|
||||||
TopClause <- Top ws count:Integer {
|
TopClause <- Top ws count:Integer {
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,20 @@ func Test_Parse_Select(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse SELECT DISTINCT", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT DISTINCT c.id FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{Path: []string{"c", "id"}},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{Value: "c"},
|
||||||
|
Distinct: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Should parse SELECT TOP", func(t *testing.T) {
|
t.Run("Should parse SELECT TOP", func(t *testing.T) {
|
||||||
testQueryParse(
|
testQueryParse(
|
||||||
t,
|
t,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package memoryexecutor
|
package memoryexecutor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
"github.com/pikami/cosmium/parsers"
|
"github.com/pikami/cosmium/parsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,13 +36,13 @@ func (c memoryExecutorContext) array_Slice(arguments []interface{}, row RowType)
|
|||||||
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row)
|
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row)
|
||||||
|
|
||||||
if length, ok = lengthEx.(int); !ok {
|
if length, ok = lengthEx.(int); !ok {
|
||||||
fmt.Println("array_Slice - got length parameters of wrong type")
|
logger.Error("array_Slice - got length parameters of wrong type")
|
||||||
return []interface{}{}
|
return []interface{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if start, ok = startEx.(int); !ok {
|
if start, ok = startEx.(int); !ok {
|
||||||
fmt.Println("array_Slice - got start parameters of wrong type")
|
logger.Error("array_Slice - got start parameters of wrong type")
|
||||||
return []interface{}{}
|
return []interface{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ func (c memoryExecutorContext) parseArray(argument interface{}, row RowType) []i
|
|||||||
|
|
||||||
arrValue := reflect.ValueOf(ex)
|
arrValue := reflect.ValueOf(ex)
|
||||||
if arrValue.Kind() != reflect.Slice {
|
if arrValue.Kind() != reflect.Slice {
|
||||||
fmt.Println("parseArray got parameters of wrong type")
|
logger.Error("parseArray got parameters of wrong type")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
"github.com/pikami/cosmium/parsers"
|
"github.com/pikami/cosmium/parsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,6 +36,18 @@ func Execute(query parsers.SelectStmt, data []RowType) []RowType {
|
|||||||
ctx.orderBy(query.OrderExpressions, result)
|
ctx.orderBy(query.OrderExpressions, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply select
|
||||||
|
selectedData := make([]RowType, 0)
|
||||||
|
for _, row := range result {
|
||||||
|
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, row))
|
||||||
|
}
|
||||||
|
result = selectedData
|
||||||
|
|
||||||
|
// Apply distinct
|
||||||
|
if query.Distinct {
|
||||||
|
result = deduplicate(result)
|
||||||
|
}
|
||||||
|
|
||||||
// Apply result limit
|
// Apply result limit
|
||||||
if query.Count > 0 {
|
if query.Count > 0 {
|
||||||
count := func() int {
|
count := func() int {
|
||||||
@ -46,13 +59,7 @@ func Execute(query parsers.SelectStmt, data []RowType) []RowType {
|
|||||||
result = result[:count]
|
result = result[:count]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply select
|
return result
|
||||||
selectedData := make([]RowType, 0)
|
|
||||||
for _, row := range result {
|
|
||||||
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, row))
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c memoryExecutorContext) selectRow(selectItems []parsers.SelectItem, row RowType) interface{} {
|
func (c memoryExecutorContext) selectRow(selectItems []parsers.SelectItem, row RowType) interface{} {
|
||||||
@ -162,7 +169,7 @@ func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row RowTy
|
|||||||
var ok bool
|
var ok bool
|
||||||
if typedValue, ok = field.Value.(parsers.Constant); !ok {
|
if typedValue, ok = field.Value.(parsers.Constant); !ok {
|
||||||
// TODO: Handle error
|
// TODO: Handle error
|
||||||
fmt.Println("parsers.Constant has incorrect Value type")
|
logger.Error("parsers.Constant has incorrect Value type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if typedValue.Type == parsers.ConstantTypeParameterConstant &&
|
if typedValue.Type == parsers.ConstantTypeParameterConstant &&
|
||||||
@ -180,7 +187,7 @@ func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row RowTy
|
|||||||
var ok bool
|
var ok bool
|
||||||
if typedValue, ok = field.Value.(parsers.FunctionCall); !ok {
|
if typedValue, ok = field.Value.(parsers.FunctionCall); !ok {
|
||||||
// TODO: Handle error
|
// TODO: Handle error
|
||||||
fmt.Println("parsers.Constant has incorrect Value type")
|
logger.Error("parsers.Constant has incorrect Value type")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch typedValue.Type {
|
switch typedValue.Type {
|
||||||
@ -282,7 +289,7 @@ func (c memoryExecutorContext) getExpressionParameterValue(
|
|||||||
return c.getFieldValue(typedParameter, row)
|
return c.getFieldValue(typedParameter, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("getExpressionParameterValue - got incorrect parameter type")
|
logger.Error("getExpressionParameterValue - got incorrect parameter type")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -349,3 +356,23 @@ func compareValues(val1, val2 interface{}) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deduplicate(slice []RowType) []RowType {
|
||||||
|
var result []RowType
|
||||||
|
|
||||||
|
for i := 0; i < len(slice); i++ {
|
||||||
|
unique := true
|
||||||
|
for j := 0; j < len(result); j++ {
|
||||||
|
if compareValues(slice[i], result[j]) == 0 {
|
||||||
|
unique = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unique {
|
||||||
|
result = append(result, slice[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -35,6 +35,24 @@ func Test_Execute_Select(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute SELECT DISTINCT", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{Path: []string{"c", "pk"}},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{Value: "c"},
|
||||||
|
Distinct: true,
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"pk": 123},
|
||||||
|
map[string]interface{}{"pk": 456},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Should execute SELECT TOP", func(t *testing.T) {
|
t.Run("Should execute SELECT TOP", func(t *testing.T) {
|
||||||
testQueryExecute(
|
testQueryExecute(
|
||||||
t,
|
t,
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
"github.com/pikami/cosmium/parsers"
|
"github.com/pikami/cosmium/parsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ func (c memoryExecutorContext) strings_Left(arguments []interface{}, row RowType
|
|||||||
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
||||||
|
|
||||||
if length, ok = lengthEx.(int); !ok {
|
if length, ok = lengthEx.(int); !ok {
|
||||||
fmt.Println("strings_Left - got parameters of wrong type")
|
logger.Error("strings_Left - got parameters of wrong type")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +158,7 @@ func (c memoryExecutorContext) strings_Replicate(arguments []interface{}, row Ro
|
|||||||
timesEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
timesEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
||||||
|
|
||||||
if times, ok = timesEx.(int); !ok {
|
if times, ok = timesEx.(int); !ok {
|
||||||
fmt.Println("strings_Replicate - got parameters of wrong type")
|
logger.Error("strings_Replicate - got parameters of wrong type")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +191,7 @@ func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowTyp
|
|||||||
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
||||||
|
|
||||||
if length, ok = lengthEx.(int); !ok {
|
if length, ok = lengthEx.(int); !ok {
|
||||||
fmt.Println("strings_Right - got parameters of wrong type")
|
logger.Error("strings_Right - got parameters of wrong type")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,11 +220,11 @@ func (c memoryExecutorContext) strings_Substring(arguments []interface{}, row Ro
|
|||||||
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row)
|
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row)
|
||||||
|
|
||||||
if startPos, ok = startPosEx.(int); !ok {
|
if startPos, ok = startPosEx.(int); !ok {
|
||||||
fmt.Println("strings_Substring - got start parameters of wrong type")
|
logger.Error("strings_Substring - got start parameters of wrong type")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if length, ok = lengthEx.(int); !ok {
|
if length, ok = lengthEx.(int); !ok {
|
||||||
fmt.Println("strings_Substring - got length parameters of wrong type")
|
logger.Error("strings_Substring - got length parameters of wrong type")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +264,7 @@ func (c memoryExecutorContext) parseString(argument interface{}, row RowType) st
|
|||||||
return str1
|
return str1
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("StringEquals got parameters of wrong type")
|
logger.Error("StringEquals got parameters of wrong type")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user