]> arthur.barton.de Git - netatalk.git/blob - etc/spotlight/sparql_parser.y
Spotlight: use async Tracker SPARQL API
[netatalk.git] / etc / spotlight / sparql_parser.y
1 %{
2   #include <atalk/standards.h>
3
4   #include <stdbool.h>
5   #include <stdio.h>
6   #include <string.h>
7   #include <time.h>
8
9   #include <gio/gio.h>
10
11   #include <atalk/talloc.h>
12   #include <atalk/logger.h>
13   #include <atalk/errchk.h>
14   #include <atalk/spotlight.h>
15
16   #include "sparql_map.h"
17
18   struct yy_buffer_state;
19   typedef struct yy_buffer_state *YY_BUFFER_STATE;
20   extern int yylex (void);
21   extern void yyerror (char const *);
22   extern void *yyterminate(void);
23   extern YY_BUFFER_STATE yy_scan_string( const char *str);
24   extern void yy_delete_buffer ( YY_BUFFER_STATE buffer );
25
26   /* forward declarations */
27   static const char *map_expr(const char *attr, char op, const char *val);
28   static const char *map_daterange(const char *dateattr, time_t date1, time_t date2);
29   static time_t isodate2unix(const char *s);
30  
31  /* global vars, eg needed by the lexer */
32   slq_t *ssp_slq;
33
34   /* local vars */
35   static gchar *ssp_result;
36   static char sparqlvar;
37   static char *result_limit;
38 %}
39
40 %code provides {
41   #define SPRAW_TIME_OFFSET 978307200
42   extern int map_spotlight_to_sparql_query(slq_t *slq, gchar **sparql_result);
43   extern slq_t *ssp_slq;
44 }
45
46 %union {
47     int ival;
48     const char *sval;
49     bool bval;
50     time_t tval;
51 }
52
53 %expect 5
54 %error-verbose
55
56 %type <sval> match expr line function
57 %type <tval> date
58
59 %token <sval> WORD
60 %token <bval> BOOL
61 %token FUNC_INRANGE
62 %token DATE_ISO
63 %token OBRACE CBRACE EQUAL UNEQUAL GT LT COMMA QUOTE
64 %left AND
65 %left OR
66 %%
67
68 input:
69 /* empty */
70 | input line
71 ;
72      
73 line:
74 expr                           {
75     if (ssp_slq->slq_result_limit)
76         result_limit = talloc_asprintf(ssp_slq, "LIMIT %ld", ssp_slq->slq_result_limit);
77     else
78         result_limit = "";
79     ssp_result = talloc_asprintf(ssp_slq,
80                                  "SELECT ?url WHERE "
81                                  "{ %s . ?obj nie:url ?url . FILTER(tracker:uri-is-descendant('file://%s/', ?url)) } %s",
82                                  $1, ssp_slq->slq_vol->v_path, result_limit);
83     $$ = ssp_result;
84 }
85 ;
86
87 expr:
88 BOOL {
89         /*
90          * We can't properly handle these in expressions, fortunately this
91          * is probably only ever used by OS X as sole element in an
92          * expression ie "False" (when Finder window selected our share
93          * but no search string entered yet). Packet traces showed that OS
94          * X Spotlight server then returns a failure (ie -1) which is what
95          * we do here too by calling YYABORT.
96          */
97         YYABORT;
98 }
99 | match OR match                 {
100     if ($1 == NULL || $3 == NULL)
101         YYABORT;
102     if (strcmp($1, $3) != 0)
103         $$ = talloc_asprintf(ssp_slq, "{ %s } UNION { %s }", $1, $3);
104     else
105         $$ = talloc_asprintf(ssp_slq, "%s", $1);
106 }
107 | match                        {$$ = $1; if ($$ == NULL) YYABORT;}
108 | function                     {$$ = $1;}
109 | OBRACE expr CBRACE           {$$ = talloc_asprintf(ssp_slq, "%s", $2);}
110 | expr AND expr                {
111     if (!ssp_slq->slq_allow_expr) {
112         yyerror("Spotlight queries with logic expressions are disabled");
113         YYABORT;
114     }
115     $$ = talloc_asprintf(ssp_slq, "%s . %s", $1, $3);
116 }
117 | expr OR expr                 {
118     if (!ssp_slq->slq_allow_expr) {
119         yyerror("Spotlight queries with logic expressions are disabled");
120         YYABORT;
121     }
122     if (strcmp($1, $3) != 0)
123         $$ = talloc_asprintf(ssp_slq, "{ %s } UNION { %s }", $1, $3);
124     else
125         $$ = talloc_asprintf(ssp_slq, "%s", $1);
126 }
127 ;
128
129 match:
130 WORD EQUAL QUOTE WORD QUOTE     {$$ = map_expr($1, '=', $4);}
131 | WORD UNEQUAL QUOTE WORD QUOTE {$$ = map_expr($1, '!', $4);}
132 | WORD LT QUOTE WORD QUOTE      {$$ = map_expr($1, '<', $4);}
133 | WORD GT QUOTE WORD QUOTE      {$$ = map_expr($1, '>', $4);}
134 | WORD EQUAL QUOTE WORD QUOTE WORD    {$$ = map_expr($1, '=', $4);}
135 | WORD UNEQUAL QUOTE WORD QUOTE WORD {$$ = map_expr($1, '!', $4);}
136 | WORD LT QUOTE WORD QUOTE WORD     {$$ = map_expr($1, '<', $4);}
137 | WORD GT QUOTE WORD QUOTE WORD     {$$ = map_expr($1, '>', $4);}
138 ;
139
140 function:
141 FUNC_INRANGE OBRACE WORD COMMA date COMMA date CBRACE {$$ = map_daterange($3, $5, $7);}
142 ;
143
144 date:
145 DATE_ISO OBRACE WORD CBRACE    {$$ = isodate2unix($3);}
146 | WORD                         {$$ = atoi($1) + SPRAW_TIME_OFFSET;}
147 ;
148
149 %%
150
151 static time_t isodate2unix(const char *s)
152 {
153     struct tm tm;
154
155     if (strptime(s, "%Y-%m-%dT%H:%M:%SZ", &tm) == NULL)
156         return (time_t)-1;
157     return mktime(&tm);
158 }
159
160 static const char *map_daterange(const char *dateattr, time_t date1, time_t date2)
161 {
162     EC_INIT;
163     char *result = NULL;
164     struct spotlight_sparql_map *p;
165     struct tm *tmp;
166     char buf1[64], buf2[64];
167
168     EC_NULL_LOG( tmp = localtime(&date1) );
169     strftime(buf1, sizeof(buf1), "%Y-%m-%dT%H:%M:%SZ", tmp);
170     EC_NULL_LOG( tmp = localtime(&date2) );
171     strftime(buf2, sizeof(buf2), "%Y-%m-%dT%H:%M:%SZ", tmp);
172
173     for (p = spotlight_sparql_map; p->ssm_spotlight_attr; p++) {
174         if (strcmp(dateattr, p->ssm_spotlight_attr) == 0) {
175             result = talloc_asprintf(ssp_slq,
176                                      "?obj %s ?%c FILTER (?%c > '%s' && ?%c < '%s')",
177                                      p->ssm_sparql_attr,
178                                      sparqlvar,
179                                      sparqlvar,
180                                      buf1,
181                                      sparqlvar,
182                                      buf2);
183             sparqlvar++;
184             break;
185         }
186     }
187
188 EC_CLEANUP:
189     if (ret != 0)
190         return NULL;
191     return result;
192 }
193
194 static char *map_type_search(const char *attr, char op, const char *val)
195 {
196     char *result = NULL;
197     const char *sparqlAttr;
198
199     for (struct MDTypeMap *p = MDTypeMap; p->mdtm_value; p++) {
200         if (strcmp(p->mdtm_value, val) == 0) {
201             switch (p->mdtm_type) {
202             case kMDTypeMapRDF:
203                 sparqlAttr = "rdf:type";
204                 break;
205             case kMDTypeMapMime:
206                 sparqlAttr = "nie:mimeType";
207                 break;
208             default:
209                 return NULL;
210             }
211             result = talloc_asprintf(ssp_slq, "?obj %s '%s'",
212                                      sparqlAttr,
213                                      p->mdtm_sparql);
214             break;
215         }
216     }
217     return result;
218 }
219
220 static const char *map_expr(const char *attr, char op, const char *val)
221 {
222     EC_INIT;
223     char *result = NULL;
224     struct spotlight_sparql_map *p;
225     time_t t;
226     struct tm *tmp;
227     char buf1[64];
228     bstring q = NULL, search = NULL, replace = NULL;
229
230     for (p = spotlight_sparql_map; p->ssm_spotlight_attr; p++) {
231         if (p->ssm_enabled && (strcmp(p->ssm_spotlight_attr, attr) == 0)) {
232             if (p->ssm_type != ssmt_type && p->ssm_sparql_attr == NULL) {
233                 yyerror("unsupported Spotlight attribute");
234                 EC_FAIL;
235             }
236             switch (p->ssm_type) {
237             case ssmt_bool:
238                 result = talloc_asprintf(ssp_slq, "?obj %s '%s'", p->ssm_sparql_attr, val);
239                 break;
240             case ssmt_num:
241                 result = talloc_asprintf(ssp_slq, "?obj %s ?%c FILTER(?%c %c%c '%s')",
242                                          p->ssm_sparql_attr,
243                                          sparqlvar,
244                                          sparqlvar,
245                                          op,
246                                          op == '!' ? '=' : ' ', /* append '=' to '!' */
247                                          val);
248                 sparqlvar++;
249                 break;
250             case ssmt_str:
251                 q = bformat("^%s$", val);
252                 search = bfromcstr("*");
253                 replace = bfromcstr(".*");
254                 bfindreplace(q, search, replace, 0);
255                 result = talloc_asprintf(ssp_slq, "?obj %s ?%c FILTER(regex(?%c, '%s'))",
256                                          p->ssm_sparql_attr,
257                                          sparqlvar,
258                                          sparqlvar,
259                                          bdata(q));
260                 sparqlvar++;
261                 break;
262             case ssmt_fts:
263                 result = talloc_asprintf(ssp_slq, "?obj %s '%s'", p->ssm_sparql_attr, val);
264                 break;
265             case ssmt_date:
266                 t = atoi(val) + SPRAW_TIME_OFFSET;
267                 EC_NULL( tmp = localtime(&t) );
268                 strftime(buf1, sizeof(buf1), "%Y-%m-%dT%H:%M:%SZ", tmp);
269                 result = talloc_asprintf(ssp_slq, "?obj %s ?%c FILTER(?%c %c '%s')",
270                                          p->ssm_sparql_attr,
271                                          sparqlvar,
272                                          sparqlvar,
273                                          op,
274                                          buf1);
275                 sparqlvar++;
276                 break;
277             case ssmt_type:
278                 result = map_type_search(attr, op, val);
279                 break;
280             default:
281                 EC_FAIL;
282             }
283             break;
284         }
285     }
286
287 EC_CLEANUP:
288     if (ret != 0)
289         result = NULL;
290     if (q)
291         bdestroy(q);
292     if (search)
293         bdestroy(search);
294     if (replace)
295         bdestroy(replace);
296     return result;
297 }
298
299 void yyerror(const char *str)
300 {
301 #ifdef MAIN
302     printf("yyerror: %s\n", str);
303 #else
304     LOG(log_error, logtype_sl, "yyerror: %s", str);
305 #endif
306 }
307  
308 int yywrap()
309 {
310     return 1;
311
312
313 /**
314  * Map a Spotlight RAW query string to a SPARQL query string
315  *
316  * @param[in]     slq            Spotlight query handle
317  * @param[out]    sparql_result  Mapped SPARQL query, string is allocated in
318  *                               talloc context of slq
319  * @return        0 on success, -1 on error
320  **/
321 int map_spotlight_to_sparql_query(slq_t *slq, gchar **sparql_result)
322 {
323     EC_INIT;
324     YY_BUFFER_STATE s = NULL;
325     ssp_result = NULL;
326
327     ssp_slq = slq;
328     s = yy_scan_string(slq->slq_qstring);
329     sparqlvar = 'a';
330
331     EC_ZERO( yyparse() );
332
333 EC_CLEANUP:
334     if (s)
335         yy_delete_buffer(s);
336     if (ret == 0)
337         *sparql_result = ssp_result;
338     else
339         *sparql_result = NULL;
340     EC_EXIT;
341 }
342
343 #ifdef MAIN
344 int main(int argc, char **argv)
345 {
346     int ret;
347     YY_BUFFER_STATE s;
348
349     if (argc != 2) {
350         printf("usage: %s QUERY\n", argv[0]);
351         return 1;
352     }
353
354     ssp_slq = talloc_zero(NULL, slq_t);
355     struct vol *vol = talloc_zero(ssp_slq, struct vol);
356     vol->v_path = "/Volumes/test";
357     ssp_slq->slq_vol = vol;
358     ssp_slq->slq_allow_expr = true;
359     sparqlvar = 'a';
360
361     s = yy_scan_string(argv[1]);
362
363     ret = yyparse();
364
365     yy_delete_buffer(s);
366
367     if (ret == 0)
368         printf("SPARQL: %s\n", ssp_result ? ssp_result : "(empty)");
369
370     return 0;
371
372 #endif