[feat] sqlite server facade

Signed-off-by: Anton Nesterov <anton@demiurg.io>
This commit is contained in:
Anton Nesterov 2024-08-17 18:53:58 +02:00
parent fa9d105e57
commit 80410d5585
No known key found for this signature in database
GPG key ID: 59121E8AE2851FB5
12 changed files with 147 additions and 18 deletions

View file

@ -12,13 +12,11 @@ replace l12.xyz/dal/adapter v0.0.0 => ../../../pkg/adapter
replace l12.xyz/dal/utils v0.0.0 => ../../../pkg/utils replace l12.xyz/dal/utils v0.0.0 => ../../../pkg/utils
replace l12.xyz/dal/proto v0.0.0 => ../../../pkg/proto replace l12.xyz/dal/proto v0.0.0 => ../../../pkg/proto
require l12.xyz/dal/server v0.0.0 require l12.xyz/dal/handler v0.0.0
replace l12.xyz/dal/server v0.0.0 => ../../../pkg/server replace l12.xyz/dal/handler v0.0.0 => ../../../pkg/handler
require ( require (
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
@ -27,6 +25,6 @@ require (
github.com/tinylib/msgp v1.2.0 // indirect github.com/tinylib/msgp v1.2.0 // indirect
l12.xyz/dal/builder v0.0.0 // indirect l12.xyz/dal/builder v0.0.0 // indirect
l12.xyz/dal/filters v0.0.0 // indirect l12.xyz/dal/filters v0.0.0 // indirect
l12.xyz/dal/utils v0.0.0 // indirect
l12.xyz/dal/proto v0.0.0 // indirect l12.xyz/dal/proto v0.0.0 // indirect
l12.xyz/dal/utils v0.0.0 // indirect
) )

View file

@ -9,7 +9,7 @@ import (
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"l12.xyz/dal/adapter" "l12.xyz/dal/adapter"
"l12.xyz/dal/server" "l12.xyz/dal/handler"
) )
func mock(adapter adapter.DBAdapter) { func mock(adapter adapter.DBAdapter) {
@ -34,7 +34,7 @@ func main() {
Type: "sqlite3", Type: "sqlite3",
} }
mock(db) mock(db)
queryHandler := server.QueryHandler(db) queryHandler := handler.QueryHandler(db)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/", queryHandler) mux.Handle("/", queryHandler)
fmt.Println("Server running on port 8111") fmt.Println("Server running on port 8111")

View file

@ -59,11 +59,11 @@ Locations:
Parsing the row stream (pseudo code): Parsing the row stream (pseudo code):
```python ```
header = [0x81, 0xa1, 0x72] header = [0x81, 0xa1, 0x72]
input: byte[] = ... input: byte[]
buffer: byte[] = [] buffer: byte[]
output: byte[][] = [] output: byte[][]
while i < input.length: while i < input.length:
if input[i] != 0x81: if input[i] != 0x81:
buffer << input[i] buffer << input[i]
@ -140,7 +140,7 @@ Locations:
``` ```
### Builder Methods ### Builder Methods
In|Find|Select|Fields|Join|Group|Sort|Limit|Offset|Delete|Insert|Set|Update|OnConflict|DoUpdate|DoNothing Raw|In|Find|Select|Fields|Join|Group|Sort|Limit|Offset|Delete|Insert|Set|Update|OnConflict|DoUpdate|DoNothing
[TS Docs]() [TS Docs]()
[Golang Docs]() [Golang Docs]()

View file

@ -8,12 +8,13 @@ import (
/* /*
DBAdapter DBAdapter
Automatically creates connections for each database URL. - Automatically creates connections for each database URL.
Executes queries on the specified database. - Executes queries on the specified database.
Closes connections older than ConnectionLiveTime - Closes connections older than ConnectionLiveTime
*/ */
type DBAdapter struct { type DBAdapter struct {
Type string Type string
DbInit []string
MaxAttempts int MaxAttempts int
ConnectionLiveTime int ConnectionLiveTime int
dbs *DBMap dbs *DBMap
@ -25,6 +26,10 @@ type DBMap struct {
ConnectionTime map[string]int64 ConnectionTime map[string]int64
} }
func (a *DBAdapter) AfterOpen(sql string) {
a.DbInit = append(a.DbInit, sql)
}
func (a *DBAdapter) Open(url string) (*sql.DB, error) { func (a *DBAdapter) Open(url string) (*sql.DB, error) {
defer a.CleanUp() defer a.CleanUp()
@ -69,6 +74,9 @@ func (a *DBAdapter) Open(url string) (*sql.DB, error) {
} }
attempts[url] = 0 attempts[url] = 0
for _, sql := range a.DbInit {
connections[url].Exec(sql)
}
return connections[url], nil return connections[url], nil
} }
@ -113,6 +121,10 @@ func (a *DBAdapter) Query(req Query) (*sql.Rows, error) {
if req.Transaction { if req.Transaction {
tx, _ := db.Begin() tx, _ := db.Begin()
rows, err := tx.Query(req.Expression, req.Data...) rows, err := tx.Query(req.Expression, req.Data...)
if err != nil {
tx.Rollback()
return nil, err
}
tx.Commit() tx.Commit()
return rows, err return rows, err
} }
@ -131,6 +143,10 @@ func (a *DBAdapter) Exec(req Query) (sql.Result, error) {
if req.Transaction { if req.Transaction {
tx, _ := db.Begin() tx, _ := db.Begin()
result, err := tx.Exec(req.Expression, req.Data...) result, err := tx.Exec(req.Expression, req.Data...)
if err != nil {
tx.Rollback()
return nil, err
}
tx.Commit() tx.Commit()
return result, err return result, err
} }

View file

@ -0,0 +1,70 @@
package facade
import (
"net/http"
"os"
"strings"
"l12.xyz/dal/adapter"
"l12.xyz/dal/handler"
)
type SQLiteServer struct {
Cwd string
Port string
}
/*
Init initializes the SQLiteServer struct with the required environment variables.
- `SQLITE_DIRECTORY` is the directory where the SQLite database is stored.
- `SQLITE_PORT` is the port on which the server listens.
*/
func (s *SQLiteServer) Init() {
s.Cwd = os.Getenv("SQLITE_DIRECTORY")
if s.Cwd == "" {
panic("env variable `SQLITE_DIRECTORY` is not set")
}
os.MkdirAll(s.Cwd, os.ModePerm)
os.Chdir(s.Cwd)
s.Cwd = os.Getenv("SQLITE_PORT")
if s.Port == "" {
s.Port = "8118"
}
}
/*
GetAdapter returns a DBAdapter struct with the SQLite3 dialect registered.
- The `SQLITE_PRAGMAS` environment variable is expected to be a semicolon separated list of PRAGMA statements.
*/
func (s *SQLiteServer) GetAdapter() adapter.DBAdapter {
adapter.RegisterDialect("sqlite3", adapter.CommonDialect{})
db := adapter.DBAdapter{
Type: "sqlite3",
}
db.AfterOpen("PRAGMA journal_mode=WAL")
for _, pragma := range strings.Split(os.Getenv("SQLITE_PRAGMAS"), ";") {
if pragma == "" {
continue
}
db.AfterOpen(pragma)
}
return db
}
/*
GetHanfler returns a http.Handler configured for the SQLiteServer.
*/
func (s *SQLiteServer) GetHandler() http.Handler {
return handler.QueryHandler(s.GetAdapter())
}
/*
Serve starts the basic server on the configured port.
Use `GetHandler` to get a handler for a custom server.
*/
func (s *SQLiteServer) Serve() {
err := http.ListenAndServe(":"+s.Port, s.GetHandler())
if err != nil {
panic(err)
}
}

29
pkg/facade/go.mod Normal file
View file

@ -0,0 +1,29 @@
module l12.xyz/dal/facade
go 1.22.6
replace l12.xyz/dal/filters v0.0.0 => ../filters
replace l12.xyz/dal/builder v0.0.0 => ../builder
require l12.xyz/dal/adapter v0.0.0
replace l12.xyz/dal/adapter v0.0.0 => ../adapter
replace l12.xyz/dal/utils v0.0.0 => ../utils
require (
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/tinylib/msgp v1.2.0 // indirect
l12.xyz/dal/builder v0.0.0 // indirect
l12.xyz/dal/filters v0.0.0 // indirect
l12.xyz/dal/proto v0.0.0 // indirect
l12.xyz/dal/utils v0.0.0 // indirect
)
replace l12.xyz/dal/proto v0.0.0 => ../proto
require l12.xyz/dal/handler v0.0.0
replace l12.xyz/dal/handler v0.0.0 => ../handler

View file

@ -1,4 +1,4 @@
module l12.xyz/dal/server module l12.xyz/dal/http
go 1.22.6 go 1.22.6

8
pkg/handler/go.sum Normal file
View file

@ -0,0 +1,8 @@
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4=
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/tinylib/msgp v1.2.0 h1:0uKB/662twsVBpYUPbokj4sTSKhWFKB7LopO2kWK8lY=
github.com/tinylib/msgp v1.2.0/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro=

View file

@ -1,4 +1,4 @@
package server package handler
import ( import (
"io" "io"

View file

@ -1,4 +1,4 @@
package server package handler
import ( import (
"bytes" "bytes"

View file

@ -49,6 +49,14 @@ func (q *Request) Parse(dialect adapter.Dialect) (adapter.Query, error) {
if cmd.Method == "Insert" || cmd.Method == "Set" || cmd.Method == "Delete" { if cmd.Method == "Insert" || cmd.Method == "Set" || cmd.Method == "Delete" {
exec = true exec = true
} }
// check if raw is an exec query
if cmd.Method == "Raw" {
qo, ok := cmd.Args[0].(map[string]interface{})
if ok {
sq := qo["s"].(string)
exec = !strings.HasPrefix(sq, "SELECT")
}
}
args := make([]reflect.Value, len(cmd.Args)) args := make([]reflect.Value, len(cmd.Args))
for i, arg := range cmd.Args { for i, arg := range cmd.Args {
args[i] = reflect.ValueOf(arg) args[i] = reflect.ValueOf(arg)