Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 382 lines (317 sloc) 8.585 kb
88f6383 @winton First commit
authored
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdbool.h>
5
6 #include "hiredis/hiredis.h"
7 #include "uthash/src/uthash.h"
8
9 // Column vars
10 char columns[][20] = {
11 "referrer",
12 "time",
13 "tags",
14 "article_id",
15 "keywords",
f257974 @winton Adding query field for TNT writer
authored
16 "type",
17 "tnt"
88f6383 @winton First commit
authored
18 };
19 unsigned int columns_count = sizeof(columns) / sizeof(columns[0]);
20
21 // Hash struct
22 struct hash_struct {
23 char *field;
24 int count;
25 UT_hash_handle hh;
26 };
27 struct hash_struct *hash_record;
28
29 // Query vars
30 struct query {
31 // Struct used?
32 bool used;
33 // Display count or group?
34 bool display_count, display_group;
35 // Query empty?
36 bool query_empty;
43f3326 @winton Support for exclusionary matching
authored
37 // Exclude empty?
38 bool exclude_empty;
39 // Query empty at index?
f257974 @winton Adding query field for TNT writer
authored
40 bool query_empty_at[7];
43f3326 @winton Support for exclusionary matching
authored
41 // Exclude empty at index?
f257974 @winton Adding query field for TNT writer
authored
42 bool exclude_empty_at[7];
88f6383 @winton First commit
authored
43 // Array of query strings (by column)
44 char **query;
43f3326 @winton Support for exclusionary matching
authored
45 // Array of exclusionary query strings (by column)
46 char **exclude;
88f6383 @winton First commit
authored
47 // Column to group by
48 int column;
49 // Match count
50 int count;
51 // Limit results (group)
52 int limit;
53 // Results displayed so far
54 int displayed;
55 // Start value for score
56 int start;
57 // End value for score
58 int finish;
59 // Hash for grouping
60 struct hash_struct *hash;
61 };
f0c58f3 @winton Changing query limit from 20 to 100
authored
62 struct query queries[100];
88f6383 @winton First commit
authored
63
64 // Pass scores into #process_record?
65 bool with_scores = false;
66
67 // Other vars
68 char empty[] = "";
69
70 // Functions
71 int column_to_index(char *key);
72 int count_sort(struct hash_struct *a, struct hash_struct *b);
73 bool hash_add_or_update(int query_index, char *record);
74 bool limit_reached();
75 void process_record(char *record, int score);
76 char **to_fields(char *record);
77 char *xstrtok(char *line, char *delims);
78
79 // Hiredis hack functions
80 static void *createArrayObject(const redisReadTask *task, int elements);
81 static void *createIntegerObject(const redisReadTask *task, long long value);
82 static void *createNilObject(const redisReadTask *task);
83 static redisReply *createReplyObject(int type);
84 static void *createStringObject(const redisReadTask *task, char *str, size_t len);
85 static void freeObject(void *ptr);
86
87 redisReplyObjectFunctions redisExtReplyObjectFunctions = {
88 createStringObject,
89 createArrayObject,
90 createIntegerObject,
91 createNilObject,
92 freeObject
93 };
94
95 int main(int argc, char *argv[]) {
96 // Argv vars
97 char *host = argv[1];
98 int port = atoi(argv[2]);
99 char *cmd = argv[3];
100
101 with_scores = strstr(cmd, "WITHSCORES") ? true : false;
102
103 int x, y, start;
0c5312d @winton Fixing bug with multiple sets of parameters
authored
104 for (x = 0; x < ((argc - 4) / 7); x++) {
105 start = x * 7 + 4;
88f6383 @winton First commit
authored
106
107 queries[x].used = true;
108 queries[x].display_count = false;
109 queries[x].display_group = false;
110 queries[x].query_empty = true;
43f3326 @winton Support for exclusionary matching
authored
111 queries[x].exclude_empty = true;
88f6383 @winton First commit
authored
112 queries[x].count = 0;
113 queries[x].displayed = 0;
114 queries[x].hash = NULL;
115
116 if (strcmp(argv[start], "COUNT") == 0)
117 queries[x].display_count = true;
118
119 if (strcmp(argv[start], "GROUP") == 0)
120 queries[x].display_group = true;
121
122 queries[x].start = atoi(argv[start+1]);
123 queries[x].finish = atoi(argv[start+2]);
124 queries[x].limit = atoi(argv[start+3]);
125 queries[x].column = column_to_index(argv[start+4]);
126 queries[x].query = to_fields(argv[start+5]);
43f3326 @winton Support for exclusionary matching
authored
127 queries[x].exclude = to_fields(argv[start+6]);
88f6383 @winton First commit
authored
128
129 // Query empty?
130 for (y = 0; y < columns_count; y++) {
43f3326 @winton Support for exclusionary matching
authored
131 if (strlen(queries[x].query[y]) > 0) {
88f6383 @winton First commit
authored
132 queries[x].query_empty = false;
43f3326 @winton Support for exclusionary matching
authored
133 queries[x].query_empty_at[y] = false;
134 } else
135 queries[x].query_empty_at[y] = true;
136
137 if (strlen(queries[x].exclude[y]) > 0) {
138 queries[x].exclude_empty = false;
139 queries[x].exclude_empty_at[y] = false;
140 } else
141 queries[x].exclude_empty_at[y] = true;
88f6383 @winton First commit
authored
142 }
143 }
144
145 // Connect to redis
146 struct timeval timeout = { 1, 500000 }; // 1.5 seconds
147 redisContext *c = redisConnectWithTimeout(host, port, timeout);
148 if (c->err) {
149 printf("Connection error: %s\n", c->errstr);
8a22211 @winton Redis failure exits with 0 instead of 1
authored
150 exit(0);
88f6383 @winton First commit
authored
151 }
152
153 // Use custom Hiredis reader functions
154 c->reader->fn = &redisExtReplyObjectFunctions;
155
156 // Execute command
157 redisReply *reply = redisCommand(c, cmd);
158
159 // Display results
160 int q;
f0c58f3 @winton Changing query limit from 20 to 100
authored
161 for (q = 0; q < 100; q++) {
88f6383 @winton First commit
authored
162 if (queries[q].used != true)
163 continue;
164
165 if (queries[q].display_count) {
166 printf("%i\n", queries[q].count);
167 }
168
169 if (queries[q].display_group) {
170 HASH_SORT(queries[q].hash, count_sort);
171 for(hash_record = queries[q].hash; hash_record != NULL; hash_record = hash_record->hh.next) {
172 if (!limit_reached(&queries[q]))
173 printf("%s|%i\n", hash_record->field, hash_record->count);
174 }
175 }
176
177 if (queries[q + 1].used)
178 printf("[END]\n");
179 }
180
181 return 0;
182 }
183
184 int column_to_index(char *key) {
185 int i, index = -1;
186
187 for (i = 0; i < columns_count; ++i)
188 if (strcmp(columns[i], key) == 0)
189 index = i;
190
191 return index;
192 }
193
194 int count_sort(struct hash_struct *a, struct hash_struct *b) {
195 return (b->count - a->count);
196 }
197
198 bool hash_add_or_update(int query_index, char *record) {
199 if (!record)
200 return false;
201 else {
202 HASH_FIND_STR(queries[query_index].hash, record, hash_record);
203 if (hash_record) {
204 hash_record->count++;
205 } else {
206 hash_record = (struct hash_struct*)malloc(sizeof(struct hash_struct));
207 hash_record->field = record;
208 hash_record->count = 1;
209 HASH_ADD_KEYPTR(hh, queries[query_index].hash, hash_record->field, strlen(hash_record->field), hash_record);
210 }
211 return true;
212 }
213 }
214
215 bool limit_reached(struct query *q) {
216 if (q->limit == -1 || q->displayed >= q->limit)
217 return true;
218 q->displayed++;
219 return false;
220 }
221
222 void process_record(char *record, int score) {
2643a7b @winton Revert "Revert "Adding AND matching to queries""
authored
223 bool exclude, match;
88f6383 @winton First commit
authored
224 char *field;
225 char **fields;
226 fields = to_fields(record);
227
228 // For each query
229 int q, x;
f0c58f3 @winton Changing query limit from 20 to 100
authored
230 for (q = 0; q < 100; q++) {
88f6383 @winton First commit
authored
231 // Skip unused query structs
232 if (queries[q].used != true)
233 continue;
2643a7b @winton Revert "Revert "Adding AND matching to queries""
authored
234
88f6383 @winton First commit
authored
235 // Skip if score not within range
236 if (score >= 0) {
a5f66d0 @winton Fixing scores ranges
authored
237 if (queries[q].finish >= 0 && score > queries[q].finish)
88f6383 @winton First commit
authored
238 continue;
a5f66d0 @winton Fixing scores ranges
authored
239 if (queries[q].start >= 0 && score < queries[q].start)
88f6383 @winton First commit
authored
240 continue;
241 }
2643a7b @winton Revert "Revert "Adding AND matching to queries""
authored
242
243 match = true;
224e60f @winton Fixes to get AND matching working
authored
244 if (!queries[q].exclude_empty)
245 // For each column
246 for (x = 0; x < columns_count; x++) {
247 // Exclude matches
248 if (!queries[q].exclude_empty_at[x] && strstr(fields[x], queries[q].exclude[x])) {
249 match = false;
250 break;
251 }
2643a7b @winton Revert "Revert "Adding AND matching to queries""
authored
252 }
224e60f @winton Fixes to get AND matching working
authored
253
254 if (match && !queries[q].query_empty)
255 // For each column
256 for (x = 0; x < columns_count; x++) {
257 // Include matches
258 if (!queries[q].query_empty_at[x] && !strstr(fields[x], queries[q].query[x])) {
259 match = false;
260 break;
261 }
88f6383 @winton First commit
authored
262 }
2643a7b @winton Revert "Revert "Adding AND matching to queries""
authored
263
264 if (match) {
265 if (queries[q].display_count)
266 queries[q].count++;
267 if (queries[q].display_group && queries[q].column > -1) {
268 field = malloc(strlen(fields[queries[q].column]) + 1);
269 strcpy(field, fields[queries[q].column]);
270 if (queries[q].column == 2) { // tag
271 hash_add_or_update(q, xstrtok(field, ","));
272 while (hash_add_or_update(q, xstrtok(NULL, ",")));
273 } else
274 hash_add_or_update(q, field);
275 }
276 }
88f6383 @winton First commit
authored
277 }
278
279 free(fields);
280 free(record);
281 }
282
283 char **to_fields(char *record) {
284 char **fields = (char **)malloc(sizeof(char*) * columns_count);
285 char *field;
286 int i;
287
288 for(i = 0; i < columns_count; ++i) {
289 field = xstrtok(i == 0 ? record : NULL, "|");
290 if (field == NULL)
291 field = empty;
292 fields[i] = field;
293 }
294
295 return fields;
296 }
297
298 char *xstrtok(char *line, char *delims) {
299 static char *saveline = NULL;
300 char *p;
301 int n;
302
303 if(line != NULL)
304 saveline = line;
305
306 if (saveline == NULL || *saveline == '\0')
307 return NULL;
308
309 n = strcspn(saveline, delims);
310 p = saveline;
311
312 saveline += n;
313
314 if (*saveline != '\0')
315 *saveline++ = '\0';
316
317 return p;
318 }
319
320
321 // Hiredis hack functions
322
323 static void *createArrayObject(const redisReadTask *task, int elements) {
324 redisReply *r, *parent;
325
326 r = createReplyObject(REDIS_REPLY_ARRAY);
327 if (r == NULL)
328 return NULL;
329
330 if (elements > 0) {
331 r->element = calloc(elements,sizeof(redisReply*));
332 if (r->element == NULL) {
333 freeReplyObject(r);
334 return NULL;
335 }
336 }
337
338 r->elements = elements;
339
340 if (task->parent) {
341 parent = task->parent->obj;
342 parent->element[task->idx] = r;
343 }
344 return r;
345 }
346
347 static void *createIntegerObject(const redisReadTask *task, long long value) {}
348 static void *createNilObject(const redisReadTask *task) {}
349
350 static redisReply *createReplyObject(int type) {
351 redisReply *r = calloc(1,sizeof(*r));
352
353 if (r == NULL)
354 return NULL;
355
356 r->type = type;
357 return r;
358 }
359
360 static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
361 static char *record = NULL;
362 static int score = -1;
363
364 if (with_scores && record != NULL)
365 score = atoi(str);
366 else {
367 record = malloc(len + 1);
368 memcpy(record, str, len);
369 record[len] = '\0';
370 }
371
372 if (!with_scores || score != -1) {
373 process_record(record, score);
374 record = NULL;
375 score = -1;
376 }
377
378 redisReply *r;
379 return r;
380 }
381
382 static void freeObject(void *ptr) {}
Something went wrong with that request. Please try again.