Add views
This commit is contained in:
56
.test/activity.json
Normal file
56
.test/activity.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"_group": "system_collections",
|
||||||
|
"_name": "activity",
|
||||||
|
"_version": "202507",
|
||||||
|
"singular": "activity",
|
||||||
|
"plural": "activities",
|
||||||
|
"idPrefix": "act",
|
||||||
|
"system": true,
|
||||||
|
"indexSpecs": [{
|
||||||
|
"name": "name_1",
|
||||||
|
"keys": { "name": 1 },
|
||||||
|
"unique": true
|
||||||
|
}],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"_id": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"person": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
},
|
||||||
|
"archivedAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"activityExpanded": {
|
||||||
|
"viewOn": "activity",
|
||||||
|
"pipeline": [{
|
||||||
|
"$lookup": {
|
||||||
|
"from": "person",
|
||||||
|
"localField": "person",
|
||||||
|
"foreignField": "_id",
|
||||||
|
"as": "refPerson"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"$unwind": "$refPerson"
|
||||||
|
}, {
|
||||||
|
"$set": { "person": "$refPerson" }
|
||||||
|
}, {
|
||||||
|
"$unset": "refPerson"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
.test/person.json
Normal file
37
.test/person.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"_group": "system_collections",
|
||||||
|
"_name": "person",
|
||||||
|
"_version": "202507",
|
||||||
|
"singular": "person",
|
||||||
|
"plural": "persons",
|
||||||
|
"idPrefix": "prs",
|
||||||
|
"system": true,
|
||||||
|
"indexSpecs": [{
|
||||||
|
"name": "name_1",
|
||||||
|
"keys": { "name": 1 },
|
||||||
|
"unique": true
|
||||||
|
}],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"_id": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"bsonType": "number"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
},
|
||||||
|
"archivedAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
.test/sample.json
Normal file
20
.test/sample.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[{
|
||||||
|
"name": "PKCELL Ultra Alkaline Batteries Size D LR20-2B (2-Pack)",
|
||||||
|
"sku": "ALK-PK-LR202B-01",
|
||||||
|
"price": {
|
||||||
|
"value": 288,
|
||||||
|
"currency": "cad"
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"tags": ["alkaline", "size-d", "pkcell"]
|
||||||
|
}, {
|
||||||
|
"name": "Shimano Cleats SH-11 Yellow 6 degrees",
|
||||||
|
"sku": "BK-SM-SH51",
|
||||||
|
"active": true,
|
||||||
|
"tags": ["shimano", "cleats", "sh11", "cycling"]
|
||||||
|
}, {
|
||||||
|
"name": "OSRAM Light Bulbs H7 Original Classic 12V 55W Spare Part Replacement",
|
||||||
|
"sku": "LT-OSR-H712V35W",
|
||||||
|
"active": false,
|
||||||
|
"tags": ["osram", "h7", "halogen-bulb", "12v", "55w"]
|
||||||
|
}]
|
||||||
53
.test/user.json
Normal file
53
.test/user.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"_group": "system_collections",
|
||||||
|
"_name": "user",
|
||||||
|
"_version": "202507",
|
||||||
|
"singular": "user",
|
||||||
|
"plural": "users",
|
||||||
|
"idPrefix": "usr",
|
||||||
|
"system": true,
|
||||||
|
"indexSpecs": [{
|
||||||
|
"name": "username_1",
|
||||||
|
"keys": { "username": 1 },
|
||||||
|
"unique": true
|
||||||
|
}, {
|
||||||
|
"name": "email_1",
|
||||||
|
"keys": { "email": 1 },
|
||||||
|
"unique": true
|
||||||
|
}],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"_id": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"fullname": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"bsonType": "string"
|
||||||
|
},
|
||||||
|
"credentials": {
|
||||||
|
"bsonType": ["object", "null"]
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
},
|
||||||
|
"archivedAt": {
|
||||||
|
"bsonType": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
main.go
15
main.go
@@ -39,12 +39,13 @@ type IMongoClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CollectionDefinition struct {
|
type CollectionDefinition struct {
|
||||||
Name string `bson:"_name"`
|
Name string `bson:"_name"`
|
||||||
Singular string `bson:"singular"`
|
Singular string `bson:"singular"`
|
||||||
Plural string `bson: "plural"`
|
Plural string `bson:"plural"`
|
||||||
IdPrefix string `bson: "idPrefix"`
|
IdPrefix string `bson:"idPrefix"`
|
||||||
IndexSpecs []map[string]any `bson: "indexSpecs"`
|
IndexSpecs []map[string]any `bson:"indexSpecs"`
|
||||||
Schema map[string]any `bson: "schema"`
|
Schema map[string]any `bson:"schema"`
|
||||||
|
Views map[string]any `bson:"views"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (cd *CollectionDefinition) GetSchema(name string)
|
// func (cd *CollectionDefinition) GetSchema(name string)
|
||||||
@@ -168,6 +169,8 @@ func (c *MongoClient) GetCollection(database, name string) *mongo.Collection {
|
|||||||
|
|
||||||
c.CreateIndexes(collection, cdef)
|
c.CreateIndexes(collection, cdef)
|
||||||
|
|
||||||
|
c.CreateViews(db, cdef)
|
||||||
|
|
||||||
return collection
|
return collection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (c *MongoClient) CreateIndexes(collection *mongo.Collection, cdef *Collecti
|
|||||||
indexModels := make([]mongo.IndexModel, 0)
|
indexModels := make([]mongo.IndexModel, 0)
|
||||||
|
|
||||||
for _, keyDef := range cdef.IndexSpecs {
|
for _, keyDef := range cdef.IndexSpecs {
|
||||||
log.Printf("Key Definition %s", keyDef["name"])
|
log.Printf("Key Definition [%s]%s", cdef.Name, keyDef["name"])
|
||||||
|
|
||||||
kdb, err := bson.Marshal(keyDef)
|
kdb, err := bson.Marshal(keyDef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
91
main_views.go
Normal file
91
main_views.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package mongo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
// "runtime"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateViews will create views for the given collection and definition.
|
||||||
|
func (c *MongoClient) CreateViews(db *mongo.Database, cdef *CollectionDefinition) {
|
||||||
|
if cdef == nil || cdef.Views == nil {
|
||||||
|
log.Printf("No definitions will not create views")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime.Breakpoint()
|
||||||
|
|
||||||
|
for name, defVal := range cdef.Views {
|
||||||
|
|
||||||
|
// 1. Decode definition
|
||||||
|
v, err := bson.Marshal(defVal)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal %v", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Take the raw representation
|
||||||
|
vRaw := bson.Raw(v)
|
||||||
|
if err := vRaw.Validate(); err != nil {
|
||||||
|
log.Printf("failed to validate bson raw: %v", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineVal := vRaw.Lookup("pipeline")
|
||||||
|
pipelineArr, ok := pipelineVal.ArrayOK()
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Unable to extract pipeline")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline := mongo.Pipeline{}
|
||||||
|
|
||||||
|
successPipeline := true
|
||||||
|
pipelineValues, _ := pipelineArr.Values()
|
||||||
|
for _, o := range pipelineValues {
|
||||||
|
var stage bson.D
|
||||||
|
if err := bson.Unmarshal(o.Value, &stage); err != nil {
|
||||||
|
log.Printf("failed to unmarshal stage %v, %v", o, err)
|
||||||
|
|
||||||
|
successPipeline = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline = append(pipeline, stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if successPipeline == false {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the Collation option to set a default collation for the view.
|
||||||
|
opts := options.CreateView().SetCollation(&options.Collation{
|
||||||
|
Locale: "en_US",
|
||||||
|
})
|
||||||
|
|
||||||
|
viewonVal := vRaw.Lookup("viewOn")
|
||||||
|
viewOn, ok := viewonVal.StringValueOK();
|
||||||
|
if !ok {
|
||||||
|
log.Printf("failed to find viewOn")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CreateView(context.TODO(), name, viewOn, pipeline, opts)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to create view %v", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
61
main_views_test.go
Normal file
61
main_views_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package mongo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateViews(t *testing.T) {
|
||||||
|
// 1. Register schemas
|
||||||
|
schemaPerson, err := os.ReadFile("./.test/person.json")
|
||||||
|
if err != nil { t.Fatal(err) }
|
||||||
|
|
||||||
|
schemaActivity, err := os.ReadFile("./.test/activity.json")
|
||||||
|
if err != nil { t.Fatal(err) }
|
||||||
|
|
||||||
|
var person bson.M
|
||||||
|
if err := json.Unmarshal(schemaPerson, &person); err != nil {
|
||||||
|
t.Fatalf("Length: %d, First bytes: %x\n", len(schemaPerson), schemaPerson[:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
var activity bson.M
|
||||||
|
if err := json.Unmarshal(schemaActivity, &activity); err != nil {
|
||||||
|
t.Fatalf("Length: %d, First bytes: %x\n", len(schemaActivity), schemaActivity[:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
client := GetMongoClient()
|
||||||
|
client.AddDefinition(person)
|
||||||
|
client.AddDefinition(activity)
|
||||||
|
|
||||||
|
// 2. Insert data
|
||||||
|
p1 := map[string]any {
|
||||||
|
"name": "MyName112",
|
||||||
|
"age": int32(25),
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := client.InsertOne(context.Background(), "mydb", "person", p1)
|
||||||
|
if err != nil { t.Fatalf("Failed to insertOne %#v", err) }
|
||||||
|
|
||||||
|
a1 := map[string]any {
|
||||||
|
"name": "Main activity",
|
||||||
|
"person": o["_id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err = client.InsertOne(context.Background(), "mydb", "activity", a1)
|
||||||
|
if err != nil { t.Fatalf("Failed to insertOne %#v", err) }
|
||||||
|
|
||||||
|
// 3. Should have activityExpanded defined, let's query it.
|
||||||
|
var results bson.M
|
||||||
|
filter := map[string]any { "person.name": "MyName112" }
|
||||||
|
c := client.Client.Database("mydb").Collection("activityExpanded")
|
||||||
|
c.FindOne(context.Background(), filter).Decode(&results)
|
||||||
|
|
||||||
|
if !strings.HasPrefix(results["_id"].(string), "act_") {
|
||||||
|
t.Fatal("_id should have been prefixed")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user