summaryrefslogtreecommitdiff
path: root/src/librygel-db/database-cursor.vala
blob: 682f7cfc8d4bdddc690946a95d054b63151377dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/*
 * Copyright (C) 2011 Jens Georg <mail@jensge.org>.
 *
 * Author: Jens Georg <mail@jensge.org>
 *
 * This file is part of Rygel.
 *
 * Rygel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Rygel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using Sqlite;

public class Rygel.Database.Cursor : Object {
    private Statement statement;
    private int current_state = -1;
    private bool dirty = true;
    private unowned Sqlite.Database db;

    /**
     * Prepare a SQLite statement from a SQL string
     *
     * If @arguments is non-null, it will call bind()
     *
     * @param db SQLite database this cursor belongs to
     * @param sql statement to execute
     * @param arguments array of values to bind to the SQL statement or null if
     * none
     */
    public Cursor (Sqlite.Database   db,
                   string            sql,
                   GLib.Value[]?     arguments) throws DatabaseError {
        this.db = db;

        this.throw_if_code_is_error (db.prepare_v2 (sql,
                                                    -1,
                                                    out this.statement,
                                                    null));
        if (arguments == null) {
            return;
        }

        this.bind (arguments);
    }

    /**
     * Bind new values to a cursor.
     *
     * The cursor will be reset.
     *
     * This function uses the type of the GValue passed in values to determine
     * which _bind function to use.
     *
     * Supported types are: int, long, int64, uint64, string and pointer.
     * Note: The only pointer supported is the null pointer as provided by
     * Database.@null. This is a special value to bind a column to NULL
     * @param arguments array of values to bind to the SQL statement or null if
     * none
     */
    public void bind (GLib.Value[]? arguments) throws DatabaseError {
        this.statement.reset ();
        this.dirty = true;
        this.current_state = -1;

        for (var i = 1; i <= arguments.length; ++i) {
            unowned GLib.Value current_value = arguments[i - 1];

            if (current_value.holds (typeof (int))) {
                statement.bind_int (i, current_value.get_int ());
            } else if (current_value.holds (typeof (int64))) {
                statement.bind_int64 (i, current_value.get_int64 ());
            } else if (current_value.holds (typeof (uint64))) {
                statement.bind_int64 (i, (int64) current_value.get_uint64 ());
            } else if (current_value.holds (typeof (long))) {
                statement.bind_int64 (i, current_value.get_long ());
            } else if (current_value.holds (typeof (uint))) {
                statement.bind_int64 (i, current_value.get_uint ());
            } else if (current_value.holds (typeof (string))) {
                statement.bind_text (i, current_value.get_string ());
            } else if (current_value.holds (typeof (void *))) {
                if (current_value.peek_pointer () == null) {
                    statement.bind_null (i);
                } else {
                    assert_not_reached ();
                }
            } else {
                var type = current_value.type ();
                warning (_("Unsupported type %s"), type.name ());
                assert_not_reached ();
            }

            if (this.db.errcode () != Sqlite.OK) {
                throw new DatabaseError.BIND ("Failed to bind value %d in %s: %s",
                                              i,
                                              this.statement.sql (),
                                              this.db.errmsg ());
            }
        }
    }

    /**
     * Check if the cursor has more rows left
     *
     * @return true if more rows left, false otherwise
     */
    public bool has_next () throws DatabaseError {
        if (this.dirty) {
            this.current_state = this.statement.step ();
            this.dirty = false;
        }

        this.throw_if_code_is_error (this.current_state);

        return this.current_state == Sqlite.ROW || this.current_state == -1;
    }

    /**
     * Get the next row of this cursor.
     *
     * This function uses pointers instead of unowned because var doesn't work
     * with unowned.
     *
     * @return a pointer to the current row
     */
    public Statement* next () throws DatabaseError {
        this.has_next ();
        this.throw_if_code_is_error (this.current_state);
        this.dirty = true;

        return this.statement;
    }

    // convenience functions for "foreach"

    /**
     * Return an iterator to the cursor to use with foreach
     *
     * @return an iterator wrapping the cursor
     */
    public Iterator iterator () {
        return new Iterator (this);
    }

    public class Iterator {
        public Cursor cursor;

        public Iterator (Cursor cursor) {
            this.cursor = cursor;
        }

        public bool next () throws DatabaseError {
            return this.cursor.has_next ();
        }

        public unowned Statement @get () throws DatabaseError {
            return this.cursor.next ();
        }
    }

    /**
     * Convert a SQLite return code to a DatabaseError
     */
    protected void throw_if_code_is_error (int sqlite_error)
                                           throws DatabaseError {
        switch (sqlite_error) {
            case Sqlite.OK:
            case Sqlite.DONE:
            case Sqlite.ROW:
                return;
            default:
                throw new DatabaseError.SQLITE_ERROR
                                        ("SQLite error %d: %s",
                                         sqlite_error,
                                         this.db.errmsg ());
        }
    }

    /**
     * Check if the last operation on the database was an error
     */
    protected void throw_if_db_has_error () throws DatabaseError {
        this.throw_if_code_is_error (this.db.errcode ());
    }
}