@@ -2,6 +2,8 @@ package exporter
2
2
3
3
import (
4
4
"context"
5
+ "sort"
6
+ "strings"
5
7
6
8
"github.com/AlekSi/pointer"
7
9
"github.com/pkg/errors"
@@ -13,81 +15,133 @@ import (
13
15
14
16
var systemDBs = []string {"admin" , "config" , "local" } //nolint:gochecknoglobals
15
17
16
- func listCollections (ctx context.Context , client * mongo.Client , collections [] string , database string ) ([]string , error ) {
18
+ func listCollections (ctx context.Context , client * mongo.Client , database string , filterInNamespaces [] string ) ([]string , error ) {
17
19
filter := bson.D {} // Default=empty -> list all collections
18
20
19
21
// if there is a filter with the list of collections we want, create a filter like
20
22
// $or: {
21
23
// {"$regex": "collection1"},
22
24
// {"$regex": "collection2"},
23
25
// }
24
- if len (collections ) > 0 {
26
+ if len (filterInNamespaces ) > 0 {
25
27
matchExpressions := []bson.D {}
26
28
27
- for _ , collection := range collections {
28
- matchExpressions = append (matchExpressions ,
29
- bson.D {{Key : "name" , Value : primitive.Regex {Pattern : collection , Options : "i" }}})
29
+ for _ , namespace := range filterInNamespaces {
30
+ parts := strings .Split (namespace , "." ) // db.collection.name.with.dots
31
+ if len (parts ) > 1 {
32
+ // The part before the first dot is the database name.
33
+ // The rest is the collection name and it can have dots. We need to rebuild it.
34
+ collection := strings .Join (parts [1 :], "." )
35
+ matchExpressions = append (matchExpressions ,
36
+ bson.D {{Key : "name" , Value : primitive.Regex {Pattern : collection , Options : "i" }}})
37
+ }
30
38
}
31
39
32
- filter = bson.D {{Key : "$or" , Value : matchExpressions }}
40
+ if len (matchExpressions ) > 0 {
41
+ filter = bson.D {{Key : "$or" , Value : matchExpressions }}
42
+ }
33
43
}
34
44
35
- databases , err := client .Database (database ).ListCollectionNames (ctx , filter )
45
+ collections , err := client .Database (database ).ListCollectionNames (ctx , filter )
36
46
if err != nil {
37
47
return nil , errors .Wrap (err , "cannot get the list of collections for discovery" )
38
48
}
39
49
40
- return databases , nil
50
+ return collections , nil
41
51
}
42
52
43
- func databases (ctx context.Context , client * mongo.Client , exclude []string ) ([]string , error ) {
53
+ // databases returns the list of databases matching the filters.
54
+ // - filterInNamespaces: Include only the database names matching the any of the regular expressions in this list.
55
+ // Case will be ignored because the function will automatically add the ignore case
56
+ // flag to the regular expression.
57
+ // - exclude: List of databases to be excluded. Useful to ignore system databases.
58
+ func databases (ctx context.Context , client * mongo.Client , filterInNamespaces []string , exclude []string ) ([]string , error ) {
44
59
opts := & options.ListDatabasesOptions {NameOnly : pointer .ToBool (true ), AuthorizedDatabases : pointer .ToBool (true )}
60
+
61
+ filter := bson.D {}
62
+
63
+ if excludeFilter := makeExcludeFilter (exclude ); excludeFilter != nil {
64
+ filter = append (filter , * excludeFilter )
65
+ }
66
+
67
+ if namespacesFilter := makeDBsFilter (filterInNamespaces ); namespacesFilter != nil {
68
+ filter = append (filter , * namespacesFilter )
69
+ }
70
+
71
+ dbNames , err := client .ListDatabaseNames (ctx , filter , opts )
72
+ if err != nil {
73
+ return nil , errors .Wrap (err , "cannot get the database names list" )
74
+ }
75
+
76
+ return dbNames , nil
77
+ }
78
+
79
+ func makeExcludeFilter (exclude []string ) * primitive.E {
45
80
filterExpressions := []bson.D {}
46
81
for _ , dbname := range exclude {
47
82
filterExpressions = append (filterExpressions ,
48
83
bson.D {{Key : "name" , Value : bson.D {{Key : "$ne" , Value : dbname }}}},
49
84
)
50
85
}
51
86
52
- filter := bson.D {{Key : "$and" , Value : filterExpressions }}
87
+ if len (filterExpressions ) == 0 {
88
+ return nil
89
+ }
53
90
54
- dbNames , err := client .ListDatabaseNames (ctx , filter , opts )
55
- if err != nil {
56
- return nil , errors .Wrap (err , "cannot get the database names list" )
91
+ return & primitive.E {Key : "$and" , Value : filterExpressions }
92
+ }
93
+
94
+ // makeDBsFilter creates a filter to list all databases or only the ones in the specified
95
+ // namespaces list. Namespaces have the form of <db>.<collection> and the collection name
96
+ // can have a dot. Example: db1.collection.one -> db: db1, collection: collection.one
97
+ // db1, db2.col2, db3.col.one will produce [db1, db2, db3].
98
+ func makeDBsFilter (filterInNamespaces []string ) * primitive.E {
99
+ dbs := []string {}
100
+ if len (dbs ) == 0 {
101
+ return nil
57
102
}
58
103
59
- return dbNames , nil
104
+ for _ , namespace := range filterInNamespaces {
105
+ parts := strings .Split (namespace , "." )
106
+ dbs = append (dbs , parts [0 ])
107
+ }
108
+
109
+ return & primitive.E {Key : "name" , Value : primitive.E {Key : "$in" , Value : dbs }}
60
110
}
61
111
62
- func listAllCollections (ctx context.Context , client * mongo.Client , filter []string ) (map [string ][]string , error ) {
112
+ func listAllCollections (ctx context.Context , client * mongo.Client , filterInNamespaces [] string , excludeDBs []string ) (map [string ][]string , error ) {
63
113
namespaces := make (map [string ][]string )
64
- // exclude system databases
65
- dbnames , err := databases (ctx , client , systemDBs )
66
- if err != nil {
67
- return nil , errors .Wrap (err , "cannot get the list of all collections in the server" )
68
- }
69
114
70
- for _ , dbname := range dbnames {
71
- colls , err := listCollections (ctx , client , filter , dbname )
115
+ for _ , namespace := range filterInNamespaces {
116
+ parts := strings .Split (namespace , "." )
117
+ dbname := parts [0 ]
118
+
119
+ colls , err := listCollections (ctx , client , dbname , []string {namespace })
72
120
if err != nil {
73
121
return nil , errors .Wrapf (err , "cannot list the collections for %q" , dbname )
74
122
}
75
- namespaces [dbname ] = colls
123
+
124
+ if _ , ok := namespaces [dbname ]; ! ok {
125
+ namespaces [dbname ] = colls
126
+ } else {
127
+ namespaces [dbname ] = append (namespaces [dbname ], colls ... )
128
+ }
129
+ sort .Strings (namespaces [dbname ]) // make it testeable.
76
130
}
77
131
78
132
return namespaces , nil
79
133
}
80
134
81
- func allCollectionsCount (ctx context.Context , client * mongo.Client , filter []string ) (int , error ) {
82
- databases , err := databases (ctx , client , systemDBs )
135
+ func nonSystemCollectionsCount (ctx context.Context , client * mongo.Client , includeNamespaces [] string , filterInCollections []string ) (int , error ) {
136
+ databases , err := databases (ctx , client , includeNamespaces , systemDBs )
83
137
if err != nil {
84
138
return 0 , errors .Wrap (err , "cannot retrieve the collection names for count collections" )
85
139
}
86
140
87
141
var count int
88
142
89
143
for _ , dbname := range databases {
90
- colls , err := listCollections (ctx , client , filter , dbname )
144
+ colls , err := listCollections (ctx , client , dbname , filterInCollections )
91
145
if err != nil {
92
146
return 0 , errors .Wrap (err , "cannot get collections count" )
93
147
}
0 commit comments