1import string
2import random
3
4
5class RepeatableGenerator(object):
6
7    def __init__(self, iterable):
8        self.reset()
9        self.iterable = iterable
10
11    def next(self):
12        self.counter += 1
13        if self.counter == len(self.iterable):
14            self.counter = 0
15        return self.iterable[self.counter]
16
17    def reset(self):
18        self.counter = -1
19
20
21class ViewGen(object):
22
23    ddocs = RepeatableGenerator(tuple(string.ascii_uppercase))
24
25    views = RepeatableGenerator((
26        "id_by_city",
27        "name_and_email_by_category_and_and_coins",
28        "id_by_realm_and_coins",
29        "name_and_email_by_city",
30        "name_by_category_and_and_coins",
31        "experts_id_by_realm_and_coins",
32        "id_by_realm",
33        "achievements_by_category_and_and_coins",
34        "name_and_email_by_realm_and_coins",
35        "experts_coins_by_name"
36    ))
37
38    maps = {
39        "id_by_city":
40            """
41            function(doc, meta) {
42                emit(doc.city, null);
43            }
44            """,
45        "name_and_email_by_city":
46            """
47            function(doc, meta) {
48                emit(doc.city, [doc.name, doc.email]);
49            }
50            """,
51        "id_by_realm":
52            """
53            function(doc, meta) {
54                emit(doc.realm, null);
55            }
56            """,
57        "experts_coins_by_name":
58            """
59            function(doc, meta) {
60                if (doc.category === 2) {
61                    emit(doc.name, doc.coins);
62                }
63            }
64            """,
65        "name_by_category_and_and_coins":
66            """
67            function(doc, meta) {
68                emit([doc.category, doc.coins], doc.name);
69            }
70            """,
71        "name_and_email_by_category_and_and_coins":
72            """
73            function(doc, meta) {
74                emit([doc.category, doc.coins], [doc.name, doc.email]);
75            }
76            """,
77        "achievements_by_category_and_and_coins":
78            """
79            function(doc, meta) {
80                emit([doc.category, doc.coins], doc.achievements);
81            }
82            """,
83        "id_by_realm_and_coins":
84            """
85            function(doc, meta) {
86                emit([doc.realm, doc.coins], null)
87            }
88            """,
89        "name_and_email_by_realm_and_coins":
90            """
91            function(doc, meta) {
92                emit([doc.realm, doc.coins], [doc.name, doc.email]);
93            }
94            """,
95        "experts_id_by_realm_and_coins":
96            """
97            function(doc, meta) {
98                if (doc.category === 2) {
99                    emit([doc.realm, doc.coins], null);
100                }
101            }
102            """
103    }
104
105    baseq = '/{bucket}/_design/{ddoc}/_view/{view}'
106
107    queries = {
108        "id_by_city":
109            baseq + '?key="{{city}}"&limit=30',
110        "name_and_email_by_city":
111            baseq + '?key="{{city}}"&limit=30',
112        "id_by_realm":
113            baseq + '?startkey="{{realm}}"&limit=30',
114        "experts_coins_by_name":
115            baseq + '?startkey="{{name}}"&descending=true&limit=30',
116        "name_by_category_and_and_coins":
117            baseq + '?startkey=[{{category}},0]&endkey=[{{category}},{{coins}}]&limit=30',
118        "name_and_email_by_category_and_and_coins":
119            baseq + '?startkey=[{{category}},0]&endkey=[{{category}},{{coins}}]&limit=30',
120        "achievements_by_category_and_and_coins":
121            baseq + '?startkey=[{{category}},0]&endkey=[{{category}},{{coins}}]&limit=30',
122        "id_by_realm_and_coins":
123            baseq + '?startkey=["{{realm}}",{{coins}}]&endkey=["{{realm}}",10000]&limit=30',
124        "name_and_email_by_realm_and_coins":
125            baseq + '?startkey=["{{realm}}",{{coins}}]&endkey=["{{realm}}",10000]&limit=30',
126        "experts_id_by_realm_and_coins":
127            baseq + '?startkey=["{{realm}}",{{coins}}]&endkey=["{{realm}}",10000]&limit=30'
128    }
129
130    queries_by_type = {  # 45%/30%/25%
131        "id_by_city": 9,
132        "name_and_email_by_category_and_and_coins": 6,
133        "id_by_realm_and_coins": 5,
134        "name_and_email_by_city": 9,
135        "name_by_category_and_and_coins": 6,
136        "experts_id_by_realm_and_coins": 5,
137        "id_by_realm": 9,
138        "achievements_by_category_and_and_coins": 6,
139        "name_and_email_by_realm_and_coins": 5,
140        "experts_coins_by_name": 9
141    }
142
143    def generate_ddocs(self, pattern=None, options=None):
144        """Generate dictionary with design documents and views.
145        Pattern looks like:
146            [8, 8, 8] -- 8 ddocs (8 views, 8 views, 8 views)
147            [2, 2, 4] -- 3 ddocs (2 views, 2 views, 4 views)
148            [8] -- 1 ddoc (8 views)
149            [1, 1, 1, 1] -- 4 ddocs (1 view per ddoc)
150        """
151        if filter(lambda v: v > 10, pattern):
152            raise Exception("Maximum 10 views per ddoc allowed")
153        if len(pattern) > 10:
154            raise Exception("Maximum 10 design documents allowed")
155
156        ddocs = dict()
157        for number_of_views in pattern:
158            ddoc_name = self.ddocs.next()
159            ddocs[ddoc_name] = {'views': {}}
160            for index_of_view in xrange(number_of_views):
161                view_name = self.views.next()
162                map = self.maps[view_name]
163                ddocs[ddoc_name]['views'][view_name] = {'map': map}
164            if options:
165                ddocs[ddoc_name]["options"] = options
166        self.ddocs.reset()
167        self.views.reset()
168        return ddocs
169
170    def _get_query(self, bucket, ddoc, view, stale):
171        """Generate template-based query"""
172        template = self.queries[view]
173        query = template.format(bucket=bucket, ddoc=ddoc, view=view)
174        if stale == "ok":
175            query += "&stale=ok"
176        elif stale == "false":
177            query += "&stale=false"
178        return (query, ) * self.queries_by_type[view]
179
180    def generate_queries(self, ddocs, bucket='default', stale="update_after"):
181        """Generate string from permuted queries"""
182        # Generate list of queries
183        queries = (self._get_query(bucket, ddoc, view, stale)
184                   for ddoc, ddoc_definition in ddocs.iteritems()
185                   for view in ddoc_definition["views"])
186        queries = [query for query_group in queries for query in query_group]
187
188        # Deterministic shuffle
189        random.seed("".join(queries))
190        random.shuffle(queries)
191
192        # Joined queries
193        return ';'.join(queries)
194