--------------------------------------------------------------------------------
/cloud/api/utils/decode.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/json"
6 | )
7 |
8 | // Decode is a function that decodes a base64-encoded string and then decodes the resulting JSON into a value of type T.
9 | // Parameters:
10 | // - s (string): The base64-encoded string to decode.
11 | // - v (*T): A pointer to a value of type T to store the decoded JSON.
12 | //
13 | // Returns:
14 | // - error: An error message if the decoding fails, or nil if the decoding is successful.
15 | func Decode[T any](s string, v *T) error {
16 | if data, err := base64.StdEncoding.DecodeString(s); err != nil {
17 | return err
18 | } else if err := json.Unmarshal(data, v); err != nil {
19 | return err
20 | }
21 | return nil
22 | }
23 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/indices.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // FTSequenceIndices is a handler function that returns a fiber context handler function for sequencing the full-text storage indices.
9 | // Parameters:
10 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the sequencing fails.
15 | func FTSequenceIndices(_ *utils.Params, c *hermes.Cache) []byte {
16 | c.FTSequenceIndices()
17 | return utils.Success("null")
18 | }
19 |
--------------------------------------------------------------------------------
/utils/json.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "os"
6 | )
7 |
8 | // ReadJson is a generic function that reads a JSON file and unmarshals its contents into a provided value of type T.
9 | // Parameters:
10 | // - file (string): The path to the JSON file to read.
11 | //
12 | // Returns:
13 | // - T: The unmarshalled value of type T.
14 | // - error: An error if the file cannot be read or the unmarshalling fails, or nil if successful.
15 | func ReadJson[T any](file string) (T, error) {
16 | var v T
17 |
18 | // Read the json data
19 | if data, err := os.ReadFile(file); err != nil {
20 | return *new(T), err
21 | } else if err := json.Unmarshal(data, &v); err != nil {
22 | return *new(T), err
23 | }
24 | return v, nil
25 | }
26 |
--------------------------------------------------------------------------------
/cloud/socket/utils/decode.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/json"
6 | )
7 |
8 | // Decode is a generic function that decodes a base64-encoded string and unmarshals the resulting JSON into a provided value.
9 | // Parameters:
10 | // - s (string): The base64-encoded string to decode and unmarshal.
11 | // - v (*T): A pointer to a value of type T to unmarshal the JSON into.
12 | //
13 | // Returns:
14 | // - error: An error if the decoding or unmarshaling fails, or nil if successful.
15 | func Decode[T any](s string, v *T) error {
16 | if data, err := base64.StdEncoding.DecodeString(s); err != nil {
17 | return err
18 | } else if err := json.Unmarshal(data, v); err != nil {
19 | return err
20 | }
21 | return nil
22 | }
23 |
--------------------------------------------------------------------------------
/testing/fulltext/insert_key_merge.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | )
8 |
9 | func insert_key_merge() {
10 | var cache *hermes.Cache = hermes.InitCache()
11 |
12 | // Initialize the FT cache
13 | cache.FTInit(-1, -1, 3)
14 | cache.Set("user_id1", map[string]any{
15 | "name": cache.WithFT("tristan"),
16 | "age": 17,
17 | })
18 | cache.Set("user_id2", map[string]any{
19 | "name": cache.WithFT("tris is cool"),
20 | "age": 17,
21 | })
22 |
23 | // Search for tris
24 | var result, _ = cache.Search(hermes.SearchParams{
25 | Query: "tris is",
26 | Limit: 100,
27 | Strict: false,
28 | })
29 | fmt.Println(result)
30 |
31 | // print cache info
32 | fmt.Println(cache.InfoForTesting())
33 | }
34 |
--------------------------------------------------------------------------------
/cloud/api/handlers/length.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // Length is a handler function that returns a fiber context handler function for getting the length of the cache.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the length of the cache and returns a success message with the length or an error message if the retrieval fails.
15 | func Length(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | return ctx.Send(utils.Success(c.Length()))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/keys.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // Keys is a method of the Cache struct that returns all the keys in the cache.
4 | // This function is thread-safe.
5 | //
6 | // Returns:
7 | // - A slice of strings containing all the keys in the cache.
8 | func (c *Cache) Keys() []string {
9 | c.mutex.RLock()
10 | defer c.mutex.RUnlock()
11 | return c.keys()
12 | }
13 |
14 | // keys is a method of the Cache struct that returns all the keys in the cache.
15 | // This function is not thread-safe, and should only be called from an exported function.
16 | //
17 | // Returns:
18 | // - A slice of strings containing all the keys in the cache.
19 | func (c *Cache) keys() []string {
20 | keys := make([]string, 0, len(c.data))
21 | for key := range c.data {
22 | keys = append(keys, key)
23 | }
24 | return keys
25 | }
26 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/keys.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | utils "github.com/realTristan/hermes/cloud/socket/utils"
8 | )
9 |
10 | // Keys is a handler function that returns a fiber context handler function for retrieving all keys from the cache.
11 | // Parameters:
12 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - []byte: A JSON-encoded byte slice containing all keys from the cache or an error message if the retrieval fails.
17 | func Keys(_ *utils.Params, c *hermes.Cache) []byte {
18 | if keys, err := json.Marshal(c.Keys()); err != nil {
19 | return utils.Error(err)
20 | } else {
21 | return keys
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/cloud/wrappers/npm/main.ts:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | To do:
4 |
5 |
6 |
7 |
8 |
9 | */
10 |
11 | // Get data from the cache function
12 | export const Get = (key: string): any => {
13 | // GET
14 | return fetch(`${host}/get`)
15 | .then(res => res.json())
16 | .then(res => res);
17 | }
18 |
19 | // Set data in the cache function
20 | export const Set = (key: string, value: map[string]interface{}): any => {
21 | // POST
22 | let value = base64encode(jsonEncode(value))
23 | return fetch(`${host}/set?key=${key}&value=${value}`)
24 | .then(res => res.json())
25 | .then(res => res);
26 | }
27 |
28 | // Delete key from the cache
29 | export const Delete = (key: string): any => {
30 | // DELETE
31 | return fetch(`${host}/delete?key=${key}`)
32 | .then(res => res.json())
33 | .then(res => res);
34 | }
--------------------------------------------------------------------------------
/cloud/socket/handlers/values.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | utils "github.com/realTristan/hermes/cloud/socket/utils"
8 | )
9 |
10 | // Values is a handler function that returns a fiber context handler function for retrieving all values from the cache.
11 | // Parameters:
12 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - []byte: A JSON-encoded byte slice containing all values from the cache or an error message if the retrieval fails.
17 | func Values(_ *utils.Params, c *hermes.Cache) []byte {
18 | if values, err := json.Marshal(c.Values()); err != nil {
19 | return utils.Error(err)
20 | } else {
21 | return values
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/cloud/api/handlers/indices.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // FTSequenceIndices is a handler function that returns a fiber context handler function for sequencing the full-text storage indices.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sequences the full-text storage indices and returns a success message or an error message if the sequencing fails.
15 | func FTSequenceIndices(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | c.FTSequenceIndices()
18 | return ctx.Send(utils.Success("null"))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/nocache/fulltext.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | /*
8 | FullText is a struct that represents a full text search cache. It has the following fields:
9 | - mutex (*sync.RWMutex): a pointer to a read-write mutex used to synchronize access to the cache
10 | - storage (map[string]any): a map where the keys are words and the values are arrays of integers representing the indices of the data items that contain the word
11 | - words ([]string): a slice of strings representing all the unique words in the cache
12 | - data ([]map[string]any): a slice of maps representing the data items in the cache, where the keys are the names of the fields and the values are the field values
13 | */
14 | type FullText struct {
15 | mutex *sync.RWMutex
16 | storage map[string]any
17 | words []string
18 | data []map[string]any
19 | }
20 |
--------------------------------------------------------------------------------
/cloud/api/utils/http.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Error is a function that returns an error message with the provided error.
8 | // Parameters:
9 | // - err (T): The error to include in the error message.
10 | //
11 | // Returns:
12 | // - []byte: A byte slice containing the error message with the provided error.
13 | func Error[T any](err T) []byte {
14 | return []byte(fmt.Sprintf(`{"success":false,"error":"%v"}`, err))
15 | }
16 |
17 | // Success is a function that returns a success message with the provided data.
18 | // Parameters:
19 | // - v (T): The data to include in the success message.
20 | //
21 | // Returns:
22 | // - []byte: A byte slice containing the success message with the provided data.
23 | func Success[T any](v T) []byte {
24 | return []byte(fmt.Sprintf(`{"success":true,"data":%v}`, v))
25 | }
26 |
--------------------------------------------------------------------------------
/cloud/socket/utils/http.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Error is a function that returns an error message with the provided error.
8 | // Parameters:
9 | // - err (T): The error to include in the error message.
10 | //
11 | // Returns:
12 | // - []byte: A byte slice containing the error message with the provided error.
13 | func Error[T any](err T) []byte {
14 | return []byte(fmt.Sprintf(`{"success":false,"error":"%v"}`, err))
15 | }
16 |
17 | // Success is a function that returns a success message with the provided data.
18 | // Parameters:
19 | // - v (T): The data to include in the success message.
20 | //
21 | // Returns:
22 | // - []byte: A byte slice containing the success message with the provided data.
23 | func Success[T any](v T) []byte {
24 | return []byte(fmt.Sprintf(`{"success":true,"data":%v}`, v))
25 | }
26 |
--------------------------------------------------------------------------------
/values.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // Values is a method of the Cache struct that gets all the values in the cache.
4 | // This function is thread-safe.
5 | //
6 | // Returns:
7 | // - A slice of map[string]any representing all the values in the cache.
8 | func (c *Cache) Values() []map[string]any {
9 | c.mutex.RLock()
10 | defer c.mutex.RUnlock()
11 | return c.values()
12 | }
13 |
14 | // values is a method of the Cache struct that returns all the values in the cache.
15 | // This function is not thread-safe, and should only be called from an exported function.
16 | //
17 | // Returns:
18 | // - A slice of map[string]any representing all the values in the cache.
19 | func (c *Cache) values() []map[string]any {
20 | values := make([]map[string]any, 0, len(c.data))
21 | for _, value := range c.data {
22 | values = append(values, value)
23 | }
24 | return values
25 | }
26 |
--------------------------------------------------------------------------------
/cache.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // Cache is a struct that represents an in-memory cache of key-value pairs.
8 | // The cache can be used to store arbitrary data, and supports concurrent access through a mutex.
9 | // Additionally, the cache can be configured to support full-text search using a FullText index.
10 | // Fields:
11 | // - data (map[string]map[string]any): A map that stores the data in the cache. The keys of the map are strings that represent the cache keys, and the values are sub-maps that store the actual data under string keys.
12 | // - mutex (*sync.RWMutex): A RWMutex that guards access to the cache data.
13 | // - ft (*FullText): A FullText index that can be used for full-text search. If nil, full-text search is disabled.
14 | type Cache struct {
15 | data map[string]map[string]any
16 | mutex *sync.RWMutex
17 | ft *FullText
18 | }
19 |
--------------------------------------------------------------------------------
/cloud/wrappers/python/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = hermescloud
3 | version = 0.0.6
4 | author = Tristan Simpson
5 | author_email = heytristaann@gmail.com
6 | description = Extremely fast full-text-search algorithm and caching system
7 | long_description = file: README.md
8 | long_description_content_type = text/markdown
9 | url = https://github.com/realTristan/Hermes
10 | project_urls =
11 | Bug Tracker = https://github.com/realTristan/Hermes/issues
12 | classifiers =
13 | Programming Language :: Python :: 3
14 | License :: OSI Approved :: MIT License
15 | Operating System :: OS Independent
16 |
17 | [options]
18 | package_dir =
19 | = src
20 | packages = find:
21 | python_requires = >=3.6
22 |
23 | [options.packages.find]
24 | where = src
25 |
26 | # python3 -m pip install --upgrade build
27 | # python3 -m pip install --upgrade twine
28 | # python3 -m build
29 | # python3 -m twine upload dist/*
--------------------------------------------------------------------------------
/cloud/api/handlers/keys.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | hermes "github.com/realTristan/hermes"
8 | utils "github.com/realTristan/hermes/cloud/api/utils"
9 | )
10 |
11 | // Keys is a handler function that returns a fiber context handler function for getting all the keys from the cache.
12 | // Parameters:
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets all the keys from the cache and returns a JSON-encoded string of the keys or an error message if the retrieval or encoding fails.
17 | func Keys(c *hermes.Cache) func(ctx *fiber.Ctx) error {
18 | return func(ctx *fiber.Ctx) error {
19 | if keys, err := json.Marshal(c.Keys()); err != nil {
20 | return ctx.Send(utils.Error(err))
21 | } else {
22 | return ctx.Send(keys)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/cloud/api/handlers/values.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | hermes "github.com/realTristan/hermes"
8 | utils "github.com/realTristan/hermes/cloud/api/utils"
9 | )
10 |
11 | // Values is a handler function that returns a fiber context handler function for getting all values from the cache.
12 | // Parameters:
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets all values from the cache and returns a JSON-encoded string of the values or an error message if the retrieval fails.
17 | func Values(c *hermes.Cache) func(ctx *fiber.Ctx) error {
18 | return func(ctx *fiber.Ctx) error {
19 | if values, err := json.Marshal(c.Values()); err != nil {
20 | return ctx.Send(utils.Error(err))
21 | } else {
22 | return ctx.Send(values)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/testing/fulltext/clean.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | )
8 |
9 | func clean() {
10 | var cache *hermes.Cache = hermes.InitCache()
11 |
12 | // Test CleanFT()
13 | var data = map[string]any{
14 | "name": "tristan",
15 | "age": 17,
16 | }
17 |
18 | // Set data
19 | cache.Set("user_id1", data)
20 | cache.Set("user_id1", data)
21 | cache.Set("user_id2", data)
22 |
23 | // Initialize the FT cache
24 | cache.FTInit(-1, -1, 3)
25 |
26 | // Search for a word in the cache
27 | var result, _ = cache.SearchOneWord(hermes.SearchParams{
28 | Query: "tristan",
29 | Limit: 100,
30 | Strict: false,
31 | })
32 | fmt.Println(result)
33 |
34 | // Clean
35 | cache.FTClean()
36 |
37 | // Search for a word in the cache
38 | result, _ = cache.SearchOneWord(hermes.SearchParams{
39 | Query: "tristan",
40 | Limit: 100,
41 | Strict: false,
42 | })
43 | fmt.Println(result)
44 | }
45 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/delete.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // Delete is a handler function that returns a fiber context handler function for deleting a key from the cache.
9 | // Parameters:
10 | // - p (*utils.Params): A pointer to a utils.Params struct.
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the key is not provided or the deletion fails.
15 | func Delete(p *utils.Params, c *hermes.Cache) []byte {
16 | // Get the key from the query
17 | var (
18 | key string
19 | err error
20 | )
21 | if key, err = utils.GetKeyParam(p); err != nil {
22 | return utils.Error("key not provided")
23 | }
24 |
25 | // Delete the key from the cache
26 | c.Delete(key)
27 | return utils.Success("null")
28 | }
29 |
--------------------------------------------------------------------------------
/exists.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // Exists is a method of the Cache struct that checks if a key exists in the cache.
4 | // This method is thread-safe.
5 | //
6 | // Parameters:
7 | // - key: A string representing the key to check for existence in the cache.
8 | //
9 | // Returns:
10 | // - A boolean value indicating whether the key exists in the cache or not.
11 | func (c *Cache) Exists(key string) bool {
12 | c.mutex.RLock()
13 | defer c.mutex.RUnlock()
14 | return c.exists(key)
15 | }
16 |
17 | // exists is a method of the Cache struct that checks if a key exists in the cache.
18 | // This method is not thread-safe and should only be called from an exported function.
19 | //
20 | // Parameters:
21 | // - key: A string representing the key to check for existence in the cache.
22 | //
23 | // Returns:
24 | // - A boolean value indicating whether the key exists in the cache or not.
25 | func (c *Cache) exists(key string) bool {
26 | _, ok := c.data[key]
27 | return ok
28 | }
29 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/exists.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // Exists is a handler function that returns a fiber context handler function for checking if a key exists in the cache.
9 | // Parameters:
10 | // - p (*utils.Params): A pointer to a utils.Params struct.
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - []byte: A JSON-encoded byte slice containing a boolean value indicating whether the key exists in the cache or an error message if the key is not provided.
15 | func Exists(p *utils.Params, c *hermes.Cache) []byte {
16 | // Get the key from the query
17 | var (
18 | key string
19 | err error
20 | )
21 | if key, err = utils.GetKeyParam(p); err != nil {
22 | return utils.Error("key not provided")
23 | }
24 |
25 | // Return whether the key exists
26 | return utils.Success(c.Exists(key))
27 | }
28 |
--------------------------------------------------------------------------------
/website/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { AppRoutingModule } from './app-routing.module';
5 | import { AppComponent } from './app.component';
6 | import { TerminalComponent } from './terminal/terminal.component';
7 | import { NavbarComponent } from './navbar/navbar.component';
8 | import { NavbarButtonComponent } from './navbar-button/navbar-button.component';
9 | import { CodeExampleComponent } from './code-example/code-example.component';
10 | import { LaptopComponent } from './laptop/laptop.component';
11 |
12 | @NgModule({
13 | declarations: [
14 | AppComponent,
15 | TerminalComponent,
16 | NavbarComponent,
17 | NavbarButtonComponent,
18 | CodeExampleComponent,
19 | LaptopComponent,
20 | ],
21 | imports: [
22 | BrowserModule,
23 | AppRoutingModule
24 | ],
25 | providers: [],
26 | bootstrap: [AppComponent]
27 | })
28 | export class AppModule { }
29 |
--------------------------------------------------------------------------------
/cloud/api/handlers/delete.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // Delete is a handler function that returns a fiber context handler function for deleting a key from the cache.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that deletes a key from the cache and returns a success message or an error message if the key is not provided.
15 | func Delete(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | // Get the key from the query
18 | var key string
19 | if key = ctx.Query("key"); len(key) == 0 {
20 | return ctx.Send(utils.Error("key not provided"))
21 | }
22 |
23 | // Delete the key from the cache
24 | c.Delete(key)
25 | return ctx.Send(utils.Success("null"))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/basic/basic.go:
--------------------------------------------------------------------------------
1 | // ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Run Command: go run .
4 | //
5 | // Host URL: http://localhost:8000/courses?q=computer&limit=100&strict=false
6 | //
7 | // ////////////////////////////////////////////////////////////////////////////
8 | package main
9 |
10 | import (
11 | "fmt"
12 | "time"
13 |
14 | hermes "github.com/realTristan/hermes"
15 | )
16 |
17 | // Initialize the cache
18 | var cache *hermes.Cache = hermes.InitCache()
19 |
20 | func main() {
21 | // Initialize the full-text cache
22 | cache.FTInitWithJson("../../testing/data/data_hash.json", -1, -1, 3)
23 |
24 | // Search for a word in the cache
25 | var startTime time.Time = time.Now()
26 | var res, _ = cache.Search(hermes.SearchParams{
27 | Query: "computer",
28 | Limit: 100,
29 | Strict: false,
30 | })
31 | var duration time.Duration = time.Since(startTime)
32 | fmt.Println("Search took", duration)
33 | fmt.Println("Search results:", len(res))
34 | }
35 |
--------------------------------------------------------------------------------
/get.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // Get is a method of the Cache struct that retrieves the value associated with the given key from the cache.
4 | // This method is thread-safe.
5 | //
6 | // Parameters:
7 | // - key: A string representing the key to retrieve the value for.
8 | //
9 | // Returns:
10 | // - A map[string]any representing the value associated with the given key in the cache.
11 | func (c *Cache) Get(key string) map[string]any {
12 | c.mutex.RLock()
13 | defer c.mutex.RUnlock()
14 | return c.get(key)
15 | }
16 |
17 | // get is a method of the Cache struct that retrieves the value associated with the given key from the cache.
18 | // This method is not thread-safe and should only be called from an exported function.
19 | //
20 | // Parameters:
21 | // - key: A string representing the key to retrieve the value for.
22 | //
23 | // Returns:
24 | // - A map[string]any representing the value associated with the given key in the cache.
25 | func (c *Cache) get(key string) map[string]any {
26 | return c.data[key]
27 | }
28 |
--------------------------------------------------------------------------------
/cloud/wrappers/python/src/test_base.py:
--------------------------------------------------------------------------------
1 | from hermescloud import Cache
2 | import time
3 |
4 | # Create a new cache instance
5 | cache = Cache("localhost:3000")
6 |
7 | def main():
8 | # Initialize the full-text search engine
9 | print(cache.ft_init(-1, -1))
10 |
11 | # Set a value
12 | cache.set("user_id", {
13 | "name": {
14 | "$hermes.full_text": True,
15 | "$hermes.value": "tristan"
16 | }
17 | })
18 |
19 | # Get a value
20 | print(cache.get("user_id"))
21 |
22 | # Track the start time
23 | start_time = time.time()
24 |
25 | # Search for a value
26 | print(cache.ft_search("tristan", False, 100, {
27 | "name": True
28 | }))
29 |
30 | # Print the duration (average: 0.0006s)
31 | print(f"Duration: {time.time() - start_time}s")
32 |
33 | # Delete a value
34 | print(cache.delete("user_id"))
35 |
36 | # Get a value
37 | print(cache.get("user_id"))
38 |
39 | # Run the main function
40 | if __name__ == "__main__":
41 | main()
--------------------------------------------------------------------------------
/cloud/app/server/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | utils "hermes/utils"
8 |
9 | "github.com/gofiber/fiber/v2"
10 | hermes "github.com/realTristan/hermes"
11 | Socket "github.com/realTristan/hermes/cloud/socket"
12 | )
13 |
14 | // Main function
15 | func main() {
16 | // Verify that the user is trying to serve the cache
17 | if len(os.Args) < 1 || os.Args[1] != "serve" {
18 | panic("incorrect usage. example: ./hermes serve -p {port}")
19 | }
20 |
21 | // Get the arg data
22 | var args, err = utils.GetArgData(os.Args)
23 | if err != nil || args.Port() == nil {
24 | panic("incorrect usage. example: ./hermes serve -p {port}")
25 | }
26 |
27 | // Get the port and json file
28 | var cache *hermes.Cache = hermes.InitCache()
29 |
30 | // Initialize a new fiber app
31 | var app *fiber.App = fiber.New(fiber.Config{
32 | Prefork: false,
33 | ServerHeader: "hermes",
34 | })
35 | Socket.SetRouter(app, cache)
36 |
37 | // Listen on the port
38 | log.Fatal(app.Listen(args.Port().(string)))
39 | }
40 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/cloud/api/handlers/exists.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // Exists is a handler function that returns a fiber context handler function for checking if a key exists in the cache.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that checks if a key exists in the cache and returns a success message with a boolean value indicating whether the key exists or an error message if the key is not provided.
15 | func Exists(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | // Get the key from the query
18 | var key string
19 | if key = ctx.Query("key"); len(key) == 0 {
20 | return ctx.Send(utils.Error("key not provided"))
21 | }
22 |
23 | // Return whether the key exists
24 | return ctx.Send(utils.Success(c.Exists(key)))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/realTristan/hermes
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gofiber/fiber/v2 v2.45.0
7 | github.com/gofiber/websocket/v2 v2.2.0
8 | )
9 |
10 | require (
11 | github.com/andybalholm/brotli v1.0.5 // indirect
12 | github.com/fasthttp/websocket v1.5.3 // indirect
13 | github.com/google/uuid v1.3.0 // indirect
14 | github.com/klauspost/compress v1.16.5 // indirect
15 | github.com/mattn/go-colorable v0.1.13 // indirect
16 | github.com/mattn/go-isatty v0.0.18 // indirect
17 | github.com/mattn/go-runewidth v0.0.14 // indirect
18 | github.com/philhofer/fwd v1.1.2 // indirect
19 | github.com/rivo/uniseg v0.2.0 // indirect
20 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
21 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
22 | github.com/tinylib/msgp v1.1.8 // indirect
23 | github.com/valyala/bytebufferpool v1.0.0 // indirect
24 | github.com/valyala/fasthttp v1.47.0 // indirect
25 | github.com/valyala/tcplisten v1.0.0 // indirect
26 | golang.org/x/sys v0.8.0 // indirect
27 | )
28 |
--------------------------------------------------------------------------------
/nocache/examples/basic/basic.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | hermes "github.com/realTristan/hermes/nocache"
8 | )
9 |
10 | // Main function
11 | func main() {
12 | // Define variables
13 | var (
14 | // Initialize the full text
15 | ft, _ = hermes.InitWithJson("../../../testing/data/data_array.json", 3)
16 |
17 | // Track the start time
18 | start time.Time = time.Now()
19 |
20 | // Search for a word in the cache
21 | // @params: query, limit, strict
22 | res, _ = ft.Search(hermes.SearchParams{
23 | Query: "computer",
24 | Limit: 100,
25 | Strict: false,
26 | })
27 | )
28 |
29 | // Print the duration
30 | fmt.Printf("\nFound %v results in %v", len(res), time.Since(start))
31 |
32 | // Search in values with key
33 | var (
34 | // Track the start time
35 | start2 time.Time = time.Now()
36 |
37 | // Search for a word in the cache
38 | res2, _ = ft.SearchWithKey("CS", "title", 100)
39 | )
40 |
41 | // Print the duration
42 | fmt.Printf("\nFound %v results in %v", len(res2), time.Since(start2))
43 | }
44 |
--------------------------------------------------------------------------------
/cloud/app/server/go.mod:
--------------------------------------------------------------------------------
1 | module hermes
2 |
3 | go 1.20
4 |
5 | require github.com/realTristan/Hermes v1.6.7
6 |
7 | require (
8 | github.com/andybalholm/brotli v1.0.5 // indirect
9 | github.com/fasthttp/websocket v1.5.3 // indirect
10 | github.com/gofiber/fiber/v2 v2.45.0
11 | github.com/gofiber/websocket/v2 v2.2.0 // indirect
12 | github.com/google/uuid v1.3.0 // indirect
13 | github.com/klauspost/compress v1.16.5 // indirect
14 | github.com/mattn/go-colorable v0.1.13 // indirect
15 | github.com/mattn/go-isatty v0.0.18 // indirect
16 | github.com/mattn/go-runewidth v0.0.14 // indirect
17 | github.com/philhofer/fwd v1.1.2 // indirect
18 | github.com/rivo/uniseg v0.2.0 // indirect
19 | github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
20 | github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
21 | github.com/tinylib/msgp v1.1.8 // indirect
22 | github.com/valyala/bytebufferpool v1.0.0 // indirect
23 | github.com/valyala/fasthttp v1.47.0 // indirect
24 | github.com/valyala/tcplisten v1.0.0 // indirect
25 | golang.org/x/sys v0.8.0 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/nocache/init.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "sync"
5 |
6 | utils "github.com/realTristan/hermes/utils"
7 | )
8 |
9 | // Initialize the full-text cache with the provided data.
10 | // This function is thread safe.
11 | func InitWithMapSlice(data []map[string]any, minWordLength int) (*FullText, error) {
12 | var ft *FullText = &FullText{
13 | mutex: &sync.RWMutex{},
14 | storage: make(map[string]any),
15 | words: []string{},
16 | data: []map[string]any{},
17 | }
18 |
19 | // Load the cache data
20 | if err := ft.insert(data, minWordLength); err != nil {
21 | return nil, err
22 | }
23 |
24 | // Set the data
25 | ft.data = data
26 |
27 | // Return the full-text variable
28 | return ft, nil
29 | }
30 |
31 | // Initialize the full-text cache with the provided json file.
32 | // This function is thread safe.
33 | func InitWithJson(file string, minWordLength int) (*FullText, error) {
34 | // Read the json data
35 | if data, err := utils.ReadJson[[]map[string]any](file); err != nil {
36 | return nil, err
37 | } else {
38 | return InitWithMapSlice(data, minWordLength)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Tristan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/website/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/website/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(() => TestBed.configureTestingModule({
7 | imports: [RouterTestingModule],
8 | declarations: [AppComponent]
9 | }));
10 |
11 | it('should create the app', () => {
12 | const fixture = TestBed.createComponent(AppComponent);
13 | const app = fixture.componentInstance;
14 | expect(app).toBeTruthy();
15 | });
16 |
17 | it(`should have as title 'hermescloud'`, () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.componentInstance;
20 | expect(app.title).toEqual('hermescloud');
21 | });
22 |
23 | it('should render title', () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | fixture.detectChanges();
26 | const compiled = fixture.nativeElement as HTMLElement;
27 | expect(compiled.querySelector('.content span')?.textContent).toContain('hermescloud app is running!');
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/cloud/wrappers/python/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Tristan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/set.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // Set is a handler function that returns a fiber context handler function for setting a value in the cache.
9 | // Parameters:
10 | // - p (*utils.Params): A pointer to a utils.Params struct.
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the set operation fails.
15 | func Set(p *utils.Params, c *hermes.Cache) []byte {
16 | var (
17 | key string
18 | err error
19 | value map[string]any
20 | )
21 |
22 | // Get the key from the query
23 | if key, err = utils.GetKeyParam(p); err != nil {
24 | return utils.Error("invalid key")
25 | }
26 |
27 | // Get the value from the query
28 | if err := utils.GetValueParam(p, &value); err != nil {
29 | return utils.Error(err)
30 | }
31 |
32 | // Set the value in the cache
33 | if err := c.Set(key, value); err != nil {
34 | return utils.Error(err)
35 | }
36 | return utils.Success("null")
37 | }
38 |
--------------------------------------------------------------------------------
/testing/fulltext/delete.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | )
8 |
9 | func delete() {
10 | var cache *hermes.Cache = hermes.InitCache()
11 |
12 | // Initialize the FT cache
13 | cache.FTInit(-1, -1, 3)
14 |
15 | // Test Delete()
16 | var data = map[string]any{
17 | "name": "tristan",
18 | "age": 17,
19 | }
20 |
21 | // print cache info
22 | fmt.Println(cache.InfoForTesting())
23 |
24 | // Set data
25 | cache.Set("user_id1", data)
26 | cache.Set("user_id1", data)
27 | cache.Set("user_id2", data)
28 |
29 | // print cache info
30 | fmt.Println(cache.InfoForTesting())
31 |
32 | // Delete data
33 | cache.Delete("user_id1")
34 | cache.Delete("user_id1")
35 |
36 | // Get data
37 | fmt.Println(cache.Get("user_id1"))
38 | fmt.Println(cache.Get("user_id2"))
39 |
40 | // Exists
41 | fmt.Println(cache.Exists("user_id1"))
42 | fmt.Println(cache.Exists("user_id2"))
43 |
44 | // Length
45 | fmt.Println(cache.Length())
46 |
47 | // Values
48 | fmt.Println(cache.Values())
49 |
50 | // Keys
51 | fmt.Println(cache.Keys())
52 |
53 | // Print the cache info
54 | fmt.Println(cache.InfoForTesting())
55 | }
56 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the offical golang image to create a binary.
2 | # This is based on Debian and sets the GOPATH to /go.
3 | # https://hub.docker.com/_/golang
4 | FROM golang:1.20-buster as builder
5 |
6 | # Create and change to the app directory.
7 | WORKDIR /app
8 |
9 | # Retrieve application dependencies.
10 | # This allows the container build to reuse cached dependencies.
11 | # Expecting to copy go.mod and if present go.sum.
12 | COPY /cloud/app/server/go.* ./
13 | RUN go mod download
14 |
15 | # Copy local code to the container image.
16 | COPY . ./
17 |
18 | # Build the binary.
19 | RUN go build -v -o server
20 |
21 | # Use the official Debian slim image for a lean production container.
22 | # https://hub.docker.com/_/debian
23 | # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
24 | FROM debian:buster-slim
25 | RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
26 | ca-certificates && \
27 | rm -rf /var/lib/apt/lists/*
28 |
29 | # Copy the binary to the production image from the builder stage.
30 | COPY --from=builder /app/server /app/server
31 |
32 | # Run the web service on container startup.
33 | CMD ["/app/server serve -p 3000"]
--------------------------------------------------------------------------------
/cloud/app/server/utils/args.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // Data struct
9 | type Data struct {
10 | port any
11 | }
12 |
13 | // Get the port
14 | func (d *Data) Port() any {
15 | var copy any = d.port
16 | return copy
17 | }
18 |
19 | // Get the argument data in a map
20 | func GetArgData(args []string) (*Data, error) {
21 | var data *Data = &Data{
22 | port: nil,
23 | }
24 |
25 | // Iterate over the args
26 | for i := 2; i < len(args); i++ {
27 | // Port arg
28 | if args[i] == "-port" || args[i] == "-p" {
29 | if i+1 >= len(args) {
30 | return data, errors.New("invalid port")
31 | }
32 |
33 | // Add a ':' to the port
34 | data.port = ":" + strings.ReplaceAll(args[i+1], ":", "")
35 |
36 | // If all past index 1 isnt a number
37 | if !isNum(data.port.(string)[1:]) {
38 | return data, errors.New("invalid port")
39 | }
40 |
41 | // Increment i then continue
42 | i = i + 1
43 | continue
44 | }
45 | }
46 | return data, nil
47 | }
48 |
49 | // Check if a string is a number
50 | func isNum(s string) bool {
51 | for _, c := range s {
52 | if c < '0' || c > '9' {
53 | return false
54 | }
55 | }
56 | return true
57 | }
58 |
--------------------------------------------------------------------------------
/cloud/wrappers/rust/target/.rustc_info.json:
--------------------------------------------------------------------------------
1 | {"rustc_fingerprint":472984446891056861,"outputs":{"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/tristan/.rustup/toolchains/stable-x86_64-apple-darwin\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.66.0 (69f9c33d7 2022-12-12)\nbinary: rustc\ncommit-hash: 69f9c33d71c871fc16ac445211281c6e7a340943\ncommit-date: 2022-12-12\nhost: x86_64-apple-darwin\nrelease: 1.66.0\nLLVM version: 15.0.2\n","stderr":""}},"successes":{}}
--------------------------------------------------------------------------------
/cloud/wrappers/python/src/test_withjson.py:
--------------------------------------------------------------------------------
1 | import hermescloud, json, base64, time
2 |
3 | # base64 encode a value
4 | def base64_encode(value):
5 | return base64.b64encode(value.encode("utf-8")).decode("utf-8")
6 |
7 | # Create a new cache instance
8 | cache = hermescloud.Cache("localhost:3000")
9 |
10 | # Initialize the full-text search engine
11 | #print(cache.ft_init(-1, -1))
12 |
13 | # open the data/data_hash.json file
14 | def set_data():
15 | with open("data/data_hash.json", "r") as file:
16 | # load the data_hash.json file
17 | data = json.loads(file.read())
18 |
19 | # loop through the data
20 | for key in data:
21 | # set the key
22 | cache.set(key, data[key])
23 |
24 | # set the data
25 | #set_data()
26 |
27 | # Track the start time
28 | start_time = time.time()
29 |
30 | # Search for a value
31 | r = cache.ft_search("computer", False, 100, {
32 | "id": False,
33 | "components": False,
34 | "units": False,
35 | "description": True,
36 | "name": True,
37 | "pre_requisites": True,
38 | "title": True
39 | })
40 |
41 | # Print the duration
42 | print(time.time() - start_time)
43 |
44 | # print the results
45 | #print(r[0])
46 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/clean.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // Clean is a handler function that returns a fiber context handler function for cleaning the cache.
9 | // Parameters:
10 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the cleaning fails.
15 | func Clean(_ *utils.Params, c *hermes.Cache) []byte {
16 | c.Clean()
17 | return utils.Success("null")
18 | }
19 |
20 | // FTClean is a handler function that returns a fiber context handler function for cleaning the full-text storage.
21 | // Parameters:
22 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
23 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
24 | //
25 | // Returns:
26 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the cleaning fails.
27 | func FTClean(_ *utils.Params, c *hermes.Cache) []byte {
28 | if err := c.FTClean(); err != nil {
29 | return utils.Error(err)
30 | }
31 | return utils.Success("null")
32 | }
33 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hermescloud",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^16.0.0",
14 | "@angular/common": "^16.0.0",
15 | "@angular/compiler": "^16.0.0",
16 | "@angular/core": "^16.0.0",
17 | "@angular/forms": "^16.0.0",
18 | "@angular/platform-browser": "^16.0.0",
19 | "@angular/platform-browser-dynamic": "^16.0.0",
20 | "@angular/router": "^16.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.13.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^16.0.2",
27 | "@angular/cli": "~16.0.2",
28 | "@angular/compiler-cli": "^16.0.0",
29 | "@types/jasmine": "~4.3.0",
30 | "angular-cli-ghpages": "^1.0.6",
31 | "autoprefixer": "^10.4.14",
32 | "jasmine-core": "~4.6.0",
33 | "karma": "~6.4.0",
34 | "karma-chrome-launcher": "~3.2.0",
35 | "karma-coverage": "~2.2.0",
36 | "karma-jasmine": "~5.1.0",
37 | "karma-jasmine-html-reporter": "~2.0.0",
38 | "postcss": "^8.4.23",
39 | "tailwindcss": "^3.3.2",
40 | "typescript": "~5.0.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/cloud/api/handlers/set.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // Set is a handler function that returns a fiber context handler function for setting a value in the cache.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets a value in the cache using the key and value parameters provided in the query string and returns a success message or an error message if the set fails or if the parameters are not provided.
15 | func Set(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | var (
18 | key string
19 | value map[string]interface{}
20 | )
21 | // Get the key from the query
22 | if key = ctx.Query("key"); len(key) == 0 {
23 | return ctx.Send(utils.Error("invalid key"))
24 | }
25 |
26 | // Get the value from the query
27 | if err := utils.GetValueParam(ctx, &value); err != nil {
28 | return ctx.Send(utils.Error(err))
29 | }
30 |
31 | // Set the value in the cache
32 | if err := c.Set(key, value); err != nil {
33 | return ctx.Send(utils.Error(err))
34 | }
35 | return ctx.Send(utils.Success("null"))
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/cloud/api/handlers/clean.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // Clean is a function that returns a fiber context handler function for cleaning the regular cache.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that cleans the regular cache and returns a success message.
15 | func Clean(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | c.Clean()
18 | return ctx.Send(utils.Success("null"))
19 | }
20 | }
21 |
22 | // FTClean is a function that returns a fiber context handler function for cleaning the full-text cache.
23 | // Parameters:
24 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
25 | //
26 | // Returns:
27 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that cleans the full-text cache and returns a success message or an error message if the cleaning fails.
28 | func FTClean(c *hermes.Cache) func(ctx *fiber.Ctx) error {
29 | return func(ctx *fiber.Ctx) error {
30 | if err := c.FTClean(); err != nil {
31 | return ctx.Send(utils.Error(err))
32 | }
33 | return ctx.Send(utils.Success("null"))
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/info.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // Info is a function that returns information about the cache.
9 | //
10 | // Parameters:
11 | // - _ : A pointer to a utils.Params struct representing the parameters of the request. This parameter is ignored.
12 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from.
13 | //
14 | // Returns:
15 | // - A byte slice representing the information about the cache.
16 | func Info(_ *utils.Params, c *hermes.Cache) []byte {
17 | if info, err := c.Info(); err != nil {
18 | return utils.Error(err)
19 | } else {
20 | return utils.Success(info)
21 | }
22 | }
23 |
24 | // InfoForTesting is a function that returns information about the cache for testing purposes.
25 | //
26 | // Parameters:
27 | // - _ : A pointer to a utils.Params struct representing the parameters of the request. This parameter is ignored.
28 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from.
29 | //
30 | // Returns:
31 | // - A byte slice representing the information about the cache for testing purposes.
32 | func InfoForTesting(_ *utils.Params, c *hermes.Cache) []byte {
33 | if info, err := c.InfoForTesting(); err != nil {
34 | return utils.Error(err)
35 | } else {
36 | return utils.Success(info)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/testing/compression/compression.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | gzip "github.com/realTristan/hermes/compression/gzip"
9 | "github.com/realTristan/hermes/compression/zlib"
10 | utils "github.com/realTristan/hermes/utils"
11 | )
12 |
13 | func main() {
14 | var v string = strings.Repeat("computer", 100)
15 | TestGzip(v)
16 | TestZlib(v)
17 | }
18 |
19 | // Test the zlib compression and decompression functions.
20 | func TestZlib(v string) {
21 | fmt.Println("zlib")
22 | var (
23 | b []byte
24 | err error
25 | st time.Time = time.Now()
26 | )
27 | if b, err = zlib.Compress([]byte(v)); err != nil {
28 | panic(err)
29 | }
30 | fmt.Println(time.Since(st))
31 | st = time.Now()
32 | if v, err = zlib.Decompress(b); err != nil {
33 | panic(err)
34 | }
35 | fmt.Println(time.Since(st))
36 | fmt.Println(utils.Size(v))
37 | fmt.Println(utils.Size(b))
38 | }
39 |
40 | // Test the gzip compression and decompression functions.
41 | func TestGzip(v string) {
42 | fmt.Println("gzip")
43 | var (
44 | b []byte
45 | err error
46 | st time.Time = time.Now()
47 | )
48 | if b, err = gzip.Compress([]byte(v)); err != nil {
49 | panic(err)
50 | }
51 | fmt.Println(time.Since(st))
52 | st = time.Now()
53 | if v, err = gzip.Decompress(b); err != nil {
54 | panic(err)
55 | }
56 | fmt.Println(time.Since(st))
57 | fmt.Println(utils.Size(v))
58 | fmt.Println(utils.Size(b))
59 | }
60 |
--------------------------------------------------------------------------------
/cloud/socket/utils/errors.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/gofiber/websocket/v2"
7 | )
8 |
9 | // IsCloseError is a function that checks if an error is a close error by converting
10 | // the error to a string and checking if it contains the word "close".
11 | // Parameters:
12 | // - err (error): The error to check.
13 | //
14 | // Returns:
15 | // - bool: true if the error is a close error, false otherwise.
16 | func IsCloseError(err error) bool {
17 | return strings.Contains(err.Error(), "close")
18 | }
19 |
20 | // IsSocketCloseError is a function that checks if an error is a WebSocket close error.
21 | // Parameters:
22 | // - err (error): The error to check.
23 | //
24 | // Returns:
25 | // - bool: true if the error is a WebSocket close error, false otherwise.
26 | func IsSocketCloseError(err error) bool {
27 | return websocket.IsCloseError(
28 | err,
29 | websocket.CloseNormalClosure,
30 | websocket.CloseGoingAway,
31 | websocket.CloseAbnormalClosure,
32 | websocket.CloseNoStatusReceived,
33 | websocket.CloseInvalidFramePayloadData,
34 | websocket.ClosePolicyViolation,
35 | websocket.CloseMessageTooBig,
36 | websocket.CloseMandatoryExtension,
37 | websocket.CloseInternalServerErr,
38 | websocket.CloseServiceRestart,
39 | websocket.CloseTryAgainLater,
40 | websocket.CloseTLSHandshake,
41 | websocket.CloseMessage,
42 | websocket.CloseProtocolError,
43 | websocket.CloseUnsupportedData,
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/cloud/socket/funcs.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | "github.com/realTristan/hermes/cloud/socket/handlers"
6 | utils "github.com/realTristan/hermes/cloud/socket/utils"
7 | )
8 |
9 | // Map of functions that can be called from the client
10 | var Functions = map[string]func(*utils.Params, *hermes.Cache) []byte{
11 | "cache.length": handlers.Length,
12 | "cache.clean": handlers.Clean,
13 | "cache.set": handlers.Set,
14 | "cache.delete": handlers.Delete,
15 | "cache.get": handlers.Get,
16 | "cache.get.all": handlers.GetAll,
17 | "cache.keys": handlers.Keys,
18 | "cache.info": handlers.Info,
19 | "cache.info.testing": handlers.InfoForTesting,
20 | "cache.exists": handlers.Exists,
21 | "ft.init": handlers.FTInit,
22 | "ft.init.json": handlers.FTInitJson,
23 | "ft.clean": handlers.FTClean,
24 | "ft.search": handlers.Search,
25 | "ft.search.oneword": handlers.SearchOneWord,
26 | "ft.search.values": handlers.SearchValues,
27 | "ft.search.withkey": handlers.SearchWithKey,
28 | "ft.maxbytes.set": handlers.FTSetMaxBytes,
29 | "ft.maxsize.set": handlers.FTSetMaxSize,
30 | "ft.storage": handlers.FTStorage,
31 | "ft.storage.size": handlers.FTStorageSize,
32 | "ft.storage.length": handlers.FTStorageLength,
33 | "ft.isinitialized": handlers.FTIsInitialized,
34 | "ft.indices.sequence": handlers.FTSequenceIndices,
35 | }
36 |
--------------------------------------------------------------------------------
/cloud/api/handlers/info.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // Info is a function that returns information about the cache.
10 | //
11 | // Parameters:
12 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from.
13 | //
14 | // Returns:
15 | // - A function that takes a pointer to a fiber.Ctx struct and returns an error.
16 | // The function returns information about the cache.
17 | func Info(c *hermes.Cache) func(ctx *fiber.Ctx) error {
18 | return func(ctx *fiber.Ctx) error {
19 | if info, err := c.Info(); err != nil {
20 | return ctx.Send(utils.Error(err))
21 | } else {
22 | return ctx.Send(utils.Success(info))
23 | }
24 | }
25 | }
26 |
27 | // InfoForTesting is a function that returns information about the cache for testing purposes.
28 | //
29 | // Parameters:
30 | // - c: A pointer to a hermes.Cache struct representing the cache to get information from.
31 | //
32 | // Returns:
33 | // - A function that takes a pointer to a fiber.Ctx struct and returns an error.
34 | // The function returns information about the cache for testing purposes.
35 | func InfoForTesting(c *hermes.Cache) func(ctx *fiber.Ctx) error {
36 | return func(ctx *fiber.Ctx) error {
37 | if info, err := c.InfoForTesting(); err != nil {
38 | return ctx.Send(utils.Error(err))
39 | } else {
40 | return ctx.Send(utils.Success(info))
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/get.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | utils "github.com/realTristan/hermes/cloud/socket/utils"
8 | )
9 |
10 | // Get is a handler function that returns a fiber context handler function for retrieving a key from the cache.
11 | // Parameters:
12 | // - p (*utils.Params): A pointer to a utils.Params struct.
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - []byte: A JSON-encoded byte slice containing the value of the key or an error message if the key is not provided or the retrieval fails.
17 | func Get(p *utils.Params, c *hermes.Cache) []byte {
18 | // Get the key from the query
19 | var (
20 | key string
21 | err error
22 | )
23 | if key, err = utils.GetKeyParam(p); err != nil {
24 | return utils.Error("key not provided")
25 | }
26 |
27 | // Get the value from the cache
28 | if data, err := json.Marshal(c.Get(key)); err != nil {
29 | return utils.Error(err)
30 | } else {
31 | return data
32 | }
33 | }
34 |
35 | // GetAll is a handler function that returns a fiber context handler function for retrieving all data from the cache.
36 | // Parameters:
37 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
38 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
39 | //
40 | // Returns:
41 | // - []byte: A JSON-encoded byte slice containing all data from the cache or an error message if the retrieval fails.
42 | func GetAll(_ *utils.Params, c *hermes.Cache) []byte {
43 | return nil
44 | }
45 |
--------------------------------------------------------------------------------
/testing/cache/cache.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | Hermes "github.com/realTristan/hermes"
7 | )
8 |
9 | func main() {
10 | var cache *Hermes.Cache = Hermes.InitCache()
11 | cache.FTInit(3, -1, -1)
12 |
13 | // Test Set, Get, Delete, Clean, Length, Values, Keys, and Exists
14 | var data = map[string]any{
15 | "name": cache.WithFT("Tristan"),
16 | "age": 17,
17 | }
18 |
19 | // Set data
20 | cache.Set("user_id1", data)
21 | cache.Set("user_id1", data)
22 | cache.Set("user_id2", data)
23 | cache.Set("user_id3", data)
24 |
25 | // Get data
26 | fmt.Println(cache.Get("user_id1"))
27 | fmt.Println(cache.Get("user_id2"))
28 | fmt.Println(cache.Get("user_id3"))
29 |
30 | // Exists
31 | fmt.Println(cache.Exists("user_id1"))
32 | fmt.Println(cache.Exists("user_id2"))
33 |
34 | // Length
35 | fmt.Println(cache.Length())
36 |
37 | // Values
38 | fmt.Println(cache.Values())
39 |
40 | // Keys
41 | fmt.Println(cache.Keys())
42 |
43 | // Delete data
44 | cache.Delete("user_id1")
45 |
46 | // Exists
47 | fmt.Println(cache.Exists("user_id1"))
48 | fmt.Println(cache.Exists("user_id2"))
49 |
50 | // Get data
51 | fmt.Println(cache.Get("user_id1"))
52 | fmt.Println(cache.Get("user_id2"))
53 | fmt.Println(cache.Get("user_id3"))
54 |
55 | // Clean data
56 | cache.Clean()
57 |
58 | // Get data
59 | fmt.Println(cache.Get("user_id1"))
60 | fmt.Println(cache.Get("user_id2"))
61 | fmt.Println(cache.Get("user_id3"))
62 |
63 | // Length
64 | fmt.Println(cache.Length())
65 |
66 | // Values
67 | fmt.Println(cache.Values())
68 |
69 | // Keys
70 | fmt.Println(cache.Keys())
71 | }
72 |
--------------------------------------------------------------------------------
/compression/gzip/gzip.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "io"
7 | )
8 |
9 | // Compress is a function that compresses a string using gzip compression.
10 | //
11 | // Parameters:
12 | // - v: A string representing the value to compress.
13 | //
14 | // Returns:
15 | // - A byte slice representing the compressed value.
16 | // - An error if there was an error compressing the value.
17 | //
18 | // Example usage:
19 | //
20 | // compressed, err := Compress("value") // compressed == []byte{...}, err == nil
21 | func Compress(v []byte) ([]byte, error) {
22 | var (
23 | b *bytes.Buffer = new(bytes.Buffer)
24 | gz *gzip.Writer = gzip.NewWriter(b)
25 | )
26 | if _, err := gz.Write(v); err != nil {
27 | return nil, err
28 | }
29 | if err := gz.Close(); err != nil {
30 | return nil, err
31 | }
32 | return b.Bytes(), nil
33 | }
34 |
35 | // Decompress is a function that decompresses a byte slice using gzip decompression.
36 | //
37 | // Parameters:
38 | // - v: A byte slice representing the compressed value to decompress.
39 | //
40 | // Returns:
41 | // - A string representing the decompressed value.
42 | // - An error if there was an error decompressing the value.
43 | //
44 | // Example usage:
45 | //
46 | // decompressed, err := Decompress([]byte{...}) // decompressed == "value", err == nil
47 | func Decompress(v []byte) (string, error) {
48 | var b *bytes.Buffer = bytes.NewBuffer(v)
49 | if gz, err := gzip.NewReader(b); err != nil {
50 | return "", err
51 | } else if s, err := io.ReadAll(gz); err != nil {
52 | return "", err
53 | } else {
54 | return string(s), nil
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/compression/zlib/zlib.go:
--------------------------------------------------------------------------------
1 | package zlib
2 |
3 | import (
4 | "bytes"
5 | "compress/zlib"
6 | "io"
7 | )
8 |
9 | // Compress is a function that compresses a string using gzip compression.
10 | //
11 | // Parameters:
12 | // - v: A string representing the value to compress.
13 | //
14 | // Returns:
15 | // - A byte slice representing the compressed value.
16 | // - An error if there was an error compressing the value.
17 | //
18 | // Example usage:
19 | //
20 | // compressed, err := Compress("value") // compressed == []byte{...}, err == nil
21 | func Compress(v []byte) ([]byte, error) {
22 | var (
23 | b *bytes.Buffer = new(bytes.Buffer)
24 | gz *zlib.Writer = zlib.NewWriter(b)
25 | )
26 | if _, err := gz.Write(v); err != nil {
27 | return nil, err
28 | }
29 | if err := gz.Close(); err != nil {
30 | return nil, err
31 | }
32 | return b.Bytes(), nil
33 | }
34 |
35 | // Decompress is a function that decompresses a byte slice using gzip decompression.
36 | //
37 | // Parameters:
38 | // - v: A byte slice representing the compressed value to decompress.
39 | //
40 | // Returns:
41 | // - A string representing the decompressed value.
42 | // - An error if there was an error decompressing the value.
43 | //
44 | // Example usage:
45 | //
46 | // decompressed, err := Decompress([]byte{...}) // decompressed == "value", err == nil
47 | func Decompress(v []byte) (string, error) {
48 | var b *bytes.Buffer = bytes.NewBuffer(v)
49 | if gz, err := zlib.NewReader(b); err != nil {
50 | return "", err
51 | } else if s, err := io.ReadAll(gz); err != nil {
52 | return "", err
53 | } else {
54 | return string(s), nil
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/cloud/api/handlers/get.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | hermes "github.com/realTristan/hermes"
8 | utils "github.com/realTristan/hermes/cloud/api/utils"
9 | )
10 |
11 | // Get is a handler function that returns a fiber context handler function for getting a value from the cache.
12 | // Parameters:
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets a value from the cache using a key provided in the query string and returns a JSON-encoded string of the value or an error message if the key is not provided or if the retrieval or encoding fails.
17 | func Get(c *hermes.Cache) func(ctx *fiber.Ctx) error {
18 | return func(ctx *fiber.Ctx) error {
19 | // Get the key from the query
20 | var key string
21 | if key = ctx.Query("key"); len(key) == 0 {
22 | return ctx.Send(utils.Error("key not provided"))
23 | }
24 |
25 | // Get the value from the cache
26 | if data, err := json.Marshal(c.Get(key)); err != nil {
27 | return ctx.Send(utils.Error(err))
28 | } else {
29 | return ctx.Send(data)
30 | }
31 | }
32 | }
33 |
34 | // GetAll is a handler function that returns a fiber context handler function for getting all the data from the cache.
35 | // Parameters:
36 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
37 | //
38 | // Returns:
39 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets all the data from the cache and returns a success message with a JSON-encoded string of the data or an error message if the retrieval or encoding fails.
40 | func GetAll(c *hermes.Cache) func(ctx *fiber.Ctx) error {
41 | return func(ctx *fiber.Ctx) error {
42 | return nil
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/withft.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // WFT is a struct that represents a value to be set in the cache and in the full-text cache.
4 | type WFT struct {
5 | value string
6 | }
7 |
8 | // WithFT is a function that creates a new WFT struct with the specified value.
9 | //
10 | // Parameters:
11 | // - value: A string representing the value to set in the cache and in the full-text cache.
12 | //
13 | // Returns:
14 | // - A WFT struct with the specified value or the initial string
15 | func (cache *Cache) WithFT(value string) *WFT {
16 | return &WFT{value}
17 | }
18 |
19 | func (wft *WFT) Set(value string) {
20 | wft.value = value
21 | }
22 |
23 | func (wft *WFT) Value() string {
24 | return wft.value
25 | }
26 |
27 | func WFTGetValue(value any) string {
28 | if wft, ok := value.(*WFT); ok {
29 | return wft.value
30 | } else if v := WFTGetValueFromMap(value); len(v) > 0 {
31 | return v
32 | }
33 | return ""
34 | }
35 |
36 | // WFTGetValueFromMap is a function that gets the full-text value from a map.
37 | //
38 | // Parameters:
39 | // - value: any representing the value to get the full-text value from.
40 | //
41 | // Returns:
42 | // - A string representing the full-text value, or an empty string if the value is not a map or does not contain the correct keys.
43 | func WFTGetValueFromMap(value any) string {
44 | if _, ok := value.(map[string]any); !ok {
45 | return ""
46 | }
47 |
48 | // Verify that the map has the correct length
49 | var v map[string]any = value.(map[string]any)
50 | if len(v) != 2 {
51 | return ""
52 | }
53 |
54 | // Verify that the map has the correct keys
55 | if ft, ok := v["$hermes.full_text"]; ok {
56 | if ft, ok := ft.(bool); ok && ft {
57 | if v, ok := v["$hermes.value"]; ok {
58 | if v, ok := v.(string); ok {
59 | return v
60 | }
61 | }
62 | }
63 | }
64 | return ""
65 | }
66 |
--------------------------------------------------------------------------------
/clean.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import "errors"
4 |
5 | // Clean is a method of the Cache struct that clears the cache contents.
6 | // If the full-text index is initialized, it is also cleared.
7 | // This method is thread-safe.
8 | //
9 | // Parameters:
10 | // - None
11 | //
12 | // Returns:
13 | // - None
14 | func (c *Cache) Clean() {
15 | c.mutex.Lock()
16 | defer c.mutex.Unlock()
17 | c.clean()
18 | }
19 |
20 | // clean is a method of the Cache struct that clears the cache contents.
21 | // If the full-text index is initialized, it is also cleared.
22 | // This method is not thread-safe and should only be called from an exported function.
23 | //
24 | // Parameters:
25 | // - None
26 | //
27 | // Returns:
28 | // - None
29 | func (c *Cache) clean() {
30 | if c.ft != nil {
31 | c.ft.clean()
32 | }
33 | c.data = map[string]map[string]any{}
34 | }
35 |
36 | // FTClean is a method of the Cache struct that clears the full-text cache contents.
37 | // If the full-text index is not initialized, this method returns an error.
38 | // Otherwise, the full-text index storage and indices are cleared, and this method returns nil.
39 | // This method is thread-safe.
40 | //
41 | // Returns:
42 | // - error: An error object. If no error occurs, this will be nil.
43 | func (c *Cache) FTClean() error {
44 | c.mutex.Lock()
45 | defer c.mutex.Unlock()
46 |
47 | // Verify that the full text is initialized
48 | if c.ft == nil {
49 | return errors.New("full text is not initialized")
50 | }
51 |
52 | // Clean the ft cache
53 | c.ft.clean()
54 |
55 | // Return no error
56 | return nil
57 | }
58 |
59 | // clean is a method of the FullText struct that clears the full-text index storage and indices.
60 | // This method initializes a new empty storage map and indices map with the maximum length specified in the FullText struct.
61 | //
62 | // Parameters:
63 | // - None
64 | //
65 | // Returns:
66 | // - None
67 | func (ft *FullText) clean() {
68 | ft.storage = make(map[string]any)
69 | ft.indices = make(map[int]string)
70 | }
71 |
--------------------------------------------------------------------------------
/searchwithkey.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // SearchWithKey searches for all records containing the given query in the specified key column with a limit of results to return.
9 | // Parameters:
10 | // - c (c *Cache): A pointer to the Cache struct
11 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
12 | //
13 | // Returns:
14 | // - []map[string]any: A slice of maps containing the search results
15 | // - error: An error if the key, query or limit is invalid
16 | func (c *Cache) SearchWithKey(sp SearchParams) ([]map[string]any, error) {
17 | switch {
18 | case len(sp.Key) == 0:
19 | return []map[string]any{}, errors.New("invalid key")
20 | case len(sp.Query) == 0:
21 | return []map[string]any{}, errors.New("invalid query")
22 | }
23 |
24 | // Set the query to lowercase
25 | sp.Query = strings.ToLower(sp.Query)
26 |
27 | // Lock the mutex
28 | c.mutex.RLock()
29 | defer c.mutex.RUnlock()
30 |
31 | // Search the data
32 | return c.searchWithKey(sp), nil
33 | }
34 |
35 | // searchWithKey searches for all records containing the given query in the specified key column with a limit of results to return.
36 | // Parameters:
37 | // - c (c *Cache): A pointer to the Cache struct
38 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
39 | //
40 | // Returns:
41 | // - []map[string]any: A slice of maps containing the search results
42 | func (c *Cache) searchWithKey(sp SearchParams) []map[string]any {
43 | // Define variables
44 | var result []map[string]any = []map[string]any{}
45 |
46 | // Iterate over the query result
47 | for _, item := range c.data {
48 | for _, v := range item {
49 | if len(result) >= sp.Limit {
50 | return result
51 | }
52 |
53 | // Check if the value contains the query
54 | if v, ok := v.(string); ok {
55 | if strings.Contains(strings.ToLower(v), sp.Query) {
56 | result = append(result, item)
57 | }
58 | }
59 | }
60 | }
61 |
62 | // Return the result
63 | return result
64 | }
65 |
--------------------------------------------------------------------------------
/nocache/insert.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "strings"
5 |
6 | utils "github.com/realTristan/hermes/utils"
7 | )
8 |
9 | // Insert data into the full-text cache.
10 | // This function is not thread-safe, and should only be called from
11 | // an exported function.
12 | func (ft *FullText) insert(data []map[string]any, minWordLength int) error {
13 | // Loop through the data
14 | for i, item := range data {
15 | // Loop through the map
16 | for key, value := range item {
17 | // Get the string value
18 | var (
19 | strvNormal string
20 | strv string
21 | )
22 | if _strv := WFTGetValueFromMap(value); len(_strv) > 0 {
23 | strv = _strv
24 | strvNormal = _strv
25 | } else {
26 | continue
27 | }
28 |
29 | // Clean the value
30 | strv = strings.TrimSpace(strv)
31 | strv = utils.RemoveDoubleSpaces(strv)
32 | strv = strings.ToLower(strv)
33 |
34 | // Loop through the words
35 | for _, word := range strings.Split(strv, " ") {
36 | if len(word) == 0 {
37 | continue
38 | }
39 |
40 | // Trim the word
41 | word = utils.TrimNonAlphaNum(word)
42 | var words []string = utils.SplitByAlphaNum(word)
43 |
44 | // Loop through the words
45 | for j := 0; j < len(words); j++ {
46 | if len(words[j]) < minWordLength {
47 | continue
48 | }
49 | if temp, ok := ft.storage[words[j]]; !ok {
50 | ft.storage[words[j]] = []int{i}
51 | ft.words = append(ft.words, words[j])
52 | } else if indices, ok := temp.([]int); !ok {
53 | ft.storage[words[j]] = []int{temp.(int), i}
54 | } else {
55 | if utils.SliceContains(indices, i) {
56 | continue
57 | }
58 | ft.storage[words[j]] = append(indices, i)
59 | }
60 | }
61 | }
62 |
63 | // Iterate over the temp storage and set the values with len 1 to int
64 | for k, v := range ft.storage {
65 | if v, ok := v.([]int); ok && len(v) == 1 {
66 | ft.storage[k] = v[0]
67 | }
68 | }
69 |
70 | // Set the value
71 | data[i][key] = strvNormal
72 | }
73 | }
74 | return nil
75 | }
76 |
--------------------------------------------------------------------------------
/nocache/examples/router/router.go:
--------------------------------------------------------------------------------
1 | // /////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Run Command: go run .
4 | //
5 | // Host URL: http://localhost:8000/courses?q=computer&limit=100&strict=false
6 | //
7 | // /////////////////////////////////////////////////////////////////////////////
8 | package main
9 |
10 | import (
11 | "encoding/json"
12 | "fmt"
13 | "net/http"
14 | "strconv"
15 | "time"
16 |
17 | hermes "github.com/realTristan/hermes/nocache"
18 | )
19 |
20 | // Global full text variable
21 | var ft *hermes.FullText
22 |
23 | // Main function
24 | func main() {
25 | ft, _ = hermes.InitWithJson("../../../testing/data/data_array.json", 3)
26 |
27 | // Print host
28 | fmt.Println(" >> Listening on: http://localhost:8000/")
29 |
30 | // Listen and serve on port 8000
31 | http.HandleFunc("/courses", Handler)
32 | http.ListenAndServe(":8000", nil)
33 | }
34 |
35 | // Handle the incoming http request
36 | func Handler(w http.ResponseWriter, r *http.Request) {
37 | w.Header().Set("Access-Control-Allow-Origin", "*")
38 |
39 | // Get the query parameter
40 | var query string = "CS"
41 | if _query := r.URL.Query().Get("q"); _query != "" {
42 | query = _query
43 | }
44 |
45 | // Get the limit parameter
46 | var limit int = 100
47 | if _limit := r.URL.Query().Get("limit"); _limit != "" {
48 | limit, _ = strconv.Atoi(_limit)
49 | }
50 |
51 | // Get the strict parameter
52 | var strict bool = false
53 | if _strict := r.URL.Query().Get("strict"); _strict != "" {
54 | strict, _ = strconv.ParseBool(_strict)
55 | }
56 |
57 | // Track the start time
58 | var start time.Time = time.Now()
59 |
60 | // Search for a word in the cache
61 | // Make sure the show which keys you do want to search through,
62 | // and which ones you don't
63 | var res, _ = ft.Search(hermes.SearchParams{
64 | Query: query,
65 | Limit: limit,
66 | Strict: strict,
67 | })
68 |
69 | // Print the duration
70 | fmt.Printf("\nFound %v results in %v", len(res), time.Since(start))
71 |
72 | // Write the courses to the json response
73 | var response, _ = json.Marshal(res)
74 | w.Write(response)
75 | }
76 |
--------------------------------------------------------------------------------
/examples/router/router.go:
--------------------------------------------------------------------------------
1 | // /////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Run Command: go run .
4 | //
5 | // Host URL: http://localhost:8000/courses?q=computer&limit=100&strict=false
6 | //
7 | // /////////////////////////////////////////////////////////////////////////////
8 | package main
9 |
10 | import (
11 | "encoding/json"
12 | "fmt"
13 | "net/http"
14 | "strconv"
15 | "time"
16 |
17 | hermes "github.com/realTristan/hermes"
18 | )
19 |
20 | // Global full text variable
21 | var cache *hermes.Cache
22 |
23 | // Main function
24 | func main() {
25 | cache = hermes.InitCache()
26 | cache.FTInitWithJson("../../testing/data/data_hash.json", -1, -1, 3)
27 |
28 | // Print host
29 | fmt.Println(" >> Listening on: http://localhost:8000/")
30 |
31 | // Listen and serve on port 8000
32 | http.HandleFunc("/courses", Handler)
33 | http.ListenAndServe(":8000", nil)
34 | }
35 |
36 | // Handle the incoming http request
37 | func Handler(w http.ResponseWriter, r *http.Request) {
38 | w.Header().Set("Access-Control-Allow-Origin", "*")
39 |
40 | // Get the query parameter
41 | var query string = "CS"
42 | if _query := r.URL.Query().Get("q"); _query != "" {
43 | query = _query
44 | }
45 |
46 | // Get the limit parameter
47 | var limit int = 100
48 | if _limit := r.URL.Query().Get("limit"); _limit != "" {
49 | limit, _ = strconv.Atoi(_limit)
50 | }
51 |
52 | // Get the strict parameter
53 | var strict bool = false
54 | if _strict := r.URL.Query().Get("strict"); _strict != "" {
55 | strict, _ = strconv.ParseBool(_strict)
56 | }
57 |
58 | // Track the start time
59 | var start time.Time = time.Now()
60 |
61 | // Search for a word in the cache
62 | // Make sure the show which keys you do want to search through,
63 | // and which ones you don't
64 | var res, _ = cache.Search(hermes.SearchParams{
65 | Query: query,
66 | Limit: limit,
67 | Strict: strict,
68 | })
69 |
70 | // Print the duration
71 | fmt.Printf("\nFound %v results in %v", len(res), time.Since(start))
72 |
73 | // Write the courses to the json response
74 | var response, _ = json.Marshal(res)
75 | w.Write(response)
76 | }
77 |
--------------------------------------------------------------------------------
/cloud/socket/router.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "log"
5 | "sync"
6 |
7 | "github.com/gofiber/fiber/v2"
8 | "github.com/gofiber/websocket/v2"
9 | hermes "github.com/realTristan/hermes"
10 | utils "github.com/realTristan/hermes/cloud/socket/utils"
11 | )
12 |
13 | // Set the router for the socket
14 | func SetRouter(app *fiber.App, cache *hermes.Cache) {
15 | // Init a new socket
16 | var socket *Socket = &Socket{
17 | active: false,
18 | mutex: &sync.Mutex{},
19 | }
20 |
21 | // Middleware
22 | app.Use("/ws", func(c *fiber.Ctx) error {
23 | // Check if the socket is active
24 | if socket.IsActive() {
25 | return fiber.ErrLocked
26 | }
27 |
28 | // Check if the request is via socket
29 | if websocket.IsWebSocketUpgrade(c) {
30 | // Allow Locals
31 | c.Locals("allowed", true)
32 |
33 | // Set the socket to active
34 | socket.SetActive()
35 |
36 | // Return the next handler
37 | return c.Next()
38 | }
39 |
40 | // Return an error
41 | return fiber.ErrUpgradeRequired
42 | })
43 |
44 | // Main websocket handler
45 | app.Get("/ws/hermes", websocket.New(func(c *websocket.Conn) {
46 | for {
47 | var (
48 | msg []byte
49 | err error
50 | )
51 |
52 | // Read the message
53 | if _, msg, err = c.ReadMessage(); err != nil {
54 | log.Println("read:", err)
55 | if IsCloseError(err) {
56 | socket.SetInactive()
57 | }
58 | break
59 | }
60 |
61 | // Get the data
62 | var p *utils.Params
63 | if p, err = utils.ParseParams(msg); err != nil {
64 | log.Println("parse:", err)
65 | break
66 | }
67 |
68 | // Get the function
69 | var function string
70 | if function, err = p.GetFunction(); err != nil {
71 | log.Println("function:", err)
72 | break
73 | }
74 |
75 | // Check if the function exists
76 | if fn, ok := Functions[function]; !ok {
77 | if c.WriteMessage(websocket.TextMessage, []byte("Function not found")) != nil {
78 | log.Println("write:", err)
79 | break
80 | }
81 | } else if c.WriteMessage(websocket.TextMessage, fn(p, cache)) != nil {
82 | log.Println("function:", err)
83 | break
84 | }
85 | }
86 | }))
87 | }
88 |
--------------------------------------------------------------------------------
/indices.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // When you delete a number of keys from the cache, the index remains
4 | // the same. Over time, this number will grow to be very large, and will
5 | // cause the cache to use a lot of memory. This function resets the indices
6 | // to be sequential, starting from 0.
7 | // This function is thread-safe.
8 | func (c *Cache) FTSequenceIndices() {
9 | c.mutex.Lock()
10 | defer c.mutex.Unlock()
11 | c.ft.sequenceIndices()
12 | }
13 |
14 | // When you delete a number of keys from the cache, the index remains
15 | // the same. Over time, this number will grow to be very large, and will
16 | // cause the cache to use a lot of memory. This function resets the indices
17 | // to be sequential, starting from 0.
18 | // This function is not thread-safe, and should only be called from
19 | // an exported function.
20 | func (ft *FullText) sequenceIndices() {
21 | // Store the temp variables
22 | var (
23 | tempIndices map[int]string = make(map[int]string)
24 | tempindex int = 0
25 | tempKeys map[string]int = make(map[string]int)
26 | )
27 |
28 | // Fill the temp indices by iterating over the current
29 | // indices and adding them to the tempIndices map
30 | for _, value := range ft.indices {
31 | tempIndices[tempindex] = value
32 | tempindex++
33 | }
34 |
35 | // Fill the temp keys with the opposites of ft.indices
36 | for key, value := range tempIndices {
37 | tempKeys[value] = key
38 | }
39 |
40 | // Iterate over the ft storage
41 | for word, data := range ft.storage {
42 | // Check if the data is []int or int
43 | if v, ok := data.(int); ok {
44 | ft.storage[word] = tempKeys[ft.indices[v]]
45 | continue
46 | }
47 |
48 | // If the data is []int, loop through the slice
49 | if keys, ok := data.([]int); !ok {
50 | for i := 0; i < len(keys); i++ {
51 | var index int = keys[i]
52 |
53 | // Get the key from the old indices
54 | var key string = ft.indices[index]
55 |
56 | // Set the new index
57 | ft.storage[word].([]int)[i] = tempKeys[key]
58 | }
59 | }
60 | }
61 |
62 | // Set the old variables to the new variables
63 | ft.indices = tempIndices
64 | ft.index = tempindex
65 | }
66 |
--------------------------------------------------------------------------------
/delete.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | // Delete is a method of the Cache struct that removes a key from the cache.
4 | // If the full-text index is initialized, it is also removed from there.
5 | // This method is thread-safe.
6 | //
7 | // Parameters:
8 | // - key: A string representing the key to remove from the cache.
9 | //
10 | // Returns:
11 | // - None
12 | func (c *Cache) Delete(key string) {
13 | c.mutex.Lock()
14 | defer c.mutex.Unlock()
15 | c.delete(key)
16 | }
17 |
18 | // delete is a method of the Cache struct that removes a key from the cache.
19 | // If the full-text index is initialized, it is also removed from there.
20 | // This method is not thread-safe and should only be called from an exported function.
21 | //
22 | // Parameters:
23 | // - key: A string representing the key to remove from the cache.
24 | //
25 | // Returns:
26 | // - None
27 | func (c *Cache) delete(key string) {
28 | // Delete the key from the FT cache
29 | if c.ft != nil {
30 | c.ft.delete(key)
31 | }
32 |
33 | // Delete the key from the cache
34 | delete(c.data, key)
35 | }
36 |
37 | // delete is a method of the FullText struct that removes a key from the full-text storage.
38 | // This function is not thread-safe and should only be called from an exported function.
39 | //
40 | // Parameters:
41 | // - key: A string representing the key to remove from the full-text storage.
42 | //
43 | // Returns:
44 | // - None
45 | func (ft *FullText) delete(key string) {
46 | // Remove the key from the ft.storage
47 | for word, data := range ft.storage {
48 | // Check if the data is []int or int
49 | if _, ok := data.(int); ok {
50 | delete(ft.storage, word)
51 | continue
52 | }
53 |
54 | // If the data is []int, loop through the slice
55 | if keys, ok := data.([]int); !ok {
56 | for i := 0; i < len(keys); i++ {
57 | if key != ft.indices[keys[i]] {
58 | continue
59 | }
60 |
61 | // Remove the key from the ft.storage slice
62 | keys = append(keys[:i], keys[i+1:]...)
63 | ft.storage[word] = keys
64 | break
65 | }
66 |
67 | // If keys is empty, remove it from the storage
68 | if len(keys) == 0 {
69 | delete(ft.storage, word)
70 | } else if len(keys) == 1 {
71 | ft.storage[word] = keys[0]
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/cloud/api/routes.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | "github.com/realTristan/hermes/cloud/api/handlers"
7 | )
8 |
9 | // SetRoutes is a function that sets the routes for the hermes Cache API.
10 | // Parameters:
11 | // - app (*fiber.App): A pointer to a fiber.App struct.
12 | // - cache (*hermes.Cache): A pointer to a hermes.Cache struct.
13 | //
14 | // Returns:
15 | // - void: This function does not return anything.
16 | func SetRoutes(app *fiber.App, cache *hermes.Cache) {
17 | // Dev Testing Handler
18 | app.Get("/dev/hermes", func(c *fiber.Ctx) error {
19 | return c.SendString("hermes Cache API Successfully Running!")
20 | })
21 |
22 | // Cache Handlers
23 | app.Get("/cache/values", handlers.Values(cache))
24 | app.Get("/cache/length", handlers.Length(cache))
25 | app.Post("/cache/clean", handlers.Clean(cache))
26 | app.Post("/cache/set", handlers.Set(cache))
27 | app.Delete("/cache/delete", handlers.Delete(cache))
28 | app.Get("/cache/get", handlers.Get(cache))
29 | app.Get("/cache/get/all", handlers.GetAll(cache))
30 | app.Get("/cache/keys", handlers.Keys(cache))
31 | app.Get("/cache/info", handlers.Info(cache))
32 | app.Get("/cache/info/testing", handlers.InfoForTesting(cache))
33 | app.Get("/cache/exists", handlers.Exists(cache))
34 |
35 | // Full-text Cache Handlers
36 | app.Post("/ft/init", handlers.FTInit(cache))
37 | app.Post("/ft/init/json", handlers.FTInitJson(cache))
38 | app.Post("/ft/clean", handlers.FTClean(cache))
39 | app.Get("/ft/search", handlers.Search(cache))
40 | app.Get("/ft/search/oneword", handlers.SearchOneWord(cache))
41 | app.Get("/ft/search/values", handlers.SearchValues(cache))
42 | app.Get("/ft/search/withkey", handlers.SearchWithKey(cache))
43 | app.Post("/ft/maxbytes", handlers.FTSetMaxBytes(cache))
44 | app.Post("/ft/maxsize", handlers.FTSetMaxSize(cache))
45 | app.Post("/ft/minwordlength", handlers.FTSetMinWordLength(cache))
46 | app.Get("/ft/storage", handlers.FTStorage(cache))
47 | app.Get("/ft/storage/size", handlers.FTStorageSize(cache))
48 | app.Get("/ft/storage/length", handlers.FTStorageLength(cache))
49 | app.Get("/ft/isinitialized", handlers.FTIsInitialized(cache))
50 | app.Post("/ft/indices/sequence", handlers.FTSequenceIndices(cache))
51 | }
52 |
--------------------------------------------------------------------------------
/testing/speed/func_speeds/fnspeeds.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | hermes "github.com/realTristan/hermes"
8 | )
9 |
10 | func main() {
11 | // Cache
12 | var cache *hermes.Cache = hermes.InitCache()
13 |
14 | /* Initialize the FT cache
15 | if err := cache.InitFTWithJson("../../data/data_hash.json", maxWords, maxSizeBytes, schema); err != nil {
16 | fmt.Println(err)
17 | }
18 | */
19 | // cache.Info()
20 |
21 | // Initialize the FT cache
22 | if err := cache.FTInit(-1, -1, 3); err != nil {
23 | fmt.Println(err)
24 | }
25 |
26 | // The data for the user_id and user_id2 key
27 | var data = map[string]any{
28 | "name": cache.WithFT("tristan1"),
29 | "age": 17,
30 | }
31 | var data2 = map[string]any{
32 | "name": map[string]any{
33 | "$hermes.full_text": true,
34 | "value": "tristan2",
35 | },
36 | "age": 17,
37 | }
38 |
39 | // Set the value in the cache
40 | duration("Set", func() {
41 | if err := cache.Set("user_id", data); err != nil {
42 | fmt.Println(err)
43 | }
44 |
45 | if err := cache.Set("user_id2", data2); err != nil {
46 | fmt.Println(err)
47 | }
48 | })
49 |
50 | // Get the user_id value
51 | duration("Get", func() {
52 | var user = cache.Get("user_id")
53 | fmt.Println(user)
54 | })
55 |
56 | // Search for a word in the cache
57 | duration("Search", func() {
58 | var result, _ = cache.SearchOneWord(hermes.SearchParams{
59 | Query: "tristan",
60 | Limit: 100,
61 | Strict: false,
62 | })
63 | fmt.Println(result)
64 | })
65 |
66 | // Print all the cache info
67 | //cache.Info()
68 |
69 | /* Reset the FT cache
70 | if err := cache.ResetFT(maxWords, maxSizeBytes, schema); err != nil {
71 | fmt.Println(err)
72 | }*/
73 |
74 | // Delete the user_id key
75 | duration("Delete", func() {
76 | cache.Delete("user_id")
77 | })
78 |
79 | // Search for a word in the cache
80 | duration("Search", func() {
81 | var result, _ = cache.SearchOneWord(hermes.SearchParams{
82 | Query: "tristan",
83 | Limit: 100,
84 | Strict: false,
85 | })
86 | fmt.Println(result)
87 | })
88 |
89 | // Print all the cache info
90 | //cache.Info()
91 | }
92 |
93 | // Track the duration of a function
94 | func duration(key string, f func()) {
95 | var start time.Time = time.Now()
96 | f()
97 | fmt.Printf("\nExecution Duration for %s: %s\n", key, time.Since(start))
98 | }
99 |
--------------------------------------------------------------------------------
/website/src/app/laptop/laptop.component.scss:
--------------------------------------------------------------------------------
1 | .laptop {
2 | position: relative;
3 | max-width: 40rem;
4 |
5 | .laptop_screen {
6 | position: relative;
7 | z-index: 1;
8 | padding: 3%;
9 | border-radius: 2rem;
10 | background: #ecf1f7;
11 | background-image: linear-gradient(to bottom, #333, #111);
12 | box-shadow: 0 .1rem 0 #cfcfcf;
13 | border: 2px solid #ccc;
14 |
15 | img {
16 | display: block;
17 | max-width: 100%;
18 | height: auto;
19 | aspect-ratio: attr(width) / attr(height);
20 | background: #000;
21 | }
22 | }
23 |
24 | .laptop_bottom {
25 | position: relative;
26 | z-index: 1;
27 | margin-right: -7%;
28 | margin-left: -7%;
29 | height: .7rem;
30 | background: #e9eff5;
31 | background-image: linear-gradient(to right, #d2dde9 0%, #f9fcff 15%, #e5ebf2 40%, #e5ebf2 60%, #f9fcff 85%, #d2dde9 100%);
32 |
33 | &::before {
34 | display: block;
35 | margin: 0 auto;
36 | width: 20%;
37 | height: .7rem;
38 | border-radius: 0 0 .2rem .2rem;
39 | background: #f6f9fc;
40 | background-image: linear-gradient(to right, #c3cfdb 0%, #f6f9fc 10%, #f6f9fc 90%, #c3cfdb 100%);
41 | content: " ";
42 | }
43 | }
44 |
45 | .laptop_under {
46 | position: absolute;
47 | top: 100%;
48 | left: 25%;
49 | display: block;
50 | width: 50%;
51 | height: 1.5rem;
52 | background: #e2e8f0;
53 | background-image: linear-gradient(to bottom, #e2e8f0, #bec7d1);
54 |
55 | &::after,
56 | &::before {
57 | position: absolute;
58 | top: 0%;
59 | right: 100%;
60 | bottom: 0;
61 | display: block;
62 | width: 50%;
63 | border-bottom-left-radius: 100%;
64 | background: inherit;
65 | content: " ";
66 | }
67 |
68 | &::after {
69 | right: auto;
70 | left: 100%;
71 | border-bottom-right-radius: 100%;
72 | border-bottom-left-radius: 0;
73 | }
74 | }
75 |
76 | .laptop_shadow {
77 | position: absolute;
78 | right: -10%;
79 | bottom: -2.5rem;
80 | left: -10%;
81 | z-index: 0;
82 | height: 2rem;
83 | background: radial-gradient(ellipse closest-side, #000, transparent);
84 | opacity: 0.5;
85 | }
86 | }
--------------------------------------------------------------------------------
/searchvalues.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // SearchValues searches for all records containing the given query in the specified schema with a limit of results to return.
9 | // Parameters:
10 | // - c (c *Cache): A pointer to the Cache struct
11 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
12 | //
13 | // Returns:
14 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
15 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
16 | // - error: An error if the query or limit is invalid
17 | func (c *Cache) SearchValues(sp SearchParams) ([]map[string]any, error) {
18 | // If the query is empty, return an error
19 | if len(sp.Query) == 0 {
20 | return []map[string]any{}, errors.New("invalid query")
21 | }
22 |
23 | // If no limit is provided, set it to 10
24 | if sp.Limit == 0 {
25 | sp.Limit = 10
26 | }
27 |
28 | // If no schema is provided, set it to all columns
29 | if len(sp.Schema) == 0 {
30 | sp.Schema = make(map[string]bool)
31 | }
32 |
33 | // Set the query to lowercase
34 | sp.Query = strings.ToLower(sp.Query)
35 |
36 | // Lock the mutex
37 | c.mutex.RLock()
38 | defer c.mutex.RUnlock()
39 |
40 | // Search the data
41 | return c.searchValues(sp), nil
42 | }
43 |
44 | // searchValues searches for all records containing the given query in the specified schema with a limit of results to return.
45 | // Parameters:
46 | // - c (c *Cache): A pointer to the Cache struct
47 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
48 | //
49 | // Returns:
50 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
51 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
52 | func (c *Cache) searchValues(sp SearchParams) []map[string]any {
53 | // Define variables
54 | var result []map[string]any = []map[string]any{}
55 |
56 | // Iterate over the query result
57 | for _, item := range c.data {
58 | // Iterate over the keys and values for the data for that index
59 | for key, value := range item {
60 | switch {
61 | case len(result) >= sp.Limit:
62 | return result
63 | case !sp.Schema[key]:
64 | continue
65 | }
66 |
67 | // Check if the value contains the query
68 | if v, ok := value.(string); ok {
69 | if strings.Contains(strings.ToLower(v), sp.Query) {
70 | result = append(result, item)
71 | }
72 | }
73 | }
74 | }
75 |
76 | // Return the result
77 | return result
78 | }
79 |
--------------------------------------------------------------------------------
/testing/nocache/speed/speed.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 |
10 | hermes "github.com/realTristan/hermes/nocache"
11 | )
12 |
13 | // Results: hermes is about 40x faster than a basic search
14 | func main() {
15 | BasicSearch()
16 | hermesSearch()
17 | }
18 |
19 | // Basic Search
20 | func BasicSearch() {
21 | // read the json data
22 | if data, err := readJson("../../../testing/data/data_array.json"); err != nil {
23 | panic(err)
24 | } else {
25 | var average int64 = 0
26 | for i := 0; i < 100; i++ {
27 | var startTime = time.Now()
28 | // Iterate over the data array
29 | for _, val := range data {
30 | // Iterate over the map
31 | for k, v := range val {
32 | var data map[string]interface{}
33 | if v, ok := v.(map[string]interface{}); !ok {
34 | continue
35 | } else {
36 | data = v
37 | }
38 | // Get the value
39 | var value string = data["$hermes.value"].(string)
40 |
41 | // Check if the value contains the search term
42 | if strings.Contains(strings.ToLower(value), strings.ToLower("computer")) {
43 | var _ = k
44 | }
45 | }
46 | }
47 | average += time.Since(startTime).Nanoseconds()
48 | }
49 | var averageNanos float64 = float64(average) / 100
50 | var averageMillis float64 = averageNanos / 1000000
51 | fmt.Println("\nBasic: Average time is: ", averageNanos, "ns or", averageMillis, "ms")
52 | }
53 | }
54 |
55 | // hermes Search
56 | func hermesSearch() {
57 | // Initialize the cache
58 | var cache, err = hermes.InitWithJson("../../../data/data_array.json", 3)
59 | if err != nil {
60 | panic(err)
61 | }
62 |
63 | var average int64 = 0
64 | for i := 0; i < 100; i++ {
65 | // Track the start time
66 | var start time.Time = time.Now()
67 |
68 | // Search for a word in the cache
69 | cache.Search(hermes.SearchParams{
70 | Query: "computer",
71 | Limit: 100,
72 | Strict: false,
73 | })
74 |
75 | // Print the duration
76 | average += time.Since(start).Nanoseconds()
77 | }
78 |
79 | var averageNanos float32 = float32(average) / 100
80 | var averageMillis float32 = averageNanos / 1000000
81 | fmt.Println("\nhermes: Average time is: ", averageNanos, "ns or", averageMillis, "ms")
82 | }
83 |
84 | // Read a json file
85 | func readJson(file string) ([]map[string]interface{}, error) {
86 | var v []map[string]interface{} = []map[string]interface{}{}
87 |
88 | // Read the json data
89 | if data, err := os.ReadFile(file); err != nil {
90 | return nil, err
91 | } else {
92 | if err := json.Unmarshal(data, &v); err != nil {
93 | return nil, err
94 | }
95 | }
96 | return v, nil
97 | }
98 |
--------------------------------------------------------------------------------
/set.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import "fmt"
4 |
5 | // Set is a method of the Cache struct that sets a value in the cache for the specified key.
6 | // This function is thread-safe.
7 | //
8 | // Parameters:
9 | // - key: A string representing the key to set the value for.
10 | // - value: A map[string]any representing the value to set.
11 | //
12 | // Returns:
13 | // - Error
14 | func (c *Cache) Set(key string, value map[string]any) error {
15 | c.mutex.Lock()
16 | defer c.mutex.Unlock()
17 | return c.set(key, value)
18 | }
19 |
20 | // set is a method of the Cache struct that sets a value in the cache for the specified key.
21 | // This function is not thread-safe, and should only be called from an exported function.
22 | // If fullText is true, set the value in the full-text cache as well.
23 | //
24 | // Parameters:
25 | // - key: A string representing the key to set the value for.
26 | // - value: A map[string]any representing the value to set.
27 | //
28 | // Returns:
29 | // - An error if the full-text cache key already exists. Otherwise, nil.
30 | func (c *Cache) set(key string, value map[string]any) error {
31 | if _, ok := c.data[key]; ok {
32 | return fmt.Errorf("full-text cache key already exists (%s). delete it before setting it another value", key)
33 | }
34 |
35 | // Update the value in the FT cache
36 | if c.ft != nil {
37 | if err := c.ftSet(key, value); err != nil {
38 | return err
39 | }
40 | }
41 |
42 | // Update the value in the cache
43 | c.data[key] = value
44 |
45 | // Return nil for no error
46 | return nil
47 | }
48 |
49 | // ftSet is a method of the Cache struct that sets a value in the full-text cache for the specified key.
50 | // This function is not thread-safe, and should only be called from an exported function.
51 | //
52 | // Parameters:
53 | // - key: A string representing the key to set the value for.
54 | // - value: A map[string]any representing the value to set.
55 | //
56 | // Returns:
57 | // - An error if the full-text storage limit or byte-size limit is reached. Otherwise, nil.
58 | func (c *Cache) ftSet(key string, value map[string]any) error {
59 | var ts *TempStorage = NewTempStorage(c.ft)
60 | for k, v := range value {
61 | if ftv := WFTGetValue(v); len(ftv) == 0 {
62 | continue
63 | } else {
64 | // Update the value
65 | value[k] = ftv
66 |
67 | // Insert the value in the temp storage
68 | if err := ts.insert(c.ft, key, ftv); err != nil {
69 | return err
70 | }
71 | }
72 | }
73 |
74 | // Iterate over the temp storage and set the values with len 1 to int
75 | ts.cleanSingleArrays()
76 |
77 | // Set the full-text cache to the temp map
78 | ts.updateFullText(c.ft)
79 |
80 | // Return nil for no errors
81 | return nil
82 | }
83 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/init.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | hermes "github.com/realTristan/hermes"
5 | utils "github.com/realTristan/hermes/cloud/socket/utils"
6 | )
7 |
8 | // FTInit is a handler function that returns a fiber context handler function for initializing the full-text search cache.
9 | // Parameters:
10 | // - p (*utils.Params): A pointer to a utils.Params struct.
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the initialization fails.
15 | func FTInit(p *utils.Params, c *hermes.Cache) []byte {
16 | var (
17 | maxSize int
18 | maxBytes int
19 | minWordLength int
20 | )
21 |
22 | // Get the max length parameter
23 | if err := utils.GetMaxSizeParam(p, &maxSize); err != nil {
24 | return utils.Error(err)
25 | }
26 |
27 | // Get the max bytes parameter
28 | if err := utils.GetMaxBytesParam(p, &maxBytes); err != nil {
29 | return utils.Error(err)
30 | }
31 |
32 | // Get the min word length parameter
33 | if err := utils.GetMinWordLengthParam(p, &minWordLength); err != nil {
34 | return utils.Error(err)
35 | }
36 |
37 | // Initialize the full-text cache
38 | if err := c.FTInit(maxSize, maxBytes, minWordLength); err != nil {
39 | return utils.Error(err)
40 | }
41 | return utils.Success("null")
42 | }
43 |
44 | // FTInitJson is a handler function that returns a fiber context handler function for initializing the full-text search cache with a JSON object.
45 | // Parameters:
46 | // - p (*utils.Params): A pointer to a utils.Params struct.
47 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
48 | //
49 | // Returns:
50 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the initialization fails.
51 | func FTInitJson(p *utils.Params, c *hermes.Cache) []byte {
52 | var (
53 | maxSize int
54 | maxBytes int
55 | minWordLength int
56 | json map[string]map[string]any
57 | )
58 |
59 | // Get the max length from the query
60 | if err := utils.GetMaxSizeParam(p, &maxSize); err != nil {
61 | return utils.Error(err)
62 | }
63 |
64 | // Get the max bytes from the query
65 | if err := utils.GetMaxBytesParam(p, &maxBytes); err != nil {
66 | return utils.Error(err)
67 | }
68 |
69 | // Get the min word length from the query
70 | if err := utils.GetMinWordLengthParam(p, &minWordLength); err != nil {
71 | return utils.Error(err)
72 | }
73 |
74 | // Get the JSON from the query
75 | if err := utils.GetJSONParam(p, &json); err != nil {
76 | return utils.Error(err)
77 | }
78 |
79 | // Initialize the full-text cache
80 | if err := c.FTInitWithMap(json, maxSize, maxBytes, minWordLength); err != nil {
81 | return utils.Error(err)
82 | }
83 |
84 | // Return success message
85 | return utils.Success("null")
86 | }
87 |
--------------------------------------------------------------------------------
/nocache/searchvalues.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // SearchValues searches for all occurrences of the given query string in the FullText object's data.
9 | // The search is done by splitting the query into separate words and looking for each of them in the data.
10 | // The search result is limited to the specified number of entries, and can optionally be filtered to only
11 | // include keys that match a given schema.
12 | // Parameters:
13 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
14 | //
15 | // Returns:
16 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs
17 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned.
18 | // - error: An error object. If no error occurs, this will be nil.
19 | //
20 | // Note: The search is case-insensitive.
21 | func (ft *FullText) SearchValues(sp SearchParams) ([]map[string]any, error) {
22 | switch {
23 | case len(sp.Query) == 0:
24 | return []map[string]any{}, errors.New("invalid query")
25 | case sp.Limit < 1:
26 | return []map[string]any{}, errors.New("invalid limit")
27 | }
28 |
29 | // Set the query to lowercase
30 | sp.Query = strings.ToLower(sp.Query)
31 |
32 | // Lock the mutex
33 | ft.mutex.RLock()
34 | defer ft.mutex.RUnlock()
35 |
36 | // Search the data
37 | return ft.searchValues(sp), nil
38 | }
39 |
40 | // searchValues searches for all occurrences of the given query string in the FullText object's data.
41 | // The search is done by looking for the query as a substring in any value associated with any key in each entry of the data.
42 | // The search result is limited to the specified number of entries, and can optionally be filtered to only include keys that match a given schema.
43 | // Parameters:
44 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
45 | //
46 | // Returns:
47 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs
48 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned.
49 | //
50 | // Note: The search is case-insensitive.
51 | func (ft *FullText) searchValues(sp SearchParams) []map[string]any {
52 | // Define variables
53 | var result []map[string]any = []map[string]any{}
54 |
55 | // Iterate over the query result
56 | for i := 0; i < len(ft.data); i++ {
57 | // Iterate over the keys and values for the data
58 | for key, value := range ft.data[i] {
59 | if v, ok := value.(string); !ok {
60 | continue
61 | } else {
62 | switch {
63 | case len(result) >= sp.Limit:
64 | return result
65 | case !sp.Schema[key]:
66 | continue
67 | case strings.Contains(strings.ToLower(v), sp.Query):
68 | result = append(result, ft.data[i])
69 | }
70 | }
71 | }
72 | }
73 |
74 | // Return the result
75 | return result
76 | }
77 |
--------------------------------------------------------------------------------
/testing/speed/search_speeds/searchspeeds.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 |
10 | hermes "github.com/realTristan/hermes"
11 | )
12 |
13 | // Results: hermes is about 40x faster than a basic search
14 | func main() {
15 | BasicSearch()
16 | hermesSearch()
17 | }
18 |
19 | // Basic Search
20 | func BasicSearch() {
21 | // read the json data
22 | if data, err := readJson("../../data/data_hash.json"); err != nil {
23 | panic(err)
24 | } else {
25 | var average int64 = 0
26 | for i := 0; i < 100; i++ {
27 | var startTime = time.Now()
28 | // print the data
29 | for _, val := range data {
30 | for k, v := range val {
31 | var data map[string]any
32 | if v, ok := v.(map[string]any); !ok {
33 | continue
34 | } else {
35 | data = v
36 | }
37 | // Get the value
38 | var value string = data["$hermes.value"].(string)
39 |
40 | if strings.Contains(strings.ToLower(value), strings.ToLower("computer")) {
41 | var _ = k
42 | }
43 | }
44 | }
45 | average += time.Since(startTime).Nanoseconds()
46 | }
47 | var (
48 | averageNanos float64 = float64(average) / 100
49 | averageMillis float64 = averageNanos / 1000000
50 | )
51 | fmt.Println("Basic: Average time is: ", averageNanos, "ns or", averageMillis, "ms")
52 | }
53 | }
54 |
55 | // hermes Search
56 | func hermesSearch() {
57 | // Initialize the cache
58 | var cache *hermes.Cache = hermes.InitCache()
59 |
60 | // Initialize the FT cache with a json file
61 | cache.FTInitWithJson("../../data/data_hash.json", -1, -1, 3)
62 | var (
63 | average int64 = 0
64 | total int = 0
65 | )
66 | for i := 0; i < 100; i++ {
67 | // Track the start time
68 | var (
69 | start time.Time = time.Now()
70 |
71 | // Search for a word in the cache
72 | res, _ = cache.Search(hermes.SearchParams{
73 | Query: "computer science",
74 | Limit: 100,
75 | Strict: false,
76 | Schema: map[string]bool{
77 | "name": true,
78 | "description": true,
79 | "title": true,
80 | },
81 | })
82 | )
83 |
84 | // Print the duration
85 | average += time.Since(start).Nanoseconds()
86 | total += len(res)
87 | }
88 | var (
89 | averageNanos float64 = float64(average) / 100
90 | averageMillis float64 = averageNanos / 1000000
91 | )
92 | fmt.Println("hermes: Average time is: ", averageNanos, "ns or", averageMillis, "ms")
93 | fmt.Println("hermes: Results: ", total)
94 | }
95 |
96 | // Read a json file
97 | func readJson(file string) (map[string]map[string]any, error) {
98 | var v map[string]map[string]any = map[string]map[string]any{}
99 |
100 | // Read the json data
101 | if data, err := os.ReadFile(file); err != nil {
102 | return nil, err
103 | } else if err := json.Unmarshal(data, &v); err != nil {
104 | return nil, err
105 | }
106 | return v, nil
107 | }
108 |
--------------------------------------------------------------------------------
/website/src/app/code-example/code-example.component.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | func main() {{'{'}}
11 | // Initialize the cache
12 | cache := hermes.InitCache()
13 | cache.FTInit(10, -1, 3)
14 |
15 | // Set a value in the cache with the key "user_id"
16 | cache.Set("user_id", map[string]any {{'{'}}
17 | "name": cache.WithFT("tristan"),
18 | "age": 17,
19 | {{'}'}})
20 |
21 | // Search for the word "tristan"
22 | result, err := cache.Search(hermes.SearchParams{{'{'}}
23 | Query: "tristan",
24 | Limit: 100,
25 | Strict: false,
26 | {{'}'}})
27 | fmt.Println(result, err)
28 | {{'}'}}
29 |
30 |
31 |
--------------------------------------------------------------------------------
/website/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "hermescloud": {
7 | "projectType": "application",
8 | "schematics": {},
9 | "root": "",
10 | "sourceRoot": "src",
11 | "prefix": "app",
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/hermescloud",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": [
20 | "zone.js"
21 | ],
22 | "tsConfig": "tsconfig.app.json",
23 | "assets": [
24 | "src/favicon.ico",
25 | "src/assets"
26 | ],
27 | "styles": [
28 | "src/styles.css"
29 | ],
30 | "scripts": []
31 | },
32 | "configurations": {
33 | "production": {
34 | "budgets": [
35 | {
36 | "type": "initial",
37 | "maximumWarning": "500kb",
38 | "maximumError": "1mb"
39 | },
40 | {
41 | "type": "anyComponentStyle",
42 | "maximumWarning": "2kb",
43 | "maximumError": "4kb"
44 | }
45 | ],
46 | "outputHashing": "all"
47 | },
48 | "development": {
49 | "buildOptimizer": false,
50 | "optimization": false,
51 | "vendorChunk": true,
52 | "extractLicenses": false,
53 | "sourceMap": true,
54 | "namedChunks": true
55 | }
56 | },
57 | "defaultConfiguration": "production"
58 | },
59 | "serve": {
60 | "builder": "@angular-devkit/build-angular:dev-server",
61 | "configurations": {
62 | "production": {
63 | "browserTarget": "hermescloud:build:production"
64 | },
65 | "development": {
66 | "browserTarget": "hermescloud:build:development"
67 | }
68 | },
69 | "defaultConfiguration": "development"
70 | },
71 | "extract-i18n": {
72 | "builder": "@angular-devkit/build-angular:extract-i18n",
73 | "options": {
74 | "browserTarget": "hermescloud:build"
75 | }
76 | },
77 | "test": {
78 | "builder": "@angular-devkit/build-angular:karma",
79 | "options": {
80 | "polyfills": [
81 | "zone.js",
82 | "zone.js/testing"
83 | ],
84 | "tsConfig": "tsconfig.spec.json",
85 | "assets": [
86 | "src/favicon.ico",
87 | "src/assets"
88 | ],
89 | "styles": [
90 | "src/styles.css"
91 | ],
92 | "scripts": []
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/nocache/searchwithkey.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // SearchWithKey searches for all records containing the given query in the specified key column with a limit of results to return.
9 | // The search result is limited to the specified number of entries.
10 | // Parameters:
11 | // - query (string): The search query to use. This string will be searched for as a substring in the data value associated with the given key.
12 | // - key (string): The name of the key in the data whose data value should be searched.
13 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored.
14 | //
15 | // Returns:
16 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
17 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
18 | // - error: An error if the query, key or limit is invalid.
19 | func (ft *FullText) SearchWithKey(query string, key string, limit int) ([]map[string]any, error) {
20 | switch {
21 | case len(key) == 0:
22 | return []map[string]any{}, errors.New("invalid key")
23 | case len(query) == 0:
24 | return []map[string]any{}, errors.New("invalid query")
25 | case limit < 1:
26 | return []map[string]any{}, errors.New("invalid limit")
27 | }
28 |
29 | // Set the query to lowercase
30 | query = strings.ToLower(query)
31 |
32 | // Lock the mutex
33 | ft.mutex.RLock()
34 | defer ft.mutex.RUnlock()
35 |
36 | // Search for the query
37 | return ft.searchWithKey(query, key, limit), nil
38 | }
39 |
40 | // searchWithKey searches for all occurrences of the given query string in the FullText object's data associated with the specified key.
41 | // The search is done by looking for the query as a substring in the data value associated with the given key.
42 | // The search result is limited to the specified number of entries.
43 | // Parameters:
44 | // - query (string): The search query to use. This string will be searched for as a substring in the data value associated with the given key.
45 | // - key (string): The name of the key in the data whose data value should be searched.
46 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored.
47 | //
48 | // Returns:
49 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs
50 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned.
51 | // - error: An error object. If no error occurs, this will be nil.
52 | //
53 | // Note: The search is case-insensitive.
54 | func (ft *FullText) searchWithKey(query string, key string, limit int) []map[string]any {
55 | // Define variables
56 | var result []map[string]any = []map[string]any{}
57 |
58 | // Iterate over the query result
59 | for i := 0; i < len(ft.data); i++ {
60 | if v, ok := ft.data[i][key].(string); !ok {
61 | continue
62 | } else {
63 | switch {
64 | case len(result) >= limit:
65 | return result
66 | case strings.Contains(strings.ToLower(v), query):
67 | result = append(result, ft.data[i])
68 | }
69 | }
70 | }
71 |
72 | // Return the result
73 | return result
74 | }
75 |
--------------------------------------------------------------------------------
/cloud/api/handlers/init.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gofiber/fiber/v2"
5 | hermes "github.com/realTristan/hermes"
6 | utils "github.com/realTristan/hermes/cloud/api/utils"
7 | )
8 |
9 | // FTInit is a handler function that returns a fiber context handler function for initializing the full-text search cache.
10 | // Parameters:
11 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
12 | //
13 | // Returns:
14 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that initializes the full-text search cache using the max length, max bytes, and min word length parameters provided in the query string and returns a success message or an error message if the parameters are not provided or if the initialization fails.
15 | func FTInit(c *hermes.Cache) func(ctx *fiber.Ctx) error {
16 | return func(ctx *fiber.Ctx) error {
17 | var (
18 | maxSize int
19 | maxBytes int
20 | minWordLength int
21 | )
22 |
23 | // Get the max length parameter
24 | if err := utils.GetMaxSizeParam(ctx, &maxSize); err != nil {
25 | return ctx.Send(utils.Error(err))
26 | }
27 |
28 | // Get the max bytes parameter
29 | if err := utils.GetMaxBytesParam(ctx, &maxBytes); err != nil {
30 | return ctx.Send(utils.Error(err))
31 | }
32 |
33 | // Get the min word length parameter
34 | if err := utils.GetMinWordLengthParam(ctx, &minWordLength); err != nil {
35 | return ctx.Send(utils.Error(err))
36 | }
37 |
38 | // Initialize the full-text cache
39 | if err := c.FTInit(maxSize, maxBytes, minWordLength); err != nil {
40 | return ctx.Send(utils.Error(err))
41 | }
42 | return ctx.Send(utils.Success("null"))
43 | }
44 | }
45 |
46 | // FTInitJson is a handler function that returns a fiber context handler function for initializing the full-text search cache with a JSON object.
47 | // Parameters:
48 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
49 | //
50 | // Returns:
51 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that initializes the full-text search cache using a JSON object, max length, max bytes, and min word length parameters provided in the query string and returns a success message or an error message if the parameters are not provided or if the initialization fails.
52 | func FTInitJson(c *hermes.Cache) func(ctx *fiber.Ctx) error {
53 | return func(ctx *fiber.Ctx) error {
54 | var (
55 | maxSize int
56 | maxBytes int
57 | minWordLength int
58 | json map[string]map[string]interface{}
59 | )
60 |
61 | // Get the max length from the query
62 | if err := utils.GetMaxSizeParam(ctx, &maxSize); err != nil {
63 | return ctx.Send(utils.Error(err))
64 | }
65 |
66 | // Get the max bytes from the query
67 | if err := utils.GetMaxBytesParam(ctx, &maxBytes); err != nil {
68 | return ctx.Send(utils.Error(err))
69 | }
70 |
71 | // Get the min word length from the query
72 | if err := utils.GetMinWordLengthParam(ctx, &minWordLength); err != nil {
73 | return ctx.Send(utils.Error(err))
74 | }
75 |
76 | // Get the JSON from the query
77 | if err := utils.GetJSONParam(ctx, &json); err != nil {
78 | return ctx.Send(utils.Error(err))
79 | }
80 |
81 | // Initialize the full-text cache
82 | if err := c.FTInitWithMap(json, maxSize, maxBytes, minWordLength); err != nil {
83 | return ctx.Send(utils.Error(err))
84 | }
85 |
86 | // Return success message
87 | return ctx.Send(utils.Success("null"))
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/utils/strings.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // IsAlphaNumChar is a function that checks if a given byte is an alphanumeric character.
8 | // Parameters:
9 | // - c (byte): The byte to check.
10 | //
11 | // Returns:
12 | // - bool: true if the byte is an alphanumeric character, false otherwise.
13 | func IsAlphaNumChar(c byte) bool {
14 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
15 | }
16 |
17 | // IsAlphaNum is a function that checks if a given string consists entirely of alphanumeric characters.
18 | // Parameters:
19 | // - s (string): The string to check.
20 | //
21 | // Returns:
22 | // - bool: true if the string consists entirely of alphanumeric characters, false otherwise.
23 | func IsAlphaNum(s string) bool {
24 | for _, c := range s {
25 | if !IsAlphaNumChar(byte(c)) {
26 | return false
27 | }
28 | }
29 | return true
30 | }
31 |
32 | // TrimNonAlphaNum is a function that removes non-alphanumeric characters from the beginning and end of a given string.
33 | // Parameters:
34 | // - s (string): The string to trim.
35 | //
36 | // Returns:
37 | // - string: The trimmed string.
38 | func TrimNonAlphaNum(s string) string {
39 | if len(s) == 0 {
40 | return s
41 | }
42 | for !IsAlphaNumChar(s[0]) {
43 | if len(s) < 2 {
44 | return ""
45 | }
46 | s = s[1:]
47 | }
48 | for !IsAlphaNumChar(s[len(s)-1]) {
49 | if len(s) < 2 {
50 | return ""
51 | }
52 | s = s[:len(s)-1]
53 | }
54 | return s
55 | }
56 |
57 | // SplitByAlphaNum is a function that splits a given string into a slice of substrings by alphanumeric characters.
58 | // Parameters:
59 | // - s (string): The string to split.
60 | //
61 | // Returns:
62 | // - []string: A slice of substrings split by alphanumeric characters.
63 | func SplitByAlphaNum(s string) []string {
64 | var (
65 | word string = ""
66 | words []string = make([]string, 0)
67 | )
68 | for i := 0; i < len(s); i++ {
69 | if s[i] == '-' || s[i] == '.' || IsAlphaNumChar(s[i]) {
70 | word += string(s[i])
71 | } else {
72 | if len(word) > 0 {
73 | words = append(words, word)
74 | word = ""
75 | }
76 | }
77 | }
78 | if len(word) > 0 {
79 | words = append(words, word)
80 | }
81 | return words
82 | }
83 |
84 | // RemoveDoubleSpaces is a function that removes double spaces from a given string and returns the modified string.
85 | // Parameters:
86 | // - s (string): The string to remove double spaces from.
87 | //
88 | // Returns:
89 | // - string: The modified string with double spaces removed.
90 | func RemoveDoubleSpaces(s string) string {
91 | for strings.Contains(s, " ") {
92 | s = strings.Replace(s, " ", " ", -1)
93 | }
94 | return s
95 | }
96 |
97 | // Contains is a function that checks if a given string contains another string as a substring.
98 | // Parameters:
99 | // - s1 (string): The string to search for the substring.
100 | // - s2 (string): The substring to search for in the string.
101 | //
102 | // Returns:
103 | // - bool: true if the substring is found in the string, false otherwise.
104 | func Contains(s1 string, s2 string) bool {
105 | var (
106 | s1Len int = len(s1)
107 | s2Len int = len(s2)
108 | )
109 | switch {
110 | case s1Len == s2Len:
111 | return s1 == s2
112 | case s1Len < s2Len:
113 | return false
114 | }
115 | for i := 0; i < s1Len-s2Len; i++ {
116 | if s1[i] == s2[0] {
117 | if s1[i:i+s2Len] == s2 {
118 | return true
119 | }
120 | }
121 | }
122 | return false
123 | }
124 |
--------------------------------------------------------------------------------
/info.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import (
4 | "errors"
5 |
6 | utils "github.com/realTristan/hermes/utils"
7 | )
8 |
9 | // Info is a method of the Cache struct that returns a map with the cache and full-text info.
10 | // This method is thread-safe.
11 | // An error is returned if the full-text index is not initialized.
12 | //
13 | // Returns:
14 | // - A map[string]any representing the cache and full-text info.
15 | // - An error if the full-text index is not initialized.
16 | func (c *Cache) Info() (map[string]any, error) {
17 | c.mutex.RLock()
18 | defer c.mutex.RUnlock()
19 | return c.info()
20 | }
21 |
22 | // info is a method of the Cache struct that returns a map with the cache and full-text info.
23 | // This method is not thread-safe, and should only be called from an exported function.
24 | // An error is returned if the full-text index is not initialized.
25 | //
26 | // Returns:
27 | // - A map[string]any representing the cache and full-text info.
28 | // - An error if the full-text index is not initialized.
29 | func (c *Cache) info() (map[string]any, error) {
30 | var info map[string]any = map[string]any{
31 | "keys": len(c.data),
32 | }
33 |
34 | // Check if the cache full-text has been initialized
35 | if c.ft == nil {
36 | return info, errors.New("full-text is not initialized")
37 | }
38 |
39 | // Add the full-text info to the map
40 | if size, err := utils.Size(c.ft.storage); err != nil {
41 | return info, err
42 | } else {
43 | // Add the full-text info to the map
44 | info["full-text"] = map[string]any{
45 | "keys": len(c.ft.storage),
46 | "index": c.ft.index,
47 | "size": size,
48 | }
49 | }
50 |
51 | // Return the info map
52 | return info, nil
53 | }
54 |
55 | // InfoForTesting is a method of the Cache struct that returns a map with the cache and full-text info for testing purposes.
56 | // This method is thread-safe.
57 | // An error is returned if the full-text index is not initialized.
58 | //
59 | // Returns:
60 | // - A map[string]any representing the cache and full-text info for testing purposes.
61 | // - An error if the full-text index is not initialized.
62 | func (c *Cache) InfoForTesting() (map[string]any, error) {
63 | c.mutex.RLock()
64 | defer c.mutex.RUnlock()
65 | return c.infoForTesting()
66 | }
67 |
68 | // infoForTesting is a method of the Cache struct that returns a map with the cache and full-text info for testing purposes.
69 | // This method is not thread-safe, and should only be called from an exported function.
70 | // An error is returned if the full-text index is not initialized.
71 | //
72 | // Returns:
73 | // - A map[string]any representing the cache and full-text info for testing purposes.
74 | // - An error if the full-text index is not initialized.
75 | func (c *Cache) infoForTesting() (map[string]any, error) {
76 | var info map[string]any = map[string]any{
77 | "keys": len(c.data),
78 | "data": c.data,
79 | }
80 |
81 | // Check if the cache full-text has been initialized
82 | if c.ft == nil {
83 | return info, errors.New("full-text is not initialized")
84 | }
85 |
86 | // Add the full-text info to the map
87 | if size, err := utils.Size(c.ft.storage); err != nil {
88 | return info, err
89 | } else {
90 | info["full-text"] = map[string]any{
91 | "keys": len(c.ft.storage),
92 | "index": c.ft.index,
93 | "size": size,
94 | "storage": c.ft.storage,
95 | "indices": c.ft.indices,
96 | }
97 | }
98 |
99 | // Return the info map
100 | return info, nil
101 | }
102 |
--------------------------------------------------------------------------------
/website/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Full-text cache framework written in Go.
8 |
9 |
10 |
11 |
The speed you need. Execute
12 | lightning fast full-text searches with just a few lines of code. Easily access
13 | your data with caching. Both Socket and API implementations are already
14 | provided. Start now with Go.
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Hermes Go
26 |
27 |
28 |
29 |
Speed made easy. Import Hermes
30 | directly into your Go project without any worry of a network connection.
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Hermes Cloud
41 |
42 |
43 |
44 |
Connect with confidence. Install the Hermes
45 | Cloud CLI and immediately begin developing in other languages. Hermes Cloud takes advantage of
46 | sockets to deliver powerful performance. Start now with Python.
47 |
48 |
49 |
50 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 | Find us on Github!
62 |
63 |
64 |
65 |
Transparency leads to success. Worried about
66 | bugs or malicious intent? Then check out our github, everything is all open-sourced! Leave a
67 | star to support the project and share it with your friends! Click the laptop or here to visit.
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/testing/api/request.py:
--------------------------------------------------------------------------------
1 | import requests, json, base64, time
2 |
3 | def base64_encode(value):
4 | return base64.b64encode(value.encode("utf-8")).decode("utf-8")
5 |
6 | def test_search():
7 | headers = {
8 | "Content-Type": "application/json"
9 | }
10 |
11 | # schema
12 | schema = base64_encode(json.dumps({
13 | "id": False,
14 | "components": False,
15 | "units": False,
16 | "description": True,
17 | "name": True,
18 | "pre_requisites": True,
19 | "title": True
20 | }))
21 |
22 | # search for a value
23 | url = "http://localhost:3000/ft/search"
24 | params = "?query=computer&strict=false&limit=100&schema=" + schema
25 |
26 | # make the request
27 | r = requests.get(url+params, headers=headers)
28 | print(r.text)
29 |
30 | def test_set_json(data):
31 | headers = {
32 | "Content-Type": "application/json"
33 | }
34 |
35 | # set the data
36 | for key in data:
37 | # set the key
38 | url = "http://localhost:3000/cache/set"
39 | params = "?key=" + key + "&value=" + base64_encode(json.dumps(data[key]))
40 |
41 | # make the request
42 | r = requests.post(url+params, headers=headers)
43 | #print(r.text)
44 |
45 | def test_set():
46 | headers = {
47 | "Content-Type": "application/json"
48 | }
49 |
50 | # set the data
51 | url = "http://localhost:3000/cache/set"
52 | params = "?key=testing&value=" + base64_encode(json.dumps({
53 | "name": "tristan"
54 | }))
55 |
56 | # make the request
57 | r = requests.post(url+params, headers=headers)
58 | print(r.text)
59 |
60 |
61 | def test_init_ft_json(data):
62 | headers = {
63 | "Content-Type": "application/json"
64 | }
65 |
66 | # url and params
67 | _json = base64_encode(json.dumps(data))
68 | url = "http://localhost:3000/ft/init/json"
69 | params = "?maxlength=-1&maxbytes=-1&json=" + _json
70 |
71 | # make the request
72 | r = requests.post(url+params, headers=headers)
73 |
74 | # print the response
75 | print(r.text)
76 |
77 |
78 | def test_init_ft():
79 | headers = {
80 | "Content-Type": "application/json"
81 | }
82 |
83 | # url and params
84 | url = "http://localhost:3000/ft/init"
85 | params = "?maxlength=-1&maxbytes=-1"
86 |
87 | # make the request
88 | r = requests.post(url+params, headers=headers)
89 |
90 | # print the response
91 | print(r.text)
92 |
93 |
94 | def test_get():
95 | headers = {
96 | "Content-Type": "application/json"
97 | }
98 |
99 | # get b4a3261059ea6f1b48eb8039e720e0b48d087583
100 | url = "http://localhost:3000/cache/get"
101 | params = "?key=b4a3261059ea6f1b48eb8039e720e0b48d087583"
102 |
103 | # make the request
104 | r = requests.get(url+params, headers=headers)
105 | print(r.text)
106 |
107 | def test_cache_info():
108 | headers = {
109 | "Content-Type": "application/json"
110 | }
111 |
112 | # get b4a3261059ea6f1b48eb8039e720e0b48d087583
113 | url = "http://localhost:3000/cache/info/testing"
114 |
115 | # make the request
116 | r = requests.get(url, headers=headers)
117 | print(r.text)
118 |
119 |
120 | if __name__ == "__main__":
121 | # read the json file from the data folder
122 | with open("data/data_hash.json", "r") as file:
123 | # load the json file
124 | data = json.loads(file.read())
125 | # test_init_ft_json(data)
126 | test_init_ft()
127 | # test_cache_info()
128 | # test_get()
129 | # test_set_json(data)
130 | # test_set()
131 | # test_cache_info()
132 |
133 | st = time.time()
134 | test_search()
135 | print(time.time() - st)
--------------------------------------------------------------------------------
/nocache/search.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // Search searches for all occurrences of the given query string in the FullText object's data.
9 | // The search is done by splitting the query into separate words and looking for each of them in the data.
10 | // The search result is limited to the specified number of entries.
11 | // Parameters:
12 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
13 | //
14 | // Returns:
15 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
16 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
17 | // - error: An error if the query or limit is invalid.
18 | func (ft *FullText) Search(sp SearchParams) ([]map[string]any, error) {
19 | switch {
20 | case len(sp.Query) == 0:
21 | return []map[string]any{}, errors.New("invalid query")
22 | case sp.Limit < 1:
23 | return []map[string]any{}, errors.New("invalid limit")
24 | }
25 |
26 | // Convert the query to lowercase
27 | sp.Query = strings.ToLower(sp.Query)
28 |
29 | // Lock the mutex
30 | ft.mutex.RLock()
31 | defer ft.mutex.RUnlock()
32 |
33 | // Perform the search
34 | return ft.search(sp), nil
35 | }
36 |
37 | // search searches for all occurrences of the given query string in the FullText object's data.
38 | // The search is done by splitting the query into separate words and looking for each of them in the data.
39 | // The search result is limited to the specified number of entries.
40 | // Parameters:
41 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
42 | //
43 | // Returns:
44 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
45 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
46 | // - error: An error if the query or limit is invalid.
47 | func (ft *FullText) search(sp SearchParams) []map[string]any {
48 | // Split the query into separate words
49 | var words []string = strings.Split(strings.TrimSpace(sp.Query), " ")
50 | switch {
51 | // If the words array is empty
52 | case len(words) == 0:
53 | return []map[string]any{}
54 | // Get the search result of the first word
55 | case len(words) == 1:
56 | sp.Query = words[0]
57 | return ft.searchOneWord(sp)
58 | }
59 |
60 | // Check if the query is in the cache
61 | if _, ok := ft.storage[words[0]]; !ok {
62 | return []map[string]any{}
63 | }
64 |
65 | // Define variables
66 | var result []map[string]any = []map[string]any{}
67 |
68 | // Variables for storing the smallest words array
69 | var (
70 | smallest int = 0 //nolint:ineffassign
71 | smallestIndex int = 0
72 | )
73 |
74 | // Check if the query is in the cache
75 | if v, ok := ft.storage[words[0]]; !ok {
76 | return []map[string]any{}
77 | } else {
78 | if index, ok := v.(int); ok {
79 | return []map[string]any{
80 | ft.data[index],
81 | }
82 | }
83 | smallest = len(v.([]int))
84 | }
85 |
86 | // Find the smallest words array
87 | // Don't include the first or last words from the query
88 | for i := 1; i < len(words)-1; i++ {
89 | if v, ok := ft.storage[words[i]]; ok {
90 | if v, ok := v.(int); ok {
91 | return []map[string]any{
92 | ft.data[v],
93 | }
94 | }
95 | if l := len(v.([]int)); l < smallest {
96 | smallest = l
97 | smallestIndex = i
98 | }
99 | }
100 | }
101 |
102 | // Loop through the indices
103 | var indices []int = ft.storage[words[smallestIndex]].([]int)
104 | for i := 0; i < len(indices); i++ {
105 | for _, value := range ft.data[indices[i]] {
106 | if v, ok := value.(string); !ok {
107 | continue
108 | } else if strings.Contains(strings.ToLower(v), sp.Query) {
109 | result = append(result, ft.data[indices[i]])
110 | }
111 | }
112 | }
113 |
114 | // Return the result
115 | return result
116 | }
117 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/fulltext.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | utils "github.com/realTristan/hermes/cloud/socket/utils"
8 | )
9 |
10 | // FTIsInitialized is a handler function that returns a fiber context handler function for checking if the full-text storage is initialized.
11 | // Parameters:
12 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - []byte: A JSON-encoded byte slice containing a boolean value indicating whether the full-text storage is initialized.
17 | func FTIsInitialized(_ *utils.Params, c *hermes.Cache) []byte {
18 | return utils.Success(c.FTIsInitialized())
19 | }
20 |
21 | // FTSetMaxBytes is a handler function that returns a fiber context handler function for setting the maximum number of bytes for the full-text storage.
22 | // Parameters:
23 | // - p (*utils.Params): A pointer to a utils.Params struct.
24 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
25 | //
26 | // Returns:
27 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the value is invalid or the setting fails.
28 | func FTSetMaxBytes(p *utils.Params, c *hermes.Cache) []byte {
29 | // Get the value from the query
30 | var value int
31 | if err := utils.GetMaxBytesParam(p, &value); err != nil {
32 | return utils.Error(err)
33 | }
34 |
35 | // Set the max bytes
36 | if err := c.FTSetMaxBytes(value); err != nil {
37 | return utils.Error(err)
38 | }
39 | return utils.Success("null")
40 | }
41 |
42 | // FTSetMaxSize is a handler function that returns a fiber context handler function for setting the maximum length for the full-text storage.
43 | // Parameters:
44 | // - p (*utils.Params): A pointer to a utils.Params struct.
45 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
46 | //
47 | // Returns:
48 | // - []byte: A JSON-encoded byte slice containing a success message or an error message if the value is invalid or the setting fails.
49 | func FTSetMaxSize(p *utils.Params, c *hermes.Cache) []byte {
50 | // Get the value from the query
51 | var value int
52 | if err := utils.GetMaxSizeParam(p, &value); err != nil {
53 | return utils.Error(err)
54 | }
55 |
56 | // Set the max length
57 | if err := c.FTSetMaxSize(value); err != nil {
58 | return utils.Error(err)
59 | }
60 | return utils.Success("null")
61 | }
62 |
63 | // FTStorage is a handler function that returns a fiber context handler function for retrieving the full-text storage.
64 | // Parameters:
65 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
66 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
67 | //
68 | // Returns:
69 | // - []byte: A JSON-encoded byte slice containing the full-text storage or an error message if the retrieval fails.
70 | func FTStorage(_ *utils.Params, c *hermes.Cache) []byte {
71 | if data, err := c.FTStorage(); err != nil {
72 | return utils.Error(err)
73 | } else if data, err := json.Marshal(data); err != nil {
74 | return utils.Error(err)
75 | } else {
76 | return data
77 | }
78 | }
79 |
80 | // FTStorageLength is a handler function that returns a fiber context handler function for retrieving the length of the full-text storage.
81 | // Parameters:
82 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
83 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
84 | //
85 | // Returns:
86 | // - []byte: A JSON-encoded byte slice containing the length of the full-text storage or an error message if the retrieval fails.
87 | func FTStorageLength(_ *utils.Params, c *hermes.Cache) []byte {
88 | if length, err := c.FTStorageLength(); err != nil {
89 | return utils.Error(err)
90 | } else {
91 | return utils.Success(length)
92 | }
93 | }
94 |
95 | // FTStorageSize is a handler function that returns a fiber context handler function for retrieving the size of the full-text storage.
96 | // Parameters:
97 | // - _ (*utils.Params): A pointer to a utils.Params struct (unused).
98 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
99 | //
100 | // Returns:
101 | // - []byte: A JSON-encoded byte slice containing the size of the full-text storage or an error message if the retrieval fails.
102 | func FTStorageSize(_ *utils.Params, c *hermes.Cache) []byte {
103 | if size, err := c.FTStorageSize(); err != nil {
104 | return utils.Error(err)
105 | } else {
106 | return utils.Success(size)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/search.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | // Search is a method of the Cache struct that searches for a query by splitting the query into separate words and returning the search results.
9 | // Parameters:
10 | // - c (c *Cache): A pointer to the Cache struct
11 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
12 | //
13 | // Returns:
14 | // - []map[string]any: A slice of maps containing the search results.
15 | // - error: An error if the query is invalid or if the smallest words array is not found in the cache.
16 | func (c *Cache) Search(sp SearchParams) ([]map[string]any, error) {
17 | // If the query is empty, return an error
18 | if len(sp.Query) == 0 {
19 | return []map[string]any{}, errors.New("invalid query")
20 | }
21 |
22 | // If no limit is provided, set it to 10
23 | if sp.Limit == 0 {
24 | sp.Limit = 10
25 | }
26 |
27 | // Lock the mutex
28 | c.mutex.RLock()
29 | defer c.mutex.RUnlock()
30 |
31 | // Check if the FT index is initialized
32 | if c.ft == nil {
33 | return []map[string]any{}, errors.New("full-text not initialized")
34 | }
35 |
36 | // Set the query to lowercase
37 | sp.Query = strings.ToLower(sp.Query)
38 |
39 | // Search for the query
40 | return c.search(sp), nil
41 | }
42 |
43 | // search is a method of the Cache struct that searches for a query by splitting the query into separate words and returning the search results.
44 | // Parameters:
45 | // - c (c *Cache): A pointer to the Cache struct
46 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
47 | //
48 | // Returns:
49 | // - []map[string]any: A slice of maps containing the search results.
50 | func (c *Cache) search(sp SearchParams) []map[string]any {
51 | // Split the query into separate words
52 | var words []string = strings.Split(strings.TrimSpace(sp.Query), " ")
53 | switch {
54 | // If the words array is empty
55 | case len(words) == 0:
56 | return []map[string]any{}
57 | // Get the search result of the first word
58 | case len(words) == 1:
59 | sp.Query = words[0]
60 | return c.searchOneWord(sp)
61 | }
62 |
63 | // Define variables
64 | var result []map[string]any = []map[string]any{}
65 |
66 | // Variables for storing the smallest words array
67 | // var smallestData []int = []int{}
68 | var (
69 | smallestIndex int = 0
70 | smallest int = 0
71 | )
72 |
73 | // Check if the query is in the cache
74 | if indices, ok := c.ft.storage[words[0]]; !ok {
75 | return []map[string]any{}
76 | } else {
77 | /*for {
78 | if v, ok := indices.(string); ok {
79 | indices = c.ft.storage[v]
80 | } else {
81 | break
82 | }
83 | }*/
84 | if temp, ok := indices.(int); ok {
85 | return []map[string]any{
86 | c.data[c.ft.indices[temp]],
87 | }
88 | }
89 | // smallestData = indices.([]int)
90 | smallest = len(indices.([]int))
91 | }
92 |
93 | // Find the smallest words array
94 | // Don't include the first or last words from the query
95 | for i := 1; i < len(words)-1; i++ {
96 | if indices, ok := c.ft.storage[words[i]]; ok {
97 | /*for {
98 | if v, ok := indices.(string); ok {
99 | indices = c.ft.storage[v]
100 | } else {
101 | break
102 | }
103 | }*/
104 | if index, ok := indices.(int); ok {
105 | return []map[string]any{
106 | c.data[c.ft.indices[index]],
107 | }
108 | }
109 | /*if l := len(indices.([]int)); l < len(smallestData) {
110 | smallestData = indices.([]int)
111 | }*/
112 | if l := len(indices.([]int)); l < smallest {
113 | smallest = l
114 | smallestIndex = i
115 | }
116 | }
117 | }
118 |
119 | // Loop through the indices
120 | /*for i := 0; i < len(smallestData); i++ {
121 | for _, value := range c.data[c.ft.indices[smallestData[i]]] {
122 | // Check if the value contains the query
123 | if v, ok := value.(string); ok {
124 | if strings.Contains(strings.ToLower(v), sp.Query) {
125 | result = append(result, c.data[c.ft.indices[smallestData[i]]])
126 | }
127 | }
128 | }
129 | }*/
130 | var keys []int = c.ft.storage[words[smallestIndex]].([]int)
131 | for i := 0; i < len(keys); i++ {
132 | for _, value := range c.data[c.ft.indices[keys[i]]] {
133 | // Check if the value contains the query
134 | if v, ok := value.(string); ok {
135 | if strings.Contains(strings.ToLower(v), sp.Query) {
136 | result = append(result, c.data[c.ft.indices[keys[i]]])
137 | }
138 | }
139 | }
140 | }
141 |
142 | // Return the result
143 | return result
144 | }
145 |
--------------------------------------------------------------------------------
/searchoneword.go:
--------------------------------------------------------------------------------
1 | package hermes
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | utils "github.com/realTristan/hermes/utils"
8 | )
9 |
10 | // SearchOneWord searches for a single word in the FullText struct's data and returns a list of maps containing the search results.
11 | // Parameters:
12 | // - c (c *Cache): A pointer to the Cache struct
13 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
14 | //
15 | // Returns:
16 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
17 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
18 | // - error: An error if the query or limit is invalid or if the full-text is not initialized.
19 | func (c Cache) SearchOneWord(sp SearchParams) ([]map[string]any, error) {
20 | // If the query is empty, return an error
21 | if len(sp.Query) == 0 {
22 | return []map[string]any{}, errors.New("invalid query")
23 | }
24 |
25 | // If no limit is provided, set it to 10
26 | if sp.Limit == 0 {
27 | sp.Limit = 10
28 | }
29 |
30 | // Lock the mutex
31 | c.mutex.RLock()
32 | defer c.mutex.RUnlock()
33 |
34 | // Check if the full-text is initialized
35 | if c.ft == nil {
36 | return []map[string]any{}, errors.New("full-text is not initialized")
37 | }
38 |
39 | // Search the data
40 | return c.searchOneWord(sp), nil
41 | }
42 |
43 | // searchOneWord searches for a single word in the FullText struct's data and returns a list of maps containing the search results.
44 | // Parameters:
45 | // - c (c *Cache): A pointer to the Cache struct
46 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
47 | //
48 | // Returns:
49 | // - []map[string]any: A slice of maps where each map represents a data record that matches the given query.
50 | // The keys of the map correspond to the column names of the data that were searched and returned in the result.
51 | func (c *Cache) searchOneWord(sp SearchParams) []map[string]any {
52 | // Set the query to lowercase
53 | sp.Query = strings.ToLower(sp.Query)
54 |
55 | // Define variables
56 | var result []map[string]any = []map[string]any{}
57 |
58 | // If the user wants a strict search, just return the result
59 | // straight from the cache
60 | if sp.Strict {
61 | return c.searchOneWordStrict(result, sp)
62 | }
63 |
64 | // Define a map to store the indices that have already been added
65 | var alreadyAdded map[int]int = map[int]int{}
66 |
67 | // Loop through the cache keys
68 | for k, v := range c.ft.storage {
69 | switch {
70 | case len(result) >= sp.Limit:
71 | return result
72 | case !utils.Contains(k, sp.Query):
73 | continue
74 | }
75 |
76 | // Loop through the cache indices
77 | if index, ok := v.(int); ok {
78 | if _, ok := alreadyAdded[index]; ok {
79 | continue
80 | }
81 | result = append(result, c.data[c.ft.indices[index]])
82 | alreadyAdded[index] = 0
83 | continue
84 | }
85 |
86 | var indices []int = v.([]int)
87 | for j := 0; j < len(indices); j++ {
88 | if _, ok := alreadyAdded[indices[j]]; ok {
89 | continue
90 | }
91 |
92 | // Else, append the index to the result
93 | result = append(result, c.data[c.ft.indices[indices[j]]])
94 | alreadyAdded[indices[j]] = 0
95 | }
96 | }
97 |
98 | // Return the result
99 | return result
100 | }
101 |
102 | // searchOneWordStrict is a method of the Cache struct that searches for a single word in the cache and returns the results.
103 | // This function is not thread-safe.
104 | //
105 | // Parameters:
106 | // - c (c *Cache): A pointer to the Cache struct
107 | // - sp (SearchParams): A SearchParams struct containing the search parameters.
108 | //
109 | // Returns:
110 | // - A slice of map[string]any representing the search results.
111 | func (c *Cache) searchOneWordStrict(result []map[string]any, sp SearchParams) []map[string]any {
112 | // Check if the query is in the cache
113 | if _, ok := c.ft.storage[sp.Query]; !ok {
114 | return result
115 | }
116 |
117 | // If there's only one result
118 | if v, ok := c.ft.storage[sp.Query].(int); ok {
119 | return []map[string]any{c.data[c.ft.indices[v]]}
120 | }
121 |
122 | // Loop through the indices
123 | for i := 0; i < len(c.ft.storage[sp.Query].([]int)); i++ {
124 | if len(result) >= sp.Limit {
125 | return result
126 | }
127 | var (
128 | index int = c.ft.storage[sp.Query].([]int)[i]
129 | key string = c.ft.indices[index]
130 | )
131 | result = append(result, c.data[key])
132 | }
133 |
134 | // Return the result
135 | return result
136 | }
137 |
--------------------------------------------------------------------------------
/nocache/searchoneword.go:
--------------------------------------------------------------------------------
1 | package nocache
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | utils "github.com/realTristan/hermes/utils"
8 | )
9 |
10 | // SearchOneWord searches for a single query within the data using a full-text search approach.
11 | // The search result is limited to the specified number of entries, and can optionally be filtered to only include exact matches.
12 | // Parameters:
13 | // - query (string): The search query to use. This string will be searched for as a single word in any value associated with any key in each entry of the data.
14 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored.
15 | // - strict (bool): If true, only exact matches will be returned. If false, partial matches will also be returned.
16 | //
17 | // Returns:
18 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs
19 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned.
20 | // - error: An error object. If no error occurs, this will be nil.
21 | //
22 | // Note: The search is case-insensitive.
23 | func (ft *FullText) SearchOneWord(sp SearchParams) ([]map[string]any, error) {
24 | switch {
25 | case len(sp.Query) == 0:
26 | return []map[string]any{}, errors.New("invalid query")
27 | case sp.Limit < 1:
28 | return []map[string]any{}, errors.New("invalid limit")
29 | }
30 |
31 | // Set the query to lowercase
32 | sp.Query = strings.ToLower(sp.Query)
33 |
34 | // Lock the mutex
35 | ft.mutex.RLock()
36 | defer ft.mutex.RUnlock()
37 |
38 | // Search the data
39 | return ft.searchOneWord(sp), nil
40 | }
41 |
42 | // searchOneWord searches for a single query within the data using a full-text search approach.
43 | // The search result is limited to the specified number of entries, and can optionally be filtered to only include exact matches.
44 | // Parameters:
45 | // - query (string): The search query to use. This string will be searched for as a single word in any value associated with any key in each entry of the data.
46 | // - limit (int): The maximum number of search results to return. If the number of matching results exceeds this limit, the excess results will be ignored.
47 | // - strict (bool): If true, only exact matches will be returned. If false, partial matches will also be returned.
48 | //
49 | // Returns:
50 | // - []map[string]any: An array of maps representing the search results. Each map contains key-value pairs
51 | // from the entry in the data that matched the search query. If no results are found, an empty array is returned.
52 | //
53 | // Note: The search is case-insensitive.
54 | func (ft *FullText) searchOneWord(sp SearchParams) []map[string]any {
55 | // Define the result variable
56 | var result []map[string]any = []map[string]any{}
57 |
58 | // If the user wants a strict search, just return the result
59 | // straight from the cache
60 | if sp.Strict {
61 | return ft.searchOneWordStrict(result, sp)
62 | }
63 |
64 | // true for already checked
65 | var alreadyAdded map[int]int = map[int]int{}
66 |
67 | // Loop through the cache keys
68 | for i := 0; i < len(ft.words); i++ {
69 | switch {
70 | case len(result) >= sp.Limit:
71 | return result
72 | case !utils.Contains(ft.words[i], sp.Query):
73 | continue
74 | }
75 |
76 | // Loop through the cache indices
77 | if v, ok := ft.storage[ft.words[i]].(int); ok {
78 | if _, ok := alreadyAdded[v]; ok {
79 | continue
80 | }
81 | result = append(result, ft.data[v])
82 | alreadyAdded[v] = 0
83 | continue
84 | }
85 |
86 | var indices []int = ft.storage[ft.words[i]].([]int)
87 | for j := 0; j < len(indices); j++ {
88 | if _, ok := alreadyAdded[indices[j]]; ok {
89 | continue
90 | }
91 | result = append(result, ft.data[indices[j]])
92 | alreadyAdded[indices[j]] = 0
93 | }
94 | }
95 |
96 | // Return the result
97 | return result
98 | }
99 |
100 | // searchOneWordStrict is a method of the FullText struct that searches for a single word in the full-text cache and returns the results.
101 | //
102 | // Parameters:
103 | // - result: A slice of map[string]any representing the current search results.
104 | // - query: A string representing the word to search for.
105 | // - limit: An integer representing the maximum number of results to return.
106 | //
107 | // Returns:
108 | // - A slice of map[string]any representing the search results.
109 | func (ft *FullText) searchOneWordStrict(result []map[string]any, sp SearchParams) []map[string]any {
110 | // Check if the query is in the cache
111 | if _, ok := ft.storage[sp.Query]; !ok {
112 | return result
113 | }
114 |
115 | // Check if the cache value is an integer
116 | if v, ok := ft.storage[sp.Query].(int); ok {
117 | return []map[string]any{ft.data[v]}
118 | }
119 |
120 | // Loop through the indices
121 | var indices []int = ft.storage[sp.Query].([]int)
122 | for i := 0; i < len(indices); i++ {
123 | if len(result) >= sp.Limit {
124 | return result
125 | }
126 | result = append(result, ft.data[indices[i]])
127 | }
128 |
129 | // Return the result
130 | return result
131 | }
132 |
--------------------------------------------------------------------------------
/cloud/socket/handlers/search.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | hermes "github.com/realTristan/hermes"
7 | utils "github.com/realTristan/hermes/cloud/socket/utils"
8 | )
9 |
10 | // Search is a handler function that returns a fiber context handler function for searching the cache for a query.
11 | // Parameters:
12 | // - p (*utils.Params): A pointer to a utils.Params struct.
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails.
17 | func Search(p *utils.Params, c *hermes.Cache) []byte {
18 | var (
19 | strict bool
20 | query string
21 | limit int
22 | err error
23 | )
24 |
25 | // Get the query from the params
26 | if query, err = utils.GetQueryParam(p); err != nil {
27 | return utils.Error("query not provided")
28 | }
29 |
30 | // Get the limit from the params
31 | if err := utils.GetLimitParam(p, &limit); err != nil {
32 | return utils.Error(err)
33 | }
34 |
35 | // Get the strict from the params
36 | if err := utils.GetStrictParam(p, &strict); err != nil {
37 | return utils.Error(err)
38 | }
39 |
40 | // Search for the query
41 | if res, err := c.Search(hermes.SearchParams{
42 | Query: query,
43 | Limit: limit,
44 | Strict: strict,
45 | }); err != nil {
46 | return utils.Error(err)
47 | } else if data, err := json.Marshal(res); err != nil {
48 | return utils.Error(err)
49 | } else {
50 | return data
51 | }
52 | }
53 |
54 | // SearchOneWord is a handler function that returns a fiber context handler function for searching the cache for a single word query.
55 | // Parameters:
56 | // - p (*utils.Params): A pointer to a utils.Params struct.
57 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
58 | //
59 | // Returns:
60 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails.
61 | func SearchOneWord(p *utils.Params, c *hermes.Cache) []byte {
62 | var (
63 | strict bool
64 | query string
65 | err error
66 | limit int
67 | )
68 |
69 | // Get the query from the params
70 | if query, err = utils.GetQueryParam(p); err != nil {
71 | return utils.Error("invalid query")
72 | }
73 |
74 | // Get the limit from the params
75 | if err := utils.GetLimitParam(p, &limit); err != nil {
76 | return utils.Error(err)
77 | }
78 |
79 | // Get the strict from the params
80 | if err := utils.GetStrictParam(p, &strict); err != nil {
81 | return utils.Error(err)
82 | }
83 |
84 | // Search for the query
85 | if res, err := c.SearchOneWord(hermes.SearchParams{
86 | Query: query,
87 | Limit: limit,
88 | Strict: strict,
89 | }); err != nil {
90 | return utils.Error(err)
91 | } else {
92 | if data, err := json.Marshal(res); err != nil {
93 | return utils.Error(err)
94 | } else {
95 | return data
96 | }
97 | }
98 | }
99 |
100 | // SearchValues is a handler function that returns a fiber context handler function for searching the cache for a query in values.
101 | // Parameters:
102 | // - p (*utils.Params): A pointer to a utils.Params struct.
103 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
104 | //
105 | // Returns:
106 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails.
107 | func SearchValues(p *utils.Params, c *hermes.Cache) []byte {
108 | var (
109 | query string
110 | limit int
111 | err error
112 | schema map[string]bool
113 | )
114 |
115 | // Get the query from the params
116 | if query, err = utils.GetQueryParam(p); err != nil {
117 | return utils.Error("invalid query")
118 | }
119 |
120 | // Get the limit from the params
121 | if err := utils.GetLimitParam(p, &limit); err != nil {
122 | return utils.Error(err)
123 | }
124 |
125 | // Get the schema from the params
126 | if err := utils.GetSchemaParam(p, &schema); err != nil {
127 | return utils.Error(err)
128 | }
129 |
130 | // Search for the query
131 | if res, err := c.SearchValues(hermes.SearchParams{
132 | Query: query,
133 | Limit: limit,
134 | Schema: schema,
135 | }); err != nil {
136 | return utils.Error(err)
137 | } else {
138 | if data, err := json.Marshal(res); err != nil {
139 | return utils.Error(err)
140 | } else {
141 | return data
142 | }
143 | }
144 | }
145 |
146 | // SearchWithKey is a handler function that returns a fiber context handler function for searching the cache for a query with a specific key.
147 | // Parameters:
148 | // - p (*utils.Params): A pointer to a utils.Params struct.
149 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
150 | //
151 | // Returns:
152 | // - []byte: A JSON-encoded byte slice containing the search results or an error message if the search fails.
153 | func SearchWithKey(p *utils.Params, c *hermes.Cache) []byte {
154 | var (
155 | key string
156 | query string
157 | err error
158 | limit int
159 | schema map[string]bool
160 | )
161 |
162 | // Get the query from the params
163 | if query, err = utils.GetQueryParam(p); err != nil {
164 | return utils.Error("invalid query")
165 | }
166 |
167 | // Get the key from the params
168 | if key, err = utils.GetKeyParam(p); err != nil {
169 | return utils.Error("invalid key")
170 | }
171 |
172 | // Get the limit from the params
173 | if err := utils.GetLimitParam(p, &limit); err != nil {
174 | return utils.Error(err)
175 | }
176 |
177 | // Get the schema from the params
178 | if err := utils.GetSchemaParam(p, &schema); err != nil {
179 | return utils.Error(err)
180 | }
181 |
182 | // Search for the query
183 | if res, err := c.SearchWithKey(hermes.SearchParams{
184 | Query: query,
185 | Key: key,
186 | Limit: limit,
187 | }); err != nil {
188 | return utils.Error(err)
189 | } else {
190 | if data, err := json.Marshal(res); err != nil {
191 | return utils.Error(err)
192 | } else {
193 | return data
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/cloud/api/utils/params.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strconv"
7 |
8 | "github.com/gofiber/fiber/v2"
9 | )
10 |
11 | // GetValueParam is a function that retrieves a value from a query parameter in a Fiber context and decodes it into a value of type T.
12 | // Parameters:
13 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
14 | // - value (*T): A pointer to a value of type T to store the decoded value.
15 | //
16 | // Returns:
17 | // - error: An error message if the decoding fails or the query parameter is invalid, or nil if the decoding is successful.
18 | func GetValueParam[T any](ctx *fiber.Ctx, value *T) error {
19 | if v := ctx.Query("value"); len(v) == 0 {
20 | return errors.New("invalid value")
21 | } else if err := Decode(v, &value); err != nil {
22 | return err
23 | }
24 | return nil
25 | }
26 |
27 | // GetMaxSizeParam is a function that retrieves the "maxsize" query parameter from a Fiber context and stores it in an integer pointer.
28 | // Parameters:
29 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
30 | // - maxSize (*int): A pointer to an integer to store the "maxsize" query parameter.
31 | //
32 | // Returns:
33 | // - error: An error message if the "maxsize" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful.
34 | func GetMaxSizeParam(ctx *fiber.Ctx, maxSize *int) error {
35 | if s := ctx.Query("maxsize"); len(s) == 0 {
36 | fmt.Println(s)
37 | return errors.New("invalid maxsize")
38 | } else if i, err := strconv.Atoi(s); err != nil {
39 | return err
40 | } else {
41 | *maxSize = i
42 | }
43 | return nil
44 | }
45 |
46 | // GetMaxBytesParam is a function that retrieves the "maxbytes" query parameter from a Fiber context and stores it in an integer pointer.
47 | // Parameters:
48 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
49 | // - maxBytes (*int): A pointer to an integer to store the "maxbytes" query parameter.
50 | //
51 | // Returns:
52 | // - error: An error message if the "maxbytes" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful.
53 | func GetMaxBytesParam(ctx *fiber.Ctx, maxBytes *int) error {
54 | if s := ctx.Query("maxbytes"); len(s) == 0 {
55 | return errors.New("invalid maxbytes")
56 | } else if i, err := strconv.Atoi(s); err != nil {
57 | return err
58 | } else {
59 | *maxBytes = i
60 | }
61 | return nil
62 | }
63 |
64 | // Get the min word length url parameter
65 | func GetMinWordLengthParam(ctx *fiber.Ctx, minWordLength *int) error {
66 | if s := ctx.Query("minwordlength"); len(s) == 0 {
67 | return errors.New("invalid minwordlength")
68 | } else if i, err := strconv.Atoi(s); err != nil {
69 | return err
70 | } else {
71 | *minWordLength = i
72 | }
73 | return nil
74 | }
75 |
76 | // GetJSONParam is a function that retrieves a JSON-encoded value from a query parameter in a Fiber context and decodes it into a value of type T.
77 | // Parameters:
78 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
79 | // - json (*T): A pointer to a value of type T to store the decoded JSON.
80 | //
81 | // Returns:
82 | // - error: An error message if the decoding fails or the query parameter is invalid, or nil if the decoding is successful.
83 | func GetJSONParam[T any](ctx *fiber.Ctx, json *T) error {
84 | if s := ctx.Query("json"); len(s) == 0 {
85 | return errors.New("invalid json")
86 | } else if err := Decode(s, &json); err != nil {
87 | return err
88 | }
89 | return nil
90 | }
91 |
92 | // GetSchemaParam is a function that retrieves a schema from a query parameter in a Fiber context and decodes it into a map of string keys and boolean values.
93 | // Parameters:
94 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
95 | // - schema (*map[string]bool): A pointer to a map of string keys and boolean values to store the decoded schema.
96 | //
97 | // Returns:
98 | // - error: An error message if the decoding fails or the query parameter is invalid, or nil if the decoding is successful.
99 | func GetSchemaParam(ctx *fiber.Ctx, schema *map[string]bool) error {
100 | // Get the schema from the url params
101 | if s := ctx.Query("schema"); len(s) == 0 {
102 | return errors.New("invalid schema")
103 | } else if err := Decode(s, schema); err != nil {
104 | return err
105 | }
106 | return nil
107 | }
108 |
109 | // GetLimitParam is a function that retrieves the "limit" query parameter from a Fiber context and stores it in an integer pointer.
110 | // Parameters:
111 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
112 | // - limit (*int): A pointer to an integer to store the "limit" query parameter.
113 | //
114 | // Returns:
115 | // - error: An error message if the "limit" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful.
116 | func GetLimitParam(ctx *fiber.Ctx, limit *int) error {
117 | // Get the limit from the url params
118 | if s := ctx.Query("limit"); len(s) == 0 {
119 | return errors.New("invalid limit")
120 | } else if i, err := strconv.Atoi(s); err != nil {
121 | return err
122 | } else {
123 | *limit = i
124 | }
125 | return nil
126 | }
127 |
128 | // GetLimitParam is a function that retrieves the "limit" query parameter from a Fiber context and stores it in an integer pointer.
129 | // Parameters:
130 | // - ctx (*fiber.Ctx): A pointer to a Fiber context.
131 | // - limit (*int): A pointer to an integer to store the "limit" query parameter.
132 | //
133 | // Returns:
134 | // - error: An error message if the "limit" query parameter is invalid or cannot be converted to an integer, or nil if the retrieval is successful.
135 | func GetStrictParam(ctx *fiber.Ctx, strict *bool) error {
136 | // Get whether strict mode is enabled/disabled
137 | if s := ctx.Query("strict"); len(s) == 0 {
138 | return errors.New("invalid strict")
139 | } else if b, err := strconv.ParseBool(s); err != nil {
140 | return err
141 | } else {
142 | *strict = b
143 | }
144 | return nil
145 | }
146 |
--------------------------------------------------------------------------------
/cloud/api/handlers/fulltext.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | hermes "github.com/realTristan/hermes"
8 | utils "github.com/realTristan/hermes/cloud/api/utils"
9 | )
10 |
11 | // FTIsInitialized is a handler function that returns a fiber context handler function for checking if the full-text search is initialized.
12 | // Parameters:
13 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
14 | //
15 | // Returns:
16 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that checks if the full-text search is initialized and returns a success message with a boolean value indicating whether it is initialized.
17 | func FTIsInitialized(c *hermes.Cache) func(ctx *fiber.Ctx) error {
18 | return func(ctx *fiber.Ctx) error {
19 | return ctx.Send(utils.Success(c.FTIsInitialized()))
20 | }
21 | }
22 |
23 | // FTSetMaxBytes is a handler function that returns a fiber context handler function for setting the maximum number of bytes for full-text search.
24 | // Parameters:
25 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
26 | //
27 | // Returns:
28 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets the maximum number of bytes for full-text search and returns a success message or an error message if the value is not provided or if the setting fails.
29 | func FTSetMaxBytes(c *hermes.Cache) func(ctx *fiber.Ctx) error {
30 | return func(ctx *fiber.Ctx) error {
31 | // Get the value from the query
32 | var value int
33 | if err := utils.GetMaxBytesParam(ctx, &value); err != nil {
34 | return ctx.Send(utils.Error(err))
35 | }
36 |
37 | // Set the max bytes
38 | if err := c.FTSetMaxBytes(value); err != nil {
39 | return ctx.Send(utils.Error(err))
40 | }
41 | return ctx.Send(utils.Success("null"))
42 | }
43 | }
44 |
45 | // FTSetMaxSize is a handler function that returns a fiber context handler function for setting the maximum length for full-text search.
46 | // Parameters:
47 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
48 | //
49 | // Returns:
50 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets the maximum length for full-text search and returns a success message or an error message if the value is not provided or if the setting fails.
51 | func FTSetMaxSize(c *hermes.Cache) func(ctx *fiber.Ctx) error {
52 | return func(ctx *fiber.Ctx) error {
53 | // Get the value from the query
54 | var value int
55 | if err := utils.GetMaxSizeParam(ctx, &value); err != nil {
56 | return ctx.Send(utils.Error(err))
57 | }
58 |
59 | // Set the max length
60 | if err := c.FTSetMaxSize(value); err != nil {
61 | return ctx.Send(utils.Error(err))
62 | }
63 | return ctx.Send(utils.Success("null"))
64 | }
65 | }
66 |
67 | // FTStorage is a handler function that returns a fiber context handler function for getting the full-text storage.
68 | // Parameters:
69 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
70 | //
71 | // Returns:
72 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the full-text storage and returns a JSON-encoded string of the data or an error message if the retrieval or encoding fails.
73 | func FTStorage(c *hermes.Cache) func(ctx *fiber.Ctx) error {
74 | return func(ctx *fiber.Ctx) error {
75 | if data, err := c.FTStorage(); err != nil {
76 | return ctx.Send(utils.Error(err))
77 | } else if data, err := json.Marshal(data); err != nil {
78 | return ctx.Send(utils.Error(err))
79 | } else {
80 | return ctx.Send(data)
81 | }
82 | }
83 | }
84 |
85 | // FTStorageLength is a handler function that returns a fiber context handler function for getting the length of the full-text storage.
86 | // Parameters:
87 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
88 | //
89 | // Returns:
90 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the length of the full-text storage and returns a success message with the length or an error message if the retrieval fails.
91 | func FTStorageLength(c *hermes.Cache) func(ctx *fiber.Ctx) error {
92 | return func(ctx *fiber.Ctx) error {
93 | if length, err := c.FTStorageLength(); err != nil {
94 | return ctx.Send(utils.Error(err))
95 | } else {
96 | return ctx.Send(utils.Success(length))
97 | }
98 | }
99 | }
100 |
101 | // FTStorageSize is a handler function that returns a fiber context handler function for getting the size of the full-text storage.
102 | // Parameters:
103 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
104 | //
105 | // Returns:
106 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that gets the size of the full-text storage and returns a success message with the size or an error message if the retrieval fails.
107 | func FTStorageSize(c *hermes.Cache) func(ctx *fiber.Ctx) error {
108 | return func(ctx *fiber.Ctx) error {
109 | if size, err := c.FTStorageSize(); err != nil {
110 | return ctx.Send(utils.Error(err))
111 | } else {
112 | return ctx.Send(utils.Success(size))
113 | }
114 | }
115 | }
116 |
117 | // FTSetMinWordLength is a handler function that returns a fiber context handler function for setting the minimum word length for full-text search.
118 | // Parameters:
119 | // - c (*hermes.Cache): A pointer to a hermes.Cache struct.
120 | //
121 | // Returns:
122 | // - func(ctx *fiber.Ctx) error: A fiber context handler function that sets the minimum word length for full-text search and returns a success message or an error message if the value is not provided or if the setting fails.
123 | func FTSetMinWordLength(c *hermes.Cache) func(ctx *fiber.Ctx) error {
124 | return func(ctx *fiber.Ctx) error {
125 | // Get the min word length from the query
126 | var minWordLength int
127 | if err := utils.GetMinWordLengthParam(ctx, &minWordLength); err != nil {
128 | return ctx.Send(utils.Error(err))
129 | }
130 |
131 | // Update the min word length
132 | if err := c.FTSetMinWordLength(minWordLength); err != nil {
133 | return ctx.Send(utils.Error(err))
134 | }
135 |
136 | // Return null
137 | return ctx.Send(utils.Success("null"))
138 | }
139 | }
140 |
--------------------------------------------------------------------------------