{"id":18307820,"url":"https://github.com/davidroman0o/comfylite3","last_synced_at":"2025-04-05T17:32:02.680Z","repository":{"id":231929586,"uuid":"783065304","full_name":"davidroman0O/comfylite3","owner":"davidroman0O","description":"`sqlite3` but comfy! Use `sqlite3` with goroutines without headaches!","archived":false,"fork":false,"pushed_at":"2024-12-14T05:14:38.000Z","size":526,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T20:43:31.525Z","etag":null,"topics":["go","golang","goroutines","mattn","sql","sqlite","sqlite3"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidroman0O.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-06T21:01:58.000Z","updated_at":"2025-01-03T12:14:27.000Z","dependencies_parsed_at":"2024-04-06T22:21:29.145Z","dependency_job_id":"300e0dc9-4797-4157-99e8-9d2935ec98d2","html_url":"https://github.com/davidroman0O/comfylite3","commit_stats":null,"previous_names":["davidroman0o/comfylite3"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidroman0O%2Fcomfylite3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidroman0O%2Fcomfylite3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidroman0O%2Fcomfylite3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidroman0O%2Fcomfylite3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidroman0O","download_url":"https://codeload.github.com/davidroman0O/comfylite3/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247374908,"owners_count":20928908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","goroutines","mattn","sql","sqlite","sqlite3"],"created_at":"2024-11-05T16:05:39.572Z","updated_at":"2025-04-05T17:32:02.269Z","avatar_url":"https://github.com/davidroman0O.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Comfylite3\n\nAren't you tired to be forced to write a different code for `sqlite3` because you can't use it with multiple goroutines? I was. I disliked the constraints and changing my habits.\n\nThat's why `comfylite3` exists! Just throw your queries at it and your `sql` will be executed*!\n\n*: eventually!\n\n![Gopher Comfy](./docs/gophercomfy.webp)\n\n# Install \n\n```\ngo get -u github.com/davidroman0O/comfylite3\n```\n\n# sql.DB\n\n`ComfyDB` is using all the functions of `sql.DB` so you can use as drop-in replacement! It now uses retrypool under the hood for better reliability and concurrent operation handling.\n\n# API\n\n## Memory or File or What you want!\n\n```go\n// You want a default memory database\ncomfylite3.WithMemory()\n\n// You want a default file database\ncomfylite3.WithPath(\"comfyName.db\")\n\n// Feeling adventurous? You can!\ncomfylite3.WithConnection(\"file:/tmp/adventurousComfy.db?cache=shared\")\n```\n\n## Retry Configuration\n\n```go\ncomfy, err := comfylite3.New(\n    comfylite3.WithMemory(),\n    comfylite3.WithRetryAttempts(3),        // Configure max retries\n    comfylite3.WithRetryDelay(time.Second), // Set delay between retries\n    comfylite3.WithPanicHandler(func(v interface{}, stackTrace string) {\n        // Custom panic handling\n    }),\n)\n```\n\n## Using ComfyDB as a standard sql.DB\n\nComfyLite3 now provides an `OpenDB` function that allows you to use ComfyDB as a standard `sql.DB` instance. This makes it easier to integrate ComfyLite3 with existing code or libraries that expect a `*sql.DB`.\n\n```go\n// Create a new ComfyDB instance\ncomfy, err := comfylite3.New(comfylite3.WithMemory())\nif err != nil {\n    panic(err)\n}\n\n// Open a standard sql.DB instance using ComfyDB\ndb := comfylite3.OpenDB(comfy, \n    comfylite3.WithOption(\"_fk=1\"),\n    comfylite3.WithForeignKeys(),\n)\n\n// Now you can use db as a regular *sql.DB\nrows, err := db.Query(\"SELECT * FROM users\")\n// ...\n\n// Don't forget to close both when you're done\ndefer db.Close()\ndefer comfy.Close()\n```\n\nThis feature makes ComfyLite3 more flexible and easier to use in a variety of scenarios, especially when working with existing codebases or third-party libraries.\n\n## What you can do\n\nVery simplistic API, `comfylite3` manage when to execute and you do as usual.\n\n```go\n// Create a new comfy database for `sqlite3`\ncomfyDB, _ := comfylite3.New(comfylite3.WithMemory())\n\n// Create future workload to cook\nid := comfyDB.New(func(db *sql.DB) (interface{}, error) {\n    _, err := db.Exec(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\")\n    return nil, err\n})\n\n// You will get what you send back! Error or not!\nresult := \u003c-comfyDB.WaitForChn(id)\n\nswitch result.(type) {\n    case error:\n        fmt.Println(\"Oooh your query failed!\", result)\n}\n```\n\n## Integration with Ent\n\nIt can comes handy to integrate with other third-party like [ent](https://github.com/ent/ent), a powerful entity framework for Go. Here's how you can use ComfyLite3 as the underlying database for your ent client:\n\n```go\nimport (\n    \"context\"\n    \"log\"\n\n    \"github.com/davidroman0O/comfylite3\"\n    \"entgo.io/ent\"\n    \"entgo.io/ent/dialect\"\n    \"entgo.io/ent/dialect/sql\"\n)\n\nfunc main() {\n    // Create a new ComfyDB instance\n    comfy, err := comfylite3.New(\n        comfylite3.WithPath(\"./ent.db\"),\n    )\n    if err != nil {\n        log.Fatalf(\"failed creating ComfyDB: %v\", err)\n    }\n    defer comfy.Close()\n\n    // Use the OpenDB function to create a sql.DB instance with SQLite options\n    db := comfylite3.OpenDB(\n        comfy, \n        comfylite3.WithOption(\"_fk=1\"),\n        comfylite3.WithOption(\"cache=shared\"),\n        comfylite3.WithOption(\"mode=rwc\"),\n        comfylite3.WithForeignKeys(),\n    )\n\n    // Create a new ent client\n    client := ent.NewClient(ent.Driver(sql.OpenDB(dialect.SQLite, db)))\n    defer client.Close()\n\n    ctx := context.Background()\n\n    // Run the auto migration tool\n    if err := client.Schema.Create(ctx); err != nil {\n        log.Fatalf(\"failed creating schema resources: %v\", err)\n    }\n\n    // Your ent operations go here\n    // For example:\n    // user, err := client.User.Create().SetName(\"John Doe\").Save(ctx)\n    // if err != nil {\n    //     log.Fatalf(\"failed creating user: %v\", err)\n    // }\n    // fmt.Printf(\"User created: %v\\n\", user)\n}\n```\n\nThis integration allows you to leverage the concurrency benefits of ComfyLite3 while using ent's powerful ORM features. [Check by yourself that repository](https://github.com/davidroman0O/comfylite3-ent)\n\n## Migrations\n\nMigrations is important and `sqlite` is a specific type of database, and it support migrations!\n\n```go\n// Let's imagine a set of migrations\nvar memoryMigrations []comfylite3.Migration = []comfylite3.Migration{\n    comfylite3.NewMigration(\n        1,\n        \"genesis\",\n        func(tx *sql.Tx) error {\n            if _, err := tx.Exec(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\"); err != nil {\n                return err\n            }\n            if _, err := tx.Exec(\"CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, user_id INTEGER, FOREIGN KEY(user_id) REFERENCES users(id))\"); err != nil {\n                return err\n            }\n            return nil\n        },\n        func(tx *sql.Tx) error {\n            // undo previous up function\n            if _, err := tx.Exec(\"DROP TABLE users\"); err != nil {\n                return err\n            }\n            if _, err := tx.Exec(\"DROP TABLE products\"); err != nil {\n                return err\n            }\n            return nil\n        }),\n    comfylite3.NewMigration(\n        2,\n        \"new_table\",\n        func(tx *sql.Tx) error {\n            if _, err := tx.Exec(\"ALTER TABLE products ADD COLUMN new_column TEXT\"); err != nil {\n                return err\n            }\n            if _, err := tx.Exec(\"CREATE TABLE new_table (id INTEGER PRIMARY KEY, name TEXT)\"); err != nil {\n                return err\n            }\n            return nil\n        },\n        func(tx *sql.Tx) error {\n            // undo previous up function\n            if _, err := tx.Exec(\"ALTER TABLE products DROP COLUMN new_column\"); err != nil {\n                return err\n            }\n            if _, err := tx.Exec(\"DROP TABLE new_table\"); err != nil {\n                return err\n            }\n            return nil\n        }),\n    comfylite3.NewMigration(\n        3,\n        \"new_random\",\n        func(tx *sql.Tx) error {\n            // remove new_column from products\n            if _, err := tx.Exec(\"ALTER TABLE products DROP COLUMN new_column\"); err != nil {\n                return err\n            }\n            return nil\n        },\n        func(tx *sql.Tx) error {\n            // add new_column to products\n            if _, err := tx.Exec(\"ALTER TABLE products ADD COLUMN new_column TEXT\"); err != nil {\n                return err\n            }\n            return nil\n        },\n    ),\n}\n\n// create and add your migrations\ncomfyDB, _ := comfylite3.New(\n        comfylite3.WithMemory(),\n        comfylite3.WithMigration(memoryMigrations...),\n        comfylite3.WithMigrationTableName(\"_migrations\"), // even customize your migration table!\n    )\n\n// Migrations Up and Down are easy\n\n// Up to the top!\nif err := comfyDB.Up(context.Background()); err != nil {\n    panic(err)\n}\n\n// Specify how many down you want to do\nif err := comfyDB.Down(context.Background(), 1); err != nil {\n    panic(err)\n}\n\ncomfyDB.Version()  // return all the existing versions []uint\ncomfyDB.Index()    // return the current index of the migration\ncomfyDB.ShowTables() // return all table names\ncomfyDB.ShowColumns(\"name\") // return columns data of one table\n```\n\n# Example with Metrics\n\nHere's a more complex example that computes the average amount of inserts per second:\n\n```go\nfunc main() {\n    var superComfy *comfylite3.ComfyDB\n    var err error\n    if superComfy, err = comfylite3.New(comfylite3.WithMemory()); err != nil {\n        panic(err)\n    }\n\n    ticket := superComfy.New(func(db *sql.DB) (interface{}, error) {\n        _, err := db.Exec(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\")\n        return nil, err\n    })\n    \u003c-superComfy.WaitForChn(ticket)\n\n    done := make(chan struct{})\n\n    go func() {\n        random := rand.New(rand.NewSource(time.Now().UnixNano()))\n        for range 10000 {\n            ticket := superComfy.New(func(db *sql.DB) (interface{}, error) {\n                _, err := db.Exec(\"INSERT INTO users (name) VALUES (?)\", fmt.Sprintf(\"user%d\", 1))\n                return nil, err\n            })\n            \u003c-superComfy.WaitForChn(ticket)\n            // simulate random insert with a random sleep\n            time.Sleep(time.Duration(random.Intn(5)) * time.Millisecond)\n        }\n        done \u003c- struct{}{}\n    }()\n\n    // Let's measure how many records per second\n    metrics := []int{}\n    ticker := time.NewTicker(1 * time.Second)\n    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n    defer cancel()\n\n    compute := true\n    for compute {\n        select {\n        case \u003c-done:\n            ticker.Stop()\n            cancel()\n            compute = false\n        case \u003c-ctx.Done():\n            ticker.Stop()\n            cancel()\n            compute = false\n        case \u003c-ticker.C:\n            ticket := superComfy.New(func(db *sql.DB) (interface{}, error) {\n                rows, err := db.Query(\"SELECT COUNT(*) FROM users\")\n                if err != nil {\n                    return nil, err\n                }\n                defer rows.Close()\n\n                var count int\n                if rows.Next() {\n                    err = rows.Scan(\u0026count)\n                    if err != nil {\n                        return nil, err\n                    }\n                }\n                return count, err\n            })\n            result := \u003c-superComfy.WaitForChn(ticket)\n            metrics = append(metrics, result.(int))\n        }\n    }\n\n    total := 0\n    for _, value := range metrics {\n        total += value\n    }\n    average := float64(total) / float64(len(metrics))\n    fmt.Printf(\"Average: %.2f\\n\", average)\n}\n```\n\nEnjoy freeing yourself from `database is locked`!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidroman0o%2Fcomfylite3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidroman0o%2Fcomfylite3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidroman0o%2Fcomfylite3/lists"}