up
This commit is contained in:
@@ -82,6 +82,7 @@ require (
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
@@ -106,6 +107,7 @@ require (
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||
@@ -122,7 +124,12 @@ require (
|
||||
github.com/valyala/fasthttp v1.56.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
|
||||
@@ -287,6 +287,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
|
||||
@@ -512,6 +514,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -671,11 +675,19 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -688,6 +700,8 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -966,6 +980,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
|
||||
@@ -3,7 +3,9 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +13,9 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
_ "github.com/microsoft/go-mssqldb"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
||||
)
|
||||
@@ -76,6 +81,9 @@ func openDynamicDB(c models.ConxDb) (*sql.DB, error) {
|
||||
// ExecuteSQL ejecuta SQL arbitrario contra la conexión y devuelve QueryResult.
|
||||
// También guarda en query_history.
|
||||
func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult {
|
||||
if isMongoDriver(strings.ToLower(conx.TipoDb.Nombre)) {
|
||||
return mongoExecuteSQL(conx, database, sqlText)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
db, err := openDynamicDB(conx)
|
||||
@@ -161,13 +169,16 @@ func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult {
|
||||
|
||||
// ListDatabases devuelve la lista de bases de datos del servidor.
|
||||
func ListDatabases(conx models.ConxDb) ([]string, error) {
|
||||
driver := strings.ToLower(conx.TipoDb.Nombre)
|
||||
if isMongoDriver(driver) {
|
||||
return mongoListDatabases(conx)
|
||||
}
|
||||
db, err := openDynamicDB(conx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
driver := strings.ToLower(conx.TipoDb.Nombre)
|
||||
var query string
|
||||
switch {
|
||||
case strings.Contains(driver, "postgres"):
|
||||
@@ -200,6 +211,9 @@ func ListDatabases(conx models.ConxDb) ([]string, error) {
|
||||
// ListTables devuelve las tablas de una base de datos.
|
||||
func ListTables(conx models.ConxDb, database string) ([]string, error) {
|
||||
driver := strings.ToLower(conx.TipoDb.Nombre)
|
||||
if isMongoDriver(driver) {
|
||||
return mongoListCollections(conx, database)
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
var err error
|
||||
@@ -246,6 +260,9 @@ func ListTables(conx models.ConxDb, database string) ([]string, error) {
|
||||
|
||||
// TestConnection verifica si la conexión es válida.
|
||||
func TestDBConnection(conx models.ConxDb) error {
|
||||
if isMongoDriver(strings.ToLower(conx.TipoDb.Nombre)) {
|
||||
return mongoTestConnection(conx)
|
||||
}
|
||||
db, err := openDynamicDB(conx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -315,3 +332,371 @@ func saveHistory(conxID uint, sqlText, status, errMsg string, rows, durationMs i
|
||||
ExecutedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
// ── MongoDB ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func isMongoDriver(driver string) bool {
|
||||
return strings.Contains(driver, "mongo")
|
||||
}
|
||||
|
||||
func mongoURI(c models.ConxDb) string {
|
||||
host := c.Servidor.IpServidor
|
||||
port := c.Puerto
|
||||
user := c.Usuario
|
||||
pass := c.Password
|
||||
if user != "" && pass != "" {
|
||||
return fmt.Sprintf("mongodb://%s:%s@%s:%s/?directConnection=true&serverSelectionTimeoutMS=8000", user, pass, host, port)
|
||||
}
|
||||
return fmt.Sprintf("mongodb://%s:%s/?directConnection=true&serverSelectionTimeoutMS=8000", host, port)
|
||||
}
|
||||
|
||||
func mongoConnect(c models.ConxDb) (*mongo.Client, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), connTimeout)
|
||||
defer cancel()
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI(c)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.Ping(ctx, nil); err != nil {
|
||||
client.Disconnect(context.Background()) //nolint
|
||||
return nil, fmt.Errorf("no se pudo conectar a MongoDB: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func mongoTestConnection(c models.ConxDb) error {
|
||||
client, err := mongoConnect(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client.Disconnect(context.Background()) //nolint
|
||||
return nil
|
||||
}
|
||||
|
||||
func mongoListDatabases(c models.ConxDb) ([]string, error) {
|
||||
client, err := mongoConnect(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Disconnect(context.Background()) //nolint
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
return client.ListDatabaseNames(ctx, bson.M{})
|
||||
}
|
||||
|
||||
func mongoListCollections(c models.ConxDb, database string) ([]string, error) {
|
||||
client, err := mongoConnect(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Disconnect(context.Background()) //nolint
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
return client.Database(database).ListCollectionNames(ctx, bson.M{})
|
||||
}
|
||||
|
||||
var mongoQueryRe = regexp.MustCompile(`(?s)^db\.(\w[\w\d_]*)\.([\w]+)\((.*)\)\s*$`)
|
||||
|
||||
func mongoExecuteSQL(conx models.ConxDb, database, queryText string) QueryResult {
|
||||
start := time.Now()
|
||||
client, err := mongoConnect(conx)
|
||||
if err != nil {
|
||||
saveHistory(conx.ID, queryText, "error", err.Error(), 0, time.Since(start).Milliseconds())
|
||||
return QueryResult{Error: err.Error()}
|
||||
}
|
||||
defer client.Disconnect(context.Background()) //nolint
|
||||
db := client.Database(database)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
result, err := mongoRunQuery(ctx, db, strings.TrimSpace(queryText))
|
||||
elapsed := time.Since(start).Milliseconds()
|
||||
if err != nil {
|
||||
saveHistory(conx.ID, queryText, "error", err.Error(), 0, elapsed)
|
||||
return QueryResult{Error: err.Error()}
|
||||
}
|
||||
result.DurationMs = elapsed
|
||||
saveHistory(conx.ID, queryText, "ok", "", int64(result.RowCount)+result.AffectedRows, elapsed)
|
||||
return *result
|
||||
}
|
||||
|
||||
func mongoRunQuery(ctx context.Context, db *mongo.Database, query string) (*QueryResult, error) {
|
||||
m := mongoQueryRe.FindStringSubmatch(query)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("sintaxis no reconocida. Use: db.coleccion.metodo({...})")
|
||||
}
|
||||
collName, method, argsRaw := m[1], strings.ToLower(m[2]), strings.TrimSpace(m[3])
|
||||
if collName == "runCommand" {
|
||||
return mongoRunCommand(ctx, db, argsRaw)
|
||||
}
|
||||
coll := db.Collection(collName)
|
||||
args := splitTopLevelArgs(argsRaw)
|
||||
switch method {
|
||||
case "find":
|
||||
return mongoFind(ctx, coll, args, false)
|
||||
case "findone":
|
||||
return mongoFind(ctx, coll, args, true)
|
||||
case "insertone":
|
||||
return mongoInsertOne(ctx, coll, args)
|
||||
case "insertmany":
|
||||
return mongoInsertMany(ctx, coll, args)
|
||||
case "updateone":
|
||||
return mongoUpdate(ctx, coll, args, false)
|
||||
case "updatemany":
|
||||
return mongoUpdate(ctx, coll, args, true)
|
||||
case "deleteone":
|
||||
return mongoDelete(ctx, coll, args, false)
|
||||
case "deletemany":
|
||||
return mongoDelete(ctx, coll, args, true)
|
||||
case "countdocuments":
|
||||
return mongoCount(ctx, coll, args)
|
||||
case "aggregate":
|
||||
return mongoAggregate(ctx, coll, argsRaw)
|
||||
case "drop":
|
||||
if err := coll.Drop(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &QueryResult{IsSelect: false}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("método MongoDB no soportado: %s", m[2])
|
||||
}
|
||||
}
|
||||
|
||||
func mongoFind(ctx context.Context, coll *mongo.Collection, args []string, one bool) (*QueryResult, error) {
|
||||
var filter bson.M
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[0]), true, &filter); err != nil {
|
||||
return nil, fmt.Errorf("filtro inválido: %w", err)
|
||||
}
|
||||
} else {
|
||||
filter = bson.M{}
|
||||
}
|
||||
var docs []bson.M
|
||||
if one {
|
||||
var doc bson.M
|
||||
if err := coll.FindOne(ctx, filter).Decode(&doc); err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return &QueryResult{IsSelect: true, Columns: []string{}, Rows: []map[string]any{}}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
docs = []bson.M{doc}
|
||||
} else {
|
||||
cursor, err := coll.Find(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
if err := cursor.All(ctx, &docs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bsonDocsToResult(docs), nil
|
||||
}
|
||||
|
||||
func mongoInsertOne(ctx context.Context, coll *mongo.Collection, args []string) (*QueryResult, error) {
|
||||
if len(args) == 0 || args[0] == "" {
|
||||
return nil, fmt.Errorf("insertOne requiere un documento")
|
||||
}
|
||||
var doc bson.M
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[0]), true, &doc); err != nil {
|
||||
return nil, fmt.Errorf("documento inválido: %w", err)
|
||||
}
|
||||
res, err := coll.InsertOne(ctx, doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &QueryResult{
|
||||
IsSelect: true, AffectedRows: 1,
|
||||
Columns: []string{"insertedId"},
|
||||
Rows: []map[string]any{{"insertedId": fmt.Sprintf("%v", res.InsertedID)}},
|
||||
RowCount: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mongoInsertMany(ctx context.Context, coll *mongo.Collection, args []string) (*QueryResult, error) {
|
||||
if len(args) == 0 || args[0] == "" {
|
||||
return nil, fmt.Errorf("insertMany requiere un array de documentos")
|
||||
}
|
||||
var arr bson.A
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[0]), true, &arr); err != nil {
|
||||
return nil, fmt.Errorf("documentos inválidos: %w", err)
|
||||
}
|
||||
docs := make([]interface{}, len(arr))
|
||||
copy(docs, arr)
|
||||
res, err := coll.InsertMany(ctx, docs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &QueryResult{IsSelect: false, AffectedRows: int64(len(res.InsertedIDs))}, nil
|
||||
}
|
||||
|
||||
func mongoUpdate(ctx context.Context, coll *mongo.Collection, args []string, many bool) (*QueryResult, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("update requiere filtro y documento de actualización")
|
||||
}
|
||||
var filter, update bson.M
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[0]), true, &filter); err != nil {
|
||||
return nil, fmt.Errorf("filtro inválido: %w", err)
|
||||
}
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[1]), true, &update); err != nil {
|
||||
return nil, fmt.Errorf("update inválido: %w", err)
|
||||
}
|
||||
var affected int64
|
||||
if many {
|
||||
res, err := coll.UpdateMany(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
affected = res.ModifiedCount
|
||||
} else {
|
||||
res, err := coll.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
affected = res.ModifiedCount
|
||||
}
|
||||
return &QueryResult{IsSelect: false, AffectedRows: affected}, nil
|
||||
}
|
||||
|
||||
func mongoDelete(ctx context.Context, coll *mongo.Collection, args []string, many bool) (*QueryResult, error) {
|
||||
var filter bson.M
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[0]), true, &filter); err != nil {
|
||||
return nil, fmt.Errorf("filtro inválido: %w", err)
|
||||
}
|
||||
} else {
|
||||
filter = bson.M{}
|
||||
}
|
||||
var deleted int64
|
||||
if many {
|
||||
res, err := coll.DeleteMany(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deleted = res.DeletedCount
|
||||
} else {
|
||||
res, err := coll.DeleteOne(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deleted = res.DeletedCount
|
||||
}
|
||||
return &QueryResult{IsSelect: false, AffectedRows: deleted}, nil
|
||||
}
|
||||
|
||||
func mongoCount(ctx context.Context, coll *mongo.Collection, args []string) (*QueryResult, error) {
|
||||
var filter bson.M
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
if err := bson.UnmarshalExtJSON([]byte(args[0]), true, &filter); err != nil {
|
||||
return nil, fmt.Errorf("filtro inválido: %w", err)
|
||||
}
|
||||
} else {
|
||||
filter = bson.M{}
|
||||
}
|
||||
count, err := coll.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &QueryResult{
|
||||
IsSelect: true,
|
||||
Columns: []string{"count"},
|
||||
Rows: []map[string]any{{"count": count}},
|
||||
RowCount: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mongoAggregate(ctx context.Context, coll *mongo.Collection, pipelineStr string) (*QueryResult, error) {
|
||||
var pipeline bson.A
|
||||
if err := bson.UnmarshalExtJSON([]byte(pipelineStr), true, &pipeline); err != nil {
|
||||
return nil, fmt.Errorf("pipeline inválido: %w", err)
|
||||
}
|
||||
cursor, err := coll.Aggregate(ctx, pipeline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
var docs []bson.M
|
||||
if err := cursor.All(ctx, &docs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bsonDocsToResult(docs), nil
|
||||
}
|
||||
|
||||
func mongoRunCommand(ctx context.Context, db *mongo.Database, cmdStr string) (*QueryResult, error) {
|
||||
var cmd bson.D
|
||||
if err := bson.UnmarshalExtJSON([]byte(cmdStr), true, &cmd); err != nil {
|
||||
return nil, fmt.Errorf("comando inválido: %w", err)
|
||||
}
|
||||
var result bson.M
|
||||
if err := db.RunCommand(ctx, cmd).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bsonDocsToResult([]bson.M{result}), nil
|
||||
}
|
||||
|
||||
func bsonDocsToResult(docs []bson.M) *QueryResult {
|
||||
if len(docs) == 0 {
|
||||
return &QueryResult{IsSelect: true, Columns: []string{}, Rows: []map[string]any{}}
|
||||
}
|
||||
keyIdx := make(map[string]int)
|
||||
for _, doc := range docs {
|
||||
for k := range doc {
|
||||
if _, seen := keyIdx[k]; !seen {
|
||||
keyIdx[k] = len(keyIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
cols := make([]string, len(keyIdx))
|
||||
for k, i := range keyIdx {
|
||||
cols[i] = k
|
||||
}
|
||||
rows := make([]map[string]any, len(docs))
|
||||
for i, doc := range docs {
|
||||
row := make(map[string]any, len(doc))
|
||||
for k, v := range doc {
|
||||
switch v.(type) {
|
||||
case bson.M, bson.A, bson.D:
|
||||
b, _ := json.Marshal(v)
|
||||
row[k] = string(b)
|
||||
default:
|
||||
row[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
rows[i] = row
|
||||
}
|
||||
return &QueryResult{IsSelect: true, Columns: cols, Rows: rows, RowCount: len(rows)}
|
||||
}
|
||||
|
||||
func splitTopLevelArgs(s string) []string {
|
||||
var args []string
|
||||
depth := 0
|
||||
inStr := false
|
||||
start := 0
|
||||
s = strings.TrimSpace(s)
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
switch ch {
|
||||
case '"':
|
||||
if i == 0 || s[i-1] != '\\' {
|
||||
inStr = !inStr
|
||||
}
|
||||
case '{', '[', '(':
|
||||
if !inStr {
|
||||
depth++
|
||||
}
|
||||
case '}', ']', ')':
|
||||
if !inStr {
|
||||
depth--
|
||||
}
|
||||
case ',':
|
||||
if !inStr && depth == 0 {
|
||||
args = append(args, strings.TrimSpace(s[start:i]))
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if last := strings.TrimSpace(s[start:]); last != "" {
|
||||
args = append(args, last)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user