Skip to content

Commit b0d6d27

Browse files
committed
Merge branch 'master' of https://github.com/kefniark/mango-sql
2 parents 3952f0d + e981b59 commit b0d6d27

46 files changed

Lines changed: 2162 additions & 555 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ devenv.local.nix
1010

1111
coverage.*
1212
*.test
13+
*.log
14+
1315
bin/
1416
codegen/test/
1517
node_modules/

Readme.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ MangoSQL is a fresh and juicy SQL code generator.
88
2. You run MangoSQL cli to generate a client with type-safe interfaces and queries
99
3. You write application code that calls the generated code
1010

11-
This is not an ORM, and you can easily inspect the code generated.
12-
This is inspired by [SQLC](https://github.com/sqlc-dev/sqlc) but pushes the idea farther by supporting batching, relations and dynamic queries.
11+
The perfect choice if you don't want an ORM, but don't want to write all your application queries by hand either.
12+
This is inspired by [SQLC](https://github.com/sqlc-dev/sqlc), but pushes the idea farther by supporting batching, relations and dynamic queries.
1313

1414
## Features
1515

16-
* **Convenient**: All the structs are generated for you, No need for manual DTO/PDO
16+
* **Convenient**: All the structs are generated for you, No need to write manually DTO/PDO
1717
* **Time Saver**: All the basic queries (CRUD) are generated from your schema alone, less queries to write
1818
* **Developer Friendly**: The code generated contains comments, examples and is designed with IDE autocompletion in mind
1919
* **Flexible**: Provide a way to run dynamic queries (pagination, search, ...)

cmd/bench/bench.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/go-echarts/go-echarts/v2/charts"
11+
"github.com/go-echarts/go-echarts/v2/opts"
12+
)
13+
14+
func parseBenchmarks() []BenchData {
15+
f, err := os.OpenFile("bench.log", os.O_RDONLY, os.ModePerm)
16+
if err != nil {
17+
panic(err)
18+
}
19+
defer f.Close()
20+
21+
entries := []BenchData{}
22+
sc := bufio.NewScanner(f)
23+
for sc.Scan() {
24+
line := sc.Text()
25+
if !strings.HasPrefix(line, "Benchmark") {
26+
continue
27+
}
28+
29+
fields := strings.Fields(line)
30+
31+
// name
32+
suite := strings.Replace(strings.Split(fields[0], "/")[0], "Benchmark", "", -1)
33+
sub := strings.Split(fields[0], "/")[1]
34+
name := sub
35+
param := "1"
36+
if strings.Contains(sub, "_") {
37+
name = strings.Split(strings.Split(fields[0], "/")[1], "_")[0]
38+
param = strings.Split(strings.Split(fields[0], "/")[1], "_")[1]
39+
40+
if strings.Contains(param, "-") {
41+
param = strings.Split(param, "-")[0]
42+
}
43+
}
44+
45+
// math
46+
paramValue, _ := strconv.Atoi(param)
47+
iteration, _ := strconv.Atoi(fields[1])
48+
speed, _ := strconv.Atoi(fields[2])
49+
data, _ := strconv.Atoi(fields[4])
50+
alloc, _ := strconv.Atoi(fields[6])
51+
52+
entries = append(entries, BenchData{
53+
Suite: suite,
54+
Name: name,
55+
Param: paramValue,
56+
Iter: iteration,
57+
Speed: speed,
58+
Data: data,
59+
Alloc: alloc,
60+
})
61+
}
62+
return entries
63+
}
64+
65+
type BenchData struct {
66+
Suite string
67+
Name string
68+
Param int
69+
Iter int
70+
Speed int
71+
Data int
72+
Alloc int
73+
}
74+
75+
func groupBySuite(data []BenchData) map[string][]BenchData {
76+
entries := map[string][]BenchData{}
77+
for _, bench := range data {
78+
entries[bench.Suite] = append(entries[bench.Suite], bench)
79+
}
80+
return entries
81+
}
82+
83+
func generateLineSpeedData(data []BenchData, filters ...string) []opts.LineData {
84+
items := make([]opts.LineData, 0)
85+
for _, v := range data {
86+
for _, f := range filters {
87+
if !strings.Contains(v.Name, f) {
88+
continue
89+
}
90+
}
91+
items = append(items, opts.LineData{Value: 1000000000 / v.Speed})
92+
}
93+
return items
94+
}
95+
96+
func generateLineAllocData(data []BenchData, filters ...string) []opts.LineData {
97+
items := make([]opts.LineData, 0)
98+
for _, v := range data {
99+
for _, f := range filters {
100+
if !strings.Contains(v.Name, f) {
101+
continue
102+
}
103+
}
104+
items = append(items, opts.LineData{Value: v.Alloc})
105+
}
106+
return items
107+
}
108+
109+
func generateInsertManyCPULines(data []BenchData, name string, filter string) *charts.Line {
110+
entries := groupBySuite(data)
111+
112+
line := charts.NewLine()
113+
line.SetGlobalOptions(
114+
charts.WithTitleOpts(opts.Title{
115+
Title: name + "|CPU",
116+
Subtitle: "Higher is Better (op/s)",
117+
}),
118+
charts.WithInitializationOpts(opts.Initialization{
119+
Theme: "shine",
120+
Width: "576px",
121+
Height: "320px",
122+
}),
123+
)
124+
line.SetXAxis([]string{"10", "25", "50", "100", "250", "500"})
125+
126+
for k, v := range entries {
127+
if !strings.Contains(k, filter) {
128+
continue
129+
}
130+
131+
line.AddSeries(strings.ReplaceAll(k, filter, ""), generateLineSpeedData(v, name))
132+
}
133+
134+
return line
135+
}
136+
137+
func generateInsertManyAllocLines(data []BenchData, name string, filter string) *charts.Line {
138+
entries := groupBySuite(data)
139+
140+
line := charts.NewLine()
141+
line.SetGlobalOptions(
142+
charts.WithTitleOpts(opts.Title{
143+
Title: name + "|Mem",
144+
Subtitle: "Lower is Better (allocs/op)",
145+
}),
146+
charts.WithInitializationOpts(opts.Initialization{
147+
Theme: "shine",
148+
Width: "576px",
149+
Height: "320px",
150+
}),
151+
)
152+
line.SetXAxis([]string{"10", "25", "50", "100", "250", "500"})
153+
154+
for k, v := range entries {
155+
if !strings.Contains(k, filter) {
156+
continue
157+
}
158+
line.AddSeries(strings.ReplaceAll(k, filter, ""), generateLineAllocData(v, name))
159+
}
160+
161+
return line
162+
}
163+
164+
func main() {
165+
data := parseBenchmarks()
166+
167+
for _, db := range []string{"Postgres", "SQLite"} {
168+
for _, op := range []string{"InsertMany", "FindMany"} {
169+
f1, _ := os.Create(fmt.Sprintf("docs/public/bench_%s_%s_cpu.html", strings.ToLower(db), strings.ToLower(op)))
170+
defer f1.Close()
171+
generateInsertManyCPULines(data, op, db).Render(f1)
172+
173+
f2, _ := os.Create(fmt.Sprintf("docs/public/bench_%s_%s_alloc.html", strings.ToLower(db), strings.ToLower(op)))
174+
defer f2.Close()
175+
generateInsertManyAllocLines(data, op, db).Render(f2)
176+
}
177+
}
178+
}

cmd/mangosql/mangosql.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path"
1111
"regexp"
12+
"slices"
1213
"strings"
1314

1415
"github.com/kefniark/mango-sql/internal"
@@ -17,40 +18,64 @@ import (
1718

1819
func main() {
1920
app := &cli.App{
21+
Version: "v0.0.1",
2022
Name: "mangosql",
2123
HelpName: "MangoSQL",
2224
Usage: "Generate a SQL Client from a SQL file or folder of SQL migrations",
2325
UsageText: `Syntax: mangosql [options] <source folder>
2426
Example: mangosql --output db/file.go db/schema.sql`,
25-
Suggest: true,
27+
Suggest: true,
28+
EnableBashCompletion: true,
2629
Flags: []cli.Flag{
2730
&cli.StringFlag{
28-
Name: "output",
29-
Value: "database/client.go",
30-
Usage: "Output file",
31+
Name: "output",
32+
Aliases: []string{"o"},
33+
Value: "database/client.go",
34+
Usage: "Output file",
3135
},
3236
&cli.StringFlag{
33-
Name: "package",
34-
Value: "database",
35-
Usage: "Go Package",
37+
Name: "package",
38+
Aliases: []string{"p"},
39+
Value: "database",
40+
Usage: "Go Package",
3641
},
3742
&cli.StringFlag{
38-
Name: "driver",
39-
Value: "pgx",
40-
Usage: "SQL Driver",
43+
Name: "driver",
44+
Aliases: []string{"d"},
45+
Value: "pgx",
46+
Usage: "SQL Driver",
47+
},
48+
&cli.StringFlag{
49+
Name: "logger",
50+
Aliases: []string{"l"},
51+
Value: "none",
52+
Usage: "Logging library",
4153
},
4254
},
4355
Action: func(ctx *cli.Context) error {
4456
if ctx.NArg() <= 0 {
4557
return fmt.Errorf("missing source folder")
4658
}
4759

60+
allowed_drivers := []string{"pq", "pgx", "sqlite"}
61+
driver := ctx.String("driver")
62+
if !slices.Contains(allowed_drivers, driver) {
63+
return fmt.Errorf("unknown driver, should be one of %v", allowed_drivers)
64+
}
65+
66+
allowed_logger := []string{"none", "zap", "logrus", "zerolog", "console"}
67+
logger := ctx.String("logger")
68+
if !slices.Contains(allowed_logger, logger) {
69+
return fmt.Errorf("unknown logger, should be one of %v", allowed_logger)
70+
}
71+
4872
name := ctx.Args().Get(0)
4973
return generate(GenerateOptions{
5074
Src: name,
5175
Output: ctx.String("output"),
5276
Package: ctx.String("package"),
53-
Driver: ctx.String("driver"),
77+
Driver: driver,
78+
Logger: logger,
5479
})
5580
},
5681
}
@@ -65,6 +90,7 @@ type GenerateOptions struct {
6590
Output string
6691
Package string
6792
Driver string
93+
Logger string
6894
}
6995

7096
func generate(opts GenerateOptions) error {
@@ -110,7 +136,7 @@ func generate(opts GenerateOptions) error {
110136
var b bytes.Buffer
111137
contents := bufio.NewWriter(&b)
112138

113-
if err = internal.Generate(schema, contents, opts.Package, opts.Driver); err != nil {
139+
if err = internal.Generate(schema, contents, opts.Package, opts.Driver, opts.Logger); err != nil {
114140
return err
115141
}
116142

docs/.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ export default defineConfig({
3434
items: [
3535
// { text: 'UUID Generation', link: '/api/mutations' },
3636
{ text: 'Soft Delete', link: '/features/soft-delete' },
37-
// { text: 'Logging', link: '/api/mutations' },
37+
{ text: 'Logging', link: '/features/logging' },
3838
// { text: 'Migrations', link: '/api/mutations' },
39+
{ text: 'Benchmark', link: '/bench/bench' },
3940
]
4041
},
4142
// {

docs/bench/bench.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<script setup>
2+
import { withBase } from 'vitepress'
3+
</script>
4+
5+
# Benchmark
6+
7+
The following benchmarks are just here to help development and give a general performance idea of MangoSQL.
8+
9+
It's not intended to cover every method or library on the market.
10+
11+
## Postgres
12+
13+
::: info
14+
15+
Library Tested:
16+
* MangoSQL with PQ+SQLX driver
17+
* MangoSQL with PGX
18+
* [Gorm](https://gorm.io/) with PGX
19+
20+
:::
21+
22+
### CPU (Operation per second)
23+
24+
<iframe :src="withBase('/bench_postgres_insertmany_cpu.html')" width=576 height=320 frameBorder="0" scrolling="no" />
25+
26+
<iframe :src="withBase('/bench_postgres_findmany_cpu.html')" width=576 height=320 frameBorder="0" scrolling="no" />
27+
28+
### Memory Allocation
29+
30+
<iframe :src="withBase('/bench_postgres_insertmany_alloc.html')" width=576 height=320 frameBorder="0" scrolling="no"/>
31+
32+
<iframe :src="withBase('/bench_postgres_findmany_alloc.html')" width=576 height=320 frameBorder="0" scrolling="no"/>
33+
34+
---
35+
36+
## SQLite
37+
38+
::: info
39+
40+
Library Tested:
41+
* MangoSQL with modernc driver
42+
* [Gorm](https://gorm.io/) with gorm sqlite driver
43+
44+
:::
45+
46+
### CPU (Operation per second)
47+
48+
<iframe :src="withBase('/bench_sqlite_insertmany_cpu.html')" width=576 height=320 frameBorder="0" scrolling="no" />
49+
50+
<iframe :src="withBase('/bench_sqlite_findmany_cpu.html')" width=576 height=320 frameBorder="0" scrolling="no" />
51+
52+
### Memory Allocation
53+
54+
<iframe :src="withBase('/bench_sqlite_insertmany_alloc.html')" width=576 height=320 frameBorder="0" scrolling="no"/>
55+
56+
<iframe :src="withBase('/bench_sqlite_findmany_alloc.html')" width=576 height=320 frameBorder="0" scrolling="no"/>

0 commit comments

Comments
 (0)