source: trunk/base/src/cregistry/file.c @ 128274

Last change on this file since 128274 was 128274, checked in by cal@…, 5 years ago

base: cregistry: Improve query performance in file.c (all those queries would use the wrong index on Yosemite)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 13.1 KB
Line 
1/*
2 * file.c
3 * vim:tw=80:expandtab
4 * $Id: file.c 128274 2014-11-17 22:34:04Z cal@macports.org $
5 *
6 * Copyright (c) 2011 Clemens Lang <cal@macports.org>
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#if HAVE_CONFIG_H
31#include <config.h>
32#endif
33
34#include "file.h"
35#include "util.h"
36#include "registry.h"
37#include "sql.h"
38
39#include <sqlite3.h>
40#include <stdlib.h>
41#include <string.h>
42
43/**
44 * Converts a `sqlite3_stmt` into a `reg_file`. The first column of the stmt's
45 * row must be the id of a file; the second column must be the path of a file;
46 * the third either `SQLITE_NULL` or the address of the entry in memory.
47 *
48 * @param [in] userdata sqlite3 database
49 * @param [out] file    file described by `stmt`
50 * @param [in] stmt     `sqlite3_stmt` with appropriate columns
51 * @param [out] errPtr  unused
52 * @return              true if success; false if failure
53 */
54static int reg_stmt_to_file(void* userdata, void** file, void* stmt,
55        void* calldata UNUSED, reg_error* errPtr UNUSED) {
56    int is_new;
57    reg_registry* reg = (reg_registry*)userdata;
58    reg_file_pk key;
59    Tcl_HashEntry* hash;
60    char* hashkey;
61
62    key.id = sqlite3_column_int64(stmt, 0);
63    key.path = strdup((const char*) sqlite3_column_text(stmt, 1));
64    if (!key.path) {
65        return 0;
66    }
67
68    hashkey = sqlite3_mprintf("%lld:%s", key.id, key.path);
69    if (!hashkey) {
70        free(key.path);
71        return 0;
72    }
73    hash = Tcl_CreateHashEntry(&reg->open_files,
74            hashkey, &is_new);
75    sqlite3_free(hashkey);
76
77    if (is_new) {
78        reg_file* f = malloc(sizeof(reg_file));
79        if (!f) {
80            free(key.path);
81            return 0;
82        }
83        f->reg = reg;
84        f->key = key;
85        f->proc = NULL;
86        *file = f;
87        Tcl_SetHashValue(hash, f);
88    } else {
89        free(key.path);
90        *file = Tcl_GetHashValue(hash);
91    }
92    return 1;
93}
94
95/**
96 * Opens an existing file in the registry.
97 *
98 * @param [in] reg      registry to open entry in
99 * @param [in] id       port id in the dabatase
100 * @param [in] name     file path in the database
101 * @param [out] errPtr  on error, a description of the error that occures
102 * @return              the file if success, NULL if failure
103 */
104reg_file* reg_file_open(reg_registry* reg, char* id, char* name,
105        reg_error* errPtr) {
106    sqlite3_stmt* stmt = NULL;
107    reg_file* file = NULL;
108    char* query = "SELECT id, path FROM registry.files "
109#if SQLITE_VERSION_NUMBER >= 3006004
110        /* if the version of SQLite supports it force the usage of the index on
111         * path, rather than the one on id which has a lot less discriminative
112         * power and leads to very slow queries. This is needed for the new
113         * query planner introduced in 3.8.0 which would not use the correct
114         * index automatically. */
115        "INDEXED BY file_path "
116#endif
117        "WHERE id=? AND path=?";
118    int lower_bound = 0;
119
120    if ((sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
121            && (sqlite3_bind_text(stmt, 1, id, -1, SQLITE_STATIC)
122                == SQLITE_OK)
123            && (sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC)
124                == SQLITE_OK)) {
125        int r;
126        do {
127            r = sqlite3_step(stmt);
128            switch (r) {
129                case SQLITE_ROW:
130                    reg_stmt_to_file(reg, (void**)&file, stmt, &lower_bound,
131                            errPtr);
132                    break;
133                case SQLITE_DONE:
134                    errPtr->code = REG_NOT_FOUND;
135                    errPtr->description = sqlite3_mprintf("no matching file found for: "
136                            "id=%s, name=%s", id, name);
137                    errPtr->free = (reg_error_destructor*) sqlite3_free;
138                    break;
139                case SQLITE_BUSY:
140                    continue;
141                default:
142                    reg_sqlite_error(reg->db, errPtr, query);
143                    break;
144            }
145        } while (r == SQLITE_BUSY);
146    } else {
147        reg_sqlite_error(reg->db, errPtr, query);
148    }
149    if (stmt) {
150        sqlite3_finalize(stmt);
151    }
152    return file;
153}
154
155/**
156 * Type-safe version of `reg_all_objects` for `reg_file`.
157 *
158 * @param [in] reg       registry to select entries from
159 * @param [in] query     the select query to execute
160 * @param [in] query_len length of the query (or -1 for automatic)
161 * @param [out] objects  the files selected
162 * @param [out] errPtr   on error, a description of the error that occurred
163 * @return               the number of entries if success; negative if failure
164 */
165static int reg_all_files(reg_registry* reg, char* query, int query_len,
166        reg_file*** objects, reg_error* errPtr) {
167    int lower_bound = 0;
168    return reg_all_objects(reg, query, query_len, (void***)objects,
169            reg_stmt_to_file, &lower_bound, NULL, errPtr);
170}
171
172/**
173 * Searches the registry for files for which each key's value is equal to the
174 * given value. To find all files, pass a key_count of 0.
175 *
176 * Bad keys should cause sqlite3 errors but not permit SQL injection attacks.
177 * Pass it good keys anyway.
178 *
179 * @param [in] reg       registry to search in
180 * @param [in] keys      a list of keys to search by
181 * @param [in] vals      a list of values to search by, matching keys
182 * @param [in] strats    a list of strategies to use when searching
183 * @param [in] key_count the number of key/value pairs passed
184 * @param [out] files    a list of matching files
185 * @param [out] errPtr   on error, a description of the error that occurred
186 * @return               the number of entries if success; false if failure
187 */
188int reg_file_search(reg_registry* reg, char** keys, char** vals, int* strats,
189        int key_count, reg_file*** files, reg_error* errPtr) {
190    int i;
191    char* kwd = " WHERE ";
192    char* query;
193    size_t query_len, query_space;
194    int result;
195
196    /* build the query */
197    query = strdup("SELECT id, path FROM registry.files");
198    if (!query) {
199        return -1;
200    }
201    query_len = query_space = strlen(query);
202
203    for (i = 0; i < key_count; i++) {
204        char* op;
205        char* cond;
206
207        /* get the strategy */
208        if ((op = reg_strategy_op(strats[i], errPtr)) == NULL) {
209            free(query);
210            return -1;
211        }
212
213        cond = sqlite3_mprintf(op, keys[i], vals[i]);
214        if (!cond || !reg_strcat(&query, &query_len, &query_space, kwd)
215            || !reg_strcat(&query, &query_len, &query_space, cond)) {
216            free(query);
217            return -1;
218        }
219        sqlite3_free(cond);
220        kwd = " AND ";
221    }
222
223    /* do the query */
224    result = reg_all_files(reg, query, -1, files, errPtr);
225    free(query);
226    return result;
227}
228
229/**
230 * Gets a named property of a file. That property can be set using
231 * `reg_file_propset`. The property named must be one that exists in the table
232 * and must not be one with internal meaning such as `id`.
233 *
234 * @param [in] file    file to get property from
235 * @param [in] key     property to get
236 * @param [out] value  the value of the property
237 * @param [out] errPtr on error, a description of the error that occurred
238 * @return             true if success; false if failure
239 */
240int reg_file_propget(reg_file* file, char* key, char** value,
241        reg_error* errPtr) {
242    reg_registry* reg = file->reg;
243    int result = 0;
244    sqlite3_stmt* stmt = NULL;
245    char* query;
246    const char *text;
247    query = sqlite3_mprintf(
248            "SELECT %q FROM registry.files "
249#if SQLITE_VERSION_NUMBER >= 3006004
250            /* if the version of SQLite supports it force the usage of the index
251             * on path, rather than the one on id which has a lot less
252             * discriminative power and leads to very slow queries. This is
253             * needed for the new query planner introduced in 3.8.0 which would
254             * not use the correct index automatically. */
255            "INDEXED BY file_path "
256#endif
257            "WHERE id=%lld AND path='%q'", key, file->key.id, file->key.path);
258    if (sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
259        int r;
260        do {
261            r = sqlite3_step(stmt);
262            switch (r) {
263                case SQLITE_ROW:
264                    text = (const char*)sqlite3_column_text(stmt, 0);
265                    if (text) {
266                        *value = strdup(text);
267                        result = 1;
268                    } else {
269                        reg_sqlite_error(reg->db, errPtr, query);
270                    }
271                    break;
272                case SQLITE_DONE:
273                    errPtr->code = REG_INVALID;
274                    errPtr->description = "an invalid file was passed";
275                    errPtr->free = NULL;
276                    break;
277                case SQLITE_BUSY:
278                    continue;
279                default:
280                    reg_sqlite_error(reg->db, errPtr, query);
281                    break;
282            }
283        } while (r == SQLITE_BUSY);
284    } else {
285        reg_sqlite_error(reg->db, errPtr, query);
286    }
287    if (stmt) {
288        sqlite3_finalize(stmt);
289    }
290    sqlite3_free(query);
291    return result;
292}
293
294/**
295 * Sets a named property of a file. That property can be later retrieved using
296 * `reg_file_propget`. The property named must be one that exists in the table
297 * and must not be one with internal meaning such as `id`.
298 *
299 * @param [in] file    file to set property for
300 * @param [in] key     property to set
301 * @param [in] value   the desired value of the property
302 * @param [out] errPtr on error, a description of the error that occurred
303 * @return             true if success; false if failure
304 */
305int reg_file_propset(reg_file* file, char* key, char* value,
306        reg_error* errPtr) {
307    reg_registry* reg = file->reg;
308    int result = 0;
309    sqlite3_stmt* stmt = NULL;
310    char* query;
311    query = sqlite3_mprintf(
312            "UPDATE registry.files "
313#if SQLITE_VERSION_NUMBER >= 3006004
314            /* if the version of SQLite supports it force the usage of the index
315             * on path, rather than the one on id which has a lot less
316             * discriminative power and leads to very slow queries. This is
317             * needed for the new query planner introduced in 3.8.0 which would
318             * not use the correct index automatically. */
319            "INDEXED BY file_path "
320#endif
321            "SET %q = '%q' WHERE id=%lld AND path='%q'", key, value, file->key.id, file->key.path);
322    if (sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
323        int r;
324        do {
325            r = sqlite3_step(stmt);
326            switch (r) {
327                case SQLITE_DONE:
328                    result = 1;
329                    break;
330                case SQLITE_BUSY:
331                    continue;
332                default:
333                    if (sqlite3_reset(stmt) == SQLITE_CONSTRAINT) {
334                        errPtr->code = REG_CONSTRAINT;
335                        errPtr->description = "a constraint was disobeyed";
336                        errPtr->free = NULL;
337                    } else {
338                        reg_sqlite_error(reg->db, errPtr, query);
339                    }
340                    break;
341            }
342        } while (r == SQLITE_BUSY);
343    } else {
344        reg_sqlite_error(reg->db, errPtr, query);
345    }
346    if (stmt) {
347        sqlite3_finalize(stmt);
348    }
349    sqlite3_free(query);
350    return result;
351}
352
353/**
354 * Fetches a list of all open files
355 *
356 * @param [in] reg      registry to fetch files from
357 * @param [out] files   a list of open files
358 * @return              the number of open entries, -1 on error
359 */
360int reg_all_open_files(reg_registry* reg, reg_file*** files) {
361    reg_file* file;
362    int file_count = 0;
363    int file_space = 10;
364    Tcl_HashEntry* hash;
365    Tcl_HashSearch search;
366    *files = malloc(file_space * sizeof(reg_file*));
367    if (!*files) {
368        return -1;
369    }
370    for (hash = Tcl_FirstHashEntry(&reg->open_files, &search); hash != NULL;
371            hash = Tcl_NextHashEntry(&search)) {
372        file = Tcl_GetHashValue(hash);
373        if (!reg_listcat((void***)files, &file_count, &file_space, file)) {
374            free(*files);
375            return -1;
376        }
377    }
378    return file_count;
379}
380
Note: See TracBrowser for help on using the repository browser.