tcltest: do a better job of cleanup up after tests
[jimtcl.git] / jim-sqlite3.c
blobe5fa30413ae0cd07e7136d75ee20879fd06544df
1 /*
2 * Jim - Sqlite bindings
4 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
19 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * The views and conclusions contained in the software and documentation
31 * are those of the authors and should not be interpreted as representing
32 * official policies, either expressed or implied, of the Jim Tcl Project.
35 #include <stdio.h>
36 #include <string.h>
37 #include <sqlite3.h>
39 #include <jim.h>
41 static void JimSqliteDelProc(Jim_Interp *interp, void *privData)
43 sqlite3 *db = privData;
45 JIM_NOTUSED(interp);
47 sqlite3_close(db);
50 static char *JimSqliteQuoteString(const char *str, int len, int *newLenPtr)
52 int i, newLen, c = 0;
53 const char *s;
54 char *d, *buf;
56 for (i = 0; i < len; i++)
57 if (str[i] == '\'')
58 c++;
59 newLen = len + c;
60 s = str;
61 d = buf = Jim_Alloc(newLen);
62 while (len--) {
63 if (*s == '\'')
64 *d++ = '\'';
65 *d++ = *s++;
67 *newLenPtr = newLen;
68 return buf;
71 static Jim_Obj *JimSqliteFormatQuery(Jim_Interp *interp, Jim_Obj *fmtObjPtr,
72 int objc, Jim_Obj *const *objv)
74 const char *fmt;
75 int fmtLen;
76 Jim_Obj *resObjPtr;
78 fmt = Jim_GetString(fmtObjPtr, &fmtLen);
79 resObjPtr = Jim_NewStringObj(interp, "", 0);
80 while (fmtLen) {
81 const char *p = fmt;
82 char spec[2];
84 while (*fmt != '%' && fmtLen) {
85 fmt++;
86 fmtLen--;
88 Jim_AppendString(interp, resObjPtr, p, fmt - p);
89 if (fmtLen == 0)
90 break;
91 fmt++;
92 fmtLen--; /* skip '%' */
93 if (*fmt != '%') {
94 if (objc == 0) {
95 Jim_FreeNewObj(interp, resObjPtr);
96 Jim_SetResultString(interp, "not enough arguments for all format specifiers", -1);
97 return NULL;
99 else {
100 objc--;
103 switch (*fmt) {
104 case 's':
106 const char *str;
107 char *quoted;
108 int len, newLen;
110 str = Jim_GetString(objv[0], &len);
111 quoted = JimSqliteQuoteString(str, len, &newLen);
112 Jim_AppendString(interp, resObjPtr, quoted, newLen);
113 Jim_Free(quoted);
115 objv++;
116 break;
117 case '%':
118 Jim_AppendString(interp, resObjPtr, "%", 1);
119 break;
120 default:
121 spec[0] = *fmt;
122 spec[1] = '\0';
123 Jim_FreeNewObj(interp, resObjPtr);
124 Jim_SetResultFormatted(interp,
125 "bad field specifier \"%s\", only %%s and %%%% are valid", spec);
126 return NULL;
128 fmt++;
129 fmtLen--;
131 return resObjPtr;
134 /* Calls to [sqlite.open] create commands that are implemented by this
135 * C command. */
136 static int JimSqliteHandlerCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
138 sqlite3 *db = Jim_CmdPrivData(interp);
139 int option;
140 static const char * const options[] = {
141 "close", "query", "lastid", "changes", NULL
143 enum
144 { OPT_CLOSE, OPT_QUERY, OPT_LASTID, OPT_CHANGES };
146 if (argc < 2) {
147 Jim_WrongNumArgs(interp, 1, argv, "method ?args ...?");
148 return JIM_ERR;
150 if (Jim_GetEnum(interp, argv[1], options, &option, "Sqlite method", JIM_ERRMSG) != JIM_OK)
151 return JIM_ERR;
152 /* CLOSE */
153 if (option == OPT_CLOSE) {
154 if (argc != 2) {
155 Jim_WrongNumArgs(interp, 2, argv, "");
156 return JIM_ERR;
158 Jim_DeleteCommand(interp, Jim_String(argv[0]));
159 return JIM_OK;
161 else if (option == OPT_QUERY) {
162 /* QUERY */
163 Jim_Obj *objPtr, *rowsListPtr;
164 sqlite3_stmt *stmt;
165 const char *query, *tail;
166 int columns, rows, len;
167 int retcode = JIM_ERR;
168 Jim_Obj *nullStrObj;
170 if (argc >= 4 && Jim_CompareStringImmediate(interp, argv[2], "-null")) {
171 nullStrObj = argv[3];
172 argv += 2;
173 argc -= 2;
175 else {
176 nullStrObj = Jim_NewEmptyStringObj(interp);
178 Jim_IncrRefCount(nullStrObj);
179 if (argc < 3) {
180 Jim_WrongNumArgs(interp, 2, argv, "query ?args?");
181 goto err;
183 objPtr = JimSqliteFormatQuery(interp, argv[2], argc - 3, argv + 3);
184 if (objPtr == NULL) {
185 return JIM_ERR;
187 query = Jim_GetString(objPtr, &len);
188 Jim_IncrRefCount(objPtr);
189 /* Compile the query into VM code */
190 if (sqlite3_prepare_v2(db, query, len, &stmt, &tail) != SQLITE_OK) {
191 Jim_DecrRefCount(interp, objPtr);
192 Jim_SetResultString(interp, sqlite3_errmsg(db), -1);
193 goto err;
195 Jim_DecrRefCount(interp, objPtr); /* query no longer needed. */
196 /* Build a list of rows (that are lists in turn) */
197 rowsListPtr = Jim_NewListObj(interp, NULL, 0);
198 Jim_IncrRefCount(rowsListPtr);
199 rows = 0;
200 columns = sqlite3_column_count(stmt);
201 while (sqlite3_step(stmt) == SQLITE_ROW) {
202 int i;
204 objPtr = Jim_NewListObj(interp, NULL, 0);
205 for (i = 0; i < columns; i++) {
206 Jim_Obj *vObj = NULL;
208 Jim_ListAppendElement(interp, objPtr,
209 Jim_NewStringObj(interp, sqlite3_column_name(stmt, i), -1));
210 switch (sqlite3_column_type(stmt, i)) {
211 case SQLITE_NULL:
212 vObj = nullStrObj;
213 break;
214 case SQLITE_INTEGER:
215 vObj = Jim_NewIntObj(interp, sqlite3_column_int(stmt, i));
216 break;
217 case SQLITE_FLOAT:
218 vObj = Jim_NewDoubleObj(interp, sqlite3_column_double(stmt, i));
219 break;
220 case SQLITE_TEXT:
221 case SQLITE_BLOB:
222 vObj = Jim_NewStringObj(interp,
223 sqlite3_column_blob(stmt, i), sqlite3_column_bytes(stmt, i));
224 break;
226 Jim_ListAppendElement(interp, objPtr, vObj);
228 Jim_ListAppendElement(interp, rowsListPtr, objPtr);
229 rows++;
231 /* Finalize */
232 if (sqlite3_finalize(stmt) != SQLITE_OK) {
233 Jim_SetResultString(interp, sqlite3_errmsg(db), -1);
235 else {
236 Jim_SetResult(interp, rowsListPtr);
237 retcode = JIM_OK;
239 Jim_DecrRefCount(interp, rowsListPtr);
240 err:
241 Jim_DecrRefCount(interp, nullStrObj);
243 return retcode;
245 else if (option == OPT_LASTID) {
246 if (argc != 2) {
247 Jim_WrongNumArgs(interp, 2, argv, "");
248 return JIM_ERR;
250 Jim_SetResult(interp, Jim_NewIntObj(interp, sqlite3_last_insert_rowid(db)));
251 return JIM_OK;
253 else if (option == OPT_CHANGES) {
254 if (argc != 2) {
255 Jim_WrongNumArgs(interp, 2, argv, "");
256 return JIM_ERR;
258 Jim_SetResult(interp, Jim_NewIntObj(interp, sqlite3_changes(db)));
259 return JIM_OK;
261 return JIM_OK;
264 static int JimSqliteOpenCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
266 sqlite3 *db;
267 char buf[60];
268 int r;
270 if (argc != 2) {
271 Jim_WrongNumArgs(interp, 1, argv, "dbname");
272 return JIM_ERR;
274 r = sqlite3_open(Jim_String(argv[1]), &db);
275 if (r != SQLITE_OK) {
276 Jim_SetResultString(interp, sqlite3_errmsg(db), -1);
277 sqlite3_close(db);
278 return JIM_ERR;
280 /* Create the file command */
281 snprintf(buf, sizeof(buf), "sqlite.handle%ld", Jim_GetId(interp));
282 Jim_CreateCommand(interp, buf, JimSqliteHandlerCommand, db, JimSqliteDelProc);
284 Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
286 return JIM_OK;
289 int Jim_sqlite3Init(Jim_Interp *interp)
291 if (Jim_PackageProvide(interp, "sqlite3", "1.0", JIM_ERRMSG))
292 return JIM_ERR;
294 Jim_CreateCommand(interp, "sqlite3.open", JimSqliteOpenCommand, NULL, NULL);
295 return JIM_OK;