mirror of
https://github.com/pikami/cosmium.git
synced 2025-02-17 09:47:14 +00: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'
|
||||
repository:
|
||||
owner: pikami
|
||||
name: homebrew-pikami
|
||||
name: homebrew-brew
|
||||
commit_author:
|
||||
name: pikami
|
||||
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.
|
||||
|
||||
# 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.
|
||||
|
||||
@ -23,11 +33,7 @@ Cosmium is available for the following platforms:
|
||||
Once downloaded, you can launch Cosmium using the following command:
|
||||
|
||||
```sh
|
||||
./cosmium-linux-amd64 \
|
||||
-Cert "cert.crt" \
|
||||
-CertKey "cert.key" \
|
||||
-Persist "./save.json" \
|
||||
-InitialData "./save.json"
|
||||
cosmium -Persist "./save.json"
|
||||
```
|
||||
|
||||
Connection String Example:
|
||||
@ -43,7 +49,9 @@ Once running, the explorer can be reached by navigating following URL: `https://
|
||||
|
||||
### 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
|
||||
|
||||
@ -51,8 +59,9 @@ By default, Cosmium runs on HTTP. However, if you provide an SSL certificate, it
|
||||
* **-DisableAuth**: Disable authentication
|
||||
* **-Host**: Hostname (default "localhost")
|
||||
* **-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)
|
||||
* **-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.
|
||||
|
||||
|
@ -20,7 +20,9 @@ func ParseFlags() {
|
||||
initialDataPath := flag.String("InitialData", "", "Path to JSON containing initial state")
|
||||
accountKey := flag.String("AccountKey", DefaultAccountKey, "Account key for 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")
|
||||
debug := flag.Bool("Debug", false, "Runs application in debug mode, this provides additional logging")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@ -32,6 +34,8 @@ func ParseFlags() {
|
||||
Config.InitialDataFilePath = *initialDataPath
|
||||
Config.PersistDataFilePath = *persistDataPath
|
||||
Config.DisableAuth = *disableAuthentication
|
||||
Config.DisableTls = *disableTls
|
||||
Config.Debug = *debug
|
||||
|
||||
Config.DatabaseAccount = Config.Host
|
||||
Config.DatabaseDomain = Config.Host
|
||||
|
@ -14,4 +14,6 @@ type ServerConfig struct {
|
||||
InitialDataFilePath string
|
||||
PersistDataFilePath string
|
||||
DisableAuth bool
|
||||
DisableTls bool
|
||||
Debug bool
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
"github.com/pikami/cosmium/internal/authentication"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
)
|
||||
|
||||
func Authentication() gin.HandlerFunc {
|
||||
@ -53,7 +53,7 @@ func Authentication() gin.HandlerFunc {
|
||||
params, _ := url.ParseQuery(decoded)
|
||||
clientSignature := strings.Replace(params.Get("sig"), " ", "+", -1)
|
||||
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{
|
||||
"code": "Unauthorized",
|
||||
"message": "Wrong signature.",
|
||||
|
@ -2,10 +2,10 @@ package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
)
|
||||
|
||||
func RequestLogger() gin.HandlerFunc {
|
||||
@ -16,7 +16,7 @@ func RequestLogger() gin.HandlerFunc {
|
||||
|
||||
bodyStr := readBody(rdr1)
|
||||
if bodyStr != "" {
|
||||
fmt.Println(bodyStr)
|
||||
logger.Debug(bodyStr)
|
||||
}
|
||||
|
||||
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-item-count", fmt.Sprintf("%d", len(partitionKeyRanges)))
|
||||
|
||||
collectionRid := collectionId
|
||||
collection, _ := repositories.GetCollection(databaseId, collectionId)
|
||||
if collection.ResourceID != "" {
|
||||
collectionRid = collection.ResourceID
|
||||
}
|
||||
|
||||
c.IndentedJSON(http.StatusOK, gin.H{
|
||||
"_rid": collection.ResourceID,
|
||||
"_rid": collectionRid,
|
||||
"_count": len(partitionKeyRanges),
|
||||
"PartitionKeyRanges": partitionKeyRanges,
|
||||
})
|
||||
|
@ -1,12 +1,42 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/internal/constants"
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
"github.com/pikami/cosmium/api/handlers"
|
||||
"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 {
|
||||
router := gin.Default()
|
||||
|
||||
router.Use(middleware.RequestLogger())
|
||||
if config.Config.Debug {
|
||||
router.Use(middleware.RequestLogger())
|
||||
}
|
||||
|
||||
router.Use(middleware.Authentication())
|
||||
|
||||
router.GET("/dbs/:databaseId/colls/:collId/pkranges", handlers.GetPartitionKeyRanges)
|
||||
@ -43,3 +52,43 @@ func CreateRouter() *gin.Engine {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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{
|
||||
"partitionedQueryExecutionInfoVersion": 2,
|
||||
"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
|
||||
func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repositorymodels.PartitionKeyRange, repositorymodels.RepositoryStatus) {
|
||||
var ok bool
|
||||
var database repositorymodels.Database
|
||||
var collection repositorymodels.Collection
|
||||
if database, ok = storeState.Databases[databaseId]; !ok {
|
||||
return make([]repositorymodels.PartitionKeyRange, 0), repositorymodels.StatusNotFound
|
||||
databaseRid := databaseId
|
||||
collectionRid := collectionId
|
||||
var timestamp int64 = 0
|
||||
|
||||
if database, ok := storeState.Databases[databaseId]; !ok {
|
||||
databaseRid = database.ResourceID
|
||||
}
|
||||
|
||||
if collection, ok = storeState.Collections[databaseId][collectionId]; !ok {
|
||||
return make([]repositorymodels.PartitionKeyRange, 0), repositorymodels.StatusNotFound
|
||||
if collection, ok := storeState.Collections[databaseId][collectionId]; !ok {
|
||||
collectionRid = collection.ResourceID
|
||||
timestamp = collection.TimeStamp
|
||||
}
|
||||
|
||||
pkrResourceId := resourceid.NewCombined(database.ResourceID, collection.ResourceID, resourceid.New())
|
||||
pkrSelf := fmt.Sprintf("dbs/%s/colls/%s/pkranges/%s/", database.ResourceID, collection.ResourceID, pkrResourceId)
|
||||
pkrResourceId := resourceid.NewCombined(databaseRid, collectionRid, resourceid.New())
|
||||
pkrSelf := fmt.Sprintf("dbs/%s/colls/%s/pkranges/%s/", databaseRid, collectionRid, pkrResourceId)
|
||||
etag := fmt.Sprintf("\"%s\"", uuid.New())
|
||||
|
||||
return []repositorymodels.PartitionKeyRange{
|
||||
@ -37,7 +39,7 @@ func GetPartitionKeyRanges(databaseId string, collectionId string) ([]repository
|
||||
ThroughputFraction: 1,
|
||||
Status: "online",
|
||||
Parents: []interface{}{},
|
||||
TimeStamp: collection.TimeStamp,
|
||||
TimeStamp: timestamp,
|
||||
Lsn: 17,
|
||||
},
|
||||
}, repositorymodels.StatusOk
|
||||
|
@ -2,11 +2,12 @@ package repositories
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/pikami/cosmium/api/config"
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
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),
|
||||
}
|
||||
|
||||
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) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading state JSON file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var state repositorymodels.State
|
||||
if err := json.Unmarshal(data, &state); err != nil {
|
||||
log.Fatalf("Error unmarshalling state JSON: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Loaded state:")
|
||||
fmt.Printf("Databases: %d\n", getLength(state.Databases))
|
||||
fmt.Printf("Collections: %d\n", getLength(state.Collections))
|
||||
fmt.Printf("Documents: %d\n", getLength(state.Documents))
|
||||
logger.Info("Loaded state:")
|
||||
logger.Infof("Databases: %d\n", getLength(state.Databases))
|
||||
logger.Infof("Collections: %d\n", getLength(state.Collections))
|
||||
logger.Infof("Documents: %d\n", getLength(state.Documents))
|
||||
|
||||
storeState = state
|
||||
|
||||
@ -43,16 +68,16 @@ func LoadStateFS(filePath string) {
|
||||
func SaveStateFS(filePath string) {
|
||||
data, err := json.MarshalIndent(storeState, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to save state: %v\n", err)
|
||||
logger.Errorf("Failed to save state: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
os.WriteFile(filePath, data, os.ModePerm)
|
||||
|
||||
fmt.Println("Saved state:")
|
||||
fmt.Printf("Databases: %d\n", getLength(storeState.Databases))
|
||||
fmt.Printf("Collections: %d\n", getLength(storeState.Collections))
|
||||
fmt.Printf("Documents: %d\n", getLength(storeState.Documents))
|
||||
logger.Info("Saved state:")
|
||||
logger.Infof("Databases: %d\n", getLength(storeState.Databases))
|
||||
logger.Infof("Collections: %d\n", getLength(storeState.Collections))
|
||||
logger.Infof("Documents: %d\n", getLength(storeState.Documents))
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
@ -14,20 +13,9 @@ import (
|
||||
func main() {
|
||||
config.ParseFlags()
|
||||
|
||||
if config.Config.InitialDataFilePath != "" {
|
||||
repositories.LoadStateFS(config.Config.InitialDataFilePath)
|
||||
}
|
||||
repositories.InitializeRepository()
|
||||
|
||||
router := api.CreateRouter()
|
||||
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)
|
||||
}
|
||||
go api.StartAPI()
|
||||
|
||||
waitForExit()
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ type SelectStmt struct {
|
||||
SelectItems []SelectItem
|
||||
Table Table
|
||||
Filters interface{}
|
||||
Distinct bool
|
||||
Count int
|
||||
Parameters map[string]interface{}
|
||||
OrderExpressions []OrderExpression
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,24 +3,32 @@ package nosql
|
||||
|
||||
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{
|
||||
SelectItems: columns.([]parsers.SelectItem),
|
||||
Table: table.(parsers.Table),
|
||||
}
|
||||
|
||||
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 n, ok := count.(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
|
||||
}
|
||||
|
||||
return selectStmt, nil
|
||||
}
|
||||
@ -136,13 +144,17 @@ Input <- selectStmt:SelectStmt {
|
||||
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
|
||||
whereClause:(ws Where ws condition:Condition { return condition, nil })?
|
||||
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 {
|
||||
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) {
|
||||
testQueryParse(
|
||||
t,
|
||||
|
@ -1,9 +1,9 @@
|
||||
package memoryexecutor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
"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)
|
||||
|
||||
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{}{}
|
||||
}
|
||||
}
|
||||
|
||||
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{}{}
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ func (c memoryExecutorContext) parseArray(argument interface{}, row RowType) []i
|
||||
|
||||
arrValue := reflect.ValueOf(ex)
|
||||
if arrValue.Kind() != reflect.Slice {
|
||||
fmt.Println("parseArray got parameters of wrong type")
|
||||
logger.Error("parseArray got parameters of wrong type")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
"github.com/pikami/cosmium/parsers"
|
||||
)
|
||||
|
||||
@ -35,6 +36,18 @@ func Execute(query parsers.SelectStmt, data []RowType) []RowType {
|
||||
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
|
||||
if query.Count > 0 {
|
||||
count := func() int {
|
||||
@ -46,13 +59,7 @@ func Execute(query parsers.SelectStmt, data []RowType) []RowType {
|
||||
result = result[:count]
|
||||
}
|
||||
|
||||
// Apply select
|
||||
selectedData := make([]RowType, 0)
|
||||
for _, row := range result {
|
||||
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, row))
|
||||
}
|
||||
|
||||
return selectedData
|
||||
return result
|
||||
}
|
||||
|
||||
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
|
||||
if typedValue, ok = field.Value.(parsers.Constant); !ok {
|
||||
// TODO: Handle error
|
||||
fmt.Println("parsers.Constant has incorrect Value type")
|
||||
logger.Error("parsers.Constant has incorrect Value type")
|
||||
}
|
||||
|
||||
if typedValue.Type == parsers.ConstantTypeParameterConstant &&
|
||||
@ -180,7 +187,7 @@ func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row RowTy
|
||||
var ok bool
|
||||
if typedValue, ok = field.Value.(parsers.FunctionCall); !ok {
|
||||
// TODO: Handle error
|
||||
fmt.Println("parsers.Constant has incorrect Value type")
|
||||
logger.Error("parsers.Constant has incorrect Value type")
|
||||
}
|
||||
|
||||
switch typedValue.Type {
|
||||
@ -282,7 +289,7 @@ func (c memoryExecutorContext) getExpressionParameterValue(
|
||||
return c.getFieldValue(typedParameter, row)
|
||||
}
|
||||
|
||||
fmt.Println("getExpressionParameterValue - got incorrect parameter type")
|
||||
logger.Error("getExpressionParameterValue - got incorrect parameter type")
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -349,3 +356,23 @@ func compareValues(val1, val2 interface{}) int {
|
||||
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) {
|
||||
testQueryExecute(
|
||||
t,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
"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)
|
||||
|
||||
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 ""
|
||||
}
|
||||
|
||||
@ -157,7 +158,7 @@ func (c memoryExecutorContext) strings_Replicate(arguments []interface{}, row Ro
|
||||
timesEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
||||
|
||||
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 ""
|
||||
}
|
||||
|
||||
@ -190,7 +191,7 @@ func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowTyp
|
||||
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row)
|
||||
|
||||
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 ""
|
||||
}
|
||||
|
||||
@ -219,11 +220,11 @@ func (c memoryExecutorContext) strings_Substring(arguments []interface{}, row Ro
|
||||
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row)
|
||||
|
||||
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 ""
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
|
||||
@ -263,7 +264,7 @@ func (c memoryExecutorContext) parseString(argument interface{}, row RowType) st
|
||||
return str1
|
||||
}
|
||||
|
||||
fmt.Println("StringEquals got parameters of wrong type")
|
||||
logger.Error("StringEquals got parameters of wrong type")
|
||||
return ""
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user