xref: /4.6.4/couchstore/src/couchscript.cc (revision 9e90a388)
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2#include "config.h"
3#include <iostream>
4#include <cassert>
5#include <errno.h>
6#include <string.h>
7#include <sysexits.h>
8#include <stdlib.h>
9#include <unistd.h>
10#include <sstream>
11#include <string>
12
13#include <libcouchstore/couch_db.h>
14#include "internal.h"
15
16// The number of bytes that is maximally needed to set the `LUA_PATH`
17// environment variable. The `18` is the number of bytes of the `LUA_PATH=`
18// prefix, the `/?.lua;;` postfix, plus the null-terminator.
19#define LUA_PATH_MAX_SIZE (PATH_MAX + 18)
20
21// For building against lua 5.2 and up. Also requires that lua was
22// built with this define. Homebrew at least will build lua with this
23// set.
24#define LUA_COMPAT_ALL
25// Copied from lua.hpp to allow us to use older versions of lua
26// without that wrapper file.. (BTW this method is broken because
27// it includes system headers within 'extern "C"' and that may not
28// work very well...
29extern "C" {
30#include <lua.h>
31#include <lualib.h>
32#include <lauxlib.h>
33}
34
35#define CHECK_NULL(db) \
36    if(db == nullptr) { \
37        lua_pushstring(ls, "The db instance is closed"); \
38        lua_error(ls); \
39        return 1; \
40    }
41
42typedef union {
43    struct {
44        uint64_t cas;
45        uint32_t exp;
46        uint32_t flags;
47    } fields;
48    char bytes[1];
49} revbuf_t;
50
51// Wrapper function while we're fixing up the namespace
52static const char *couchstore_strerror(int code) {
53    return couchstore_strerror((couchstore_error_t)code);
54}
55
56extern "C" {
57
58    static void push_db(lua_State *ls, Db *db)
59    {
60        Db **d = static_cast<Db **>(lua_newuserdata(ls, sizeof(Db *)));
61        assert(d);
62        *d = db;
63
64        luaL_getmetatable(ls, "couch");
65        lua_setmetatable(ls, -2);
66    }
67
68    static void push_docinfo(lua_State *ls, DocInfo *docinfo)
69    {
70        DocInfo **di = static_cast<DocInfo **>(lua_newuserdata(ls, sizeof(DocInfo *)));
71        assert(di);
72        *di = docinfo;
73        assert(*di);
74
75        luaL_getmetatable(ls, "docinfo");
76        lua_setmetatable(ls, -2);
77    }
78
79    static DocInfo *getDocInfo(lua_State *ls)
80    {
81        DocInfo **d = static_cast<DocInfo **>(luaL_checkudata(ls, 1, "docinfo"));
82        assert(d);
83        assert(*d);
84        return *d;
85    }
86
87    static int couch_open(lua_State *ls)
88    {
89        if (lua_gettop(ls) < 1) {
90            lua_pushstring(ls, "couch.open takes at least one argument: "
91                           "\"pathname\" [shouldCreate]");
92            lua_error(ls);
93            return 1;
94        }
95
96        const char *pathname = luaL_checkstring(ls, 1);
97        uint64_t flags(0);
98
99        if (lua_gettop(ls) > 1) {
100            if (!lua_isboolean(ls, 2)) {
101                lua_pushstring(ls, "Second arg must be a boolean, "
102                               "true if allowed to create databases.");
103                lua_error(ls);
104                return 1;
105            }
106            flags = lua_toboolean(ls, 2) ? COUCHSTORE_OPEN_FLAG_CREATE : 0;
107        }
108
109        Db *db(NULL);
110
111        couchstore_error_t rc = couchstore_open_db(pathname, flags, &db);
112        if (rc != COUCHSTORE_SUCCESS) {
113            char buf[256];
114            snprintf(buf, sizeof(buf), "error opening DB: %s", couchstore_strerror(rc));
115            lua_pushstring(ls, buf);
116            lua_error(ls);
117            return 1;
118        }
119
120        push_db(ls, db);
121
122        return 1;
123    }
124
125    static Db *getDb(lua_State *ls)
126    {
127        Db **d = static_cast<Db **>(luaL_checkudata(ls, 1, "couch"));
128        assert(d);
129        return *d;
130    }
131
132    static void nullDb(lua_State *ls)
133    {
134        Db **d = static_cast<Db **>(luaL_checkudata(ls, 1, "couch"));
135        *d = nullptr;
136    }
137
138    static int couch_close(lua_State *ls)
139    {
140        Db *db = getDb(ls);
141        CHECK_NULL(db)
142
143        if (couchstore_close_db(db) != COUCHSTORE_SUCCESS) {
144            lua_pushstring(ls, "error closing database");
145            lua_error(ls);
146            return 1;
147        }
148        nullDb(ls);
149        return 0;
150    }
151
152    static int couch_commit(lua_State *ls)
153    {
154        Db *db = getDb(ls);
155        CHECK_NULL(db)
156
157        if (couchstore_commit(db) != COUCHSTORE_SUCCESS) {
158            lua_pushstring(ls, "error committing");
159            lua_error(ls);
160            return 1;
161        }
162        return 0;
163    }
164
165    static int couch_get_from_docinfo(lua_State *ls)
166    {
167        if (lua_gettop(ls) < 2) {
168            lua_pushstring(ls, "couch:get_from_docinfo takes one argument: \"docinfo\"");
169            lua_error(ls);
170            return 1;
171        }
172
173        Db *db = getDb(ls);
174        CHECK_NULL(db)
175
176        Doc *doc(NULL);
177        lua_remove(ls, 1);
178        DocInfo *docinfo = getDocInfo(ls);
179        assert(docinfo);
180
181        int rc = couchstore_open_doc_with_docinfo(db, docinfo, &doc, 0);
182        if (rc < 0) {
183            char buf[256];
184            couchstore_free_docinfo(docinfo);
185            snprintf(buf, sizeof(buf), "error getting doc by docinfo: %s", couchstore_strerror(rc));
186            lua_pushstring(ls, buf);
187            lua_error(ls);
188            return 1;
189        }
190
191        lua_pushlstring(ls, doc->data.buf, doc->data.size);
192
193        couchstore_free_document(doc);
194
195        return 1;
196    }
197
198    // couch:get(key) -> string, docinfo
199    static int couch_get(lua_State *ls)
200    {
201        if (lua_gettop(ls) < 1) {
202            lua_pushstring(ls, "couch:get takes one argument: \"key\"");
203            lua_error(ls);
204            return 1;
205        }
206
207        Doc *doc;
208        DocInfo *docinfo;
209        Db *db = getDb(ls);
210        CHECK_NULL(db)
211
212        size_t klen;
213        const char *key = luaL_checklstring(ls, 2, &klen);
214
215        int rc = couchstore_docinfo_by_id(db, key, klen, &docinfo);
216        if (rc < 0) {
217            char buf[256];
218            snprintf(buf, sizeof(buf), "error get docinfo (key=\"%s\"): %s",
219                     key, couchstore_strerror(rc));
220            lua_pushstring(ls, buf);
221            lua_error(ls);
222            return 1;
223        }
224
225        rc = couchstore_open_doc_with_docinfo(db, docinfo, &doc, 0);
226        if (rc < 0) {
227            char buf[1024];
228            snprintf(buf, sizeof(buf),
229                     "error get doc by docinfo (key=\"%s\", bp=%llu, size=%llu): %s",
230                     key, (unsigned long long) docinfo->bp,
231                     (unsigned long long) docinfo->size, couchstore_strerror(rc));
232            couchstore_free_docinfo(docinfo);
233            lua_pushstring(ls, buf);
234            lua_error(ls);
235            return 1;
236        }
237
238        lua_pushlstring(ls, doc->data.buf, doc->data.size);
239        push_docinfo(ls, docinfo);
240
241        couchstore_free_document(doc);
242
243        return 2;
244    }
245
246    // couch:truncate(length)
247    static int couch_truncate(lua_State *ls)
248    {
249        if (lua_gettop(ls) < 1) {
250            lua_pushstring(ls, "couch:truncate takes one argument: length");
251            lua_error(ls);
252            return 1;
253        }
254
255        Db *db = getDb(ls);
256        CHECK_NULL(db)
257
258        int64_t arg = static_cast<int64_t>(luaL_checknumber(ls, 2));
259        cs_off_t location(0);
260        if (arg < 1) {
261            location = db->file.pos + arg;
262        } else {
263            location = static_cast<cs_off_t>(arg);
264        }
265
266        const char* path = couchstore_get_db_filename(db);
267
268        int rv = truncate(path, location);
269        if (rv != 0) {
270            char buf[1256];
271            snprintf(buf, sizeof(buf), "error truncating DB %s to %lld: %s (%d)",
272                     path, (long long) location, strerror(errno), errno);
273            lua_pushstring(ls, buf);
274            lua_error(ls);
275            return 1;
276        }
277
278        db->file.pos = location;
279
280        return 0;
281    }
282
283    // couch:delete(key, [rev])
284    static int couch_delete(lua_State *ls)
285    {
286        if (lua_gettop(ls) < 1) {
287            lua_pushstring(ls, "couch:delete takes at least one argument: "
288                           "\"key\" [rev_seq]");
289            lua_error(ls);
290            return 1;
291        }
292
293        Doc doc;
294        DocInfo docinfo = DOC_INFO_INITIALIZER;
295
296        doc.id.buf = const_cast<char *>(luaL_checklstring(ls, 2, &doc.id.size));
297        doc.data.size = 0;
298        docinfo.id = doc.id;
299        docinfo.deleted = 1;
300
301        if (lua_gettop(ls) > 2) {
302            docinfo.rev_seq = (uint64_t) luaL_checknumber(ls, 3);
303        }
304
305        Db *db = getDb(ls);
306        CHECK_NULL(db)
307
308        int rc = couchstore_save_document(db, &doc, &docinfo, 0);
309        if (rc < 0) {
310            char buf[256];
311            snprintf(buf, sizeof(buf), "error deleting document: %s", couchstore_strerror(rc));
312            lua_pushstring(ls, buf);
313            lua_error(ls);
314            return 1;
315        }
316
317        return 0;
318    }
319
320    class BulkData
321    {
322    public:
323        BulkData(unsigned n) : size(n),
324            docs(static_cast<Doc **>(calloc(size, sizeof(Doc *)))),
325            infos(static_cast<DocInfo **>(calloc(size, sizeof(DocInfo *)))) {
326            assert(docs);
327            assert(infos);
328
329            for (unsigned i = 0; i < size; ++i) {
330                docs[i] = static_cast<Doc *>(calloc(1, sizeof(Doc)));
331                assert(docs[i]);
332                infos[i] = static_cast<DocInfo *>(calloc(1, sizeof(DocInfo)));
333                assert(infos[i]);
334            }
335        }
336
337        ~BulkData() {
338            for (unsigned i = 0; i < size; ++i) {
339                free(docs[i]);
340                free(infos[i]);
341            }
342            free(docs);
343            free(infos);
344        }
345
346        unsigned size;
347        Doc **docs;
348        DocInfo **infos;
349    };
350
351    static int couch_save_bulk(lua_State *ls)
352    {
353        if (lua_gettop(ls) < 2) {
354            lua_pushstring(ls, "couch:save_bulk requires a table full of docs to save");
355            lua_error(ls);
356            return 1;
357        }
358
359        if (!lua_istable(ls, -1)) {
360            lua_pushstring(ls, "argument must be a table.");
361            lua_error(ls);
362            return 1;
363        }
364
365        BulkData bs((unsigned)lua_objlen(ls, -1));
366
367        int offset(0);
368        for (lua_pushnil(ls); lua_next(ls, -2); lua_pop(ls, 1)) {
369
370            int n = static_cast<int>(lua_objlen(ls, -1));
371
372            if (n > 2) {
373                Doc *doc(bs.docs[offset]);
374                assert(doc);
375                DocInfo *docinfo(bs.infos[offset]);
376                assert(docinfo);
377                ++offset;
378                revbuf_t revbuf;
379                memset(&revbuf, 0, sizeof(revbuf));
380
381                lua_rawgeti(ls, -1, 1);
382                doc->id.buf = const_cast<char *>(luaL_checklstring(ls, -1, &doc->id.size));
383                lua_pop(ls, 1);
384
385                lua_rawgeti(ls, -1, 2);
386                doc->data.buf = const_cast<char *>(luaL_checklstring(ls, -1, &doc->data.size));
387                docinfo->id = doc->id;
388                lua_pop(ls, 1);
389
390                lua_rawgeti(ls, -1, 3);
391                docinfo->content_meta = static_cast<uint8_t>(luaL_checkint(ls, -1));
392                lua_pop(ls, 1);
393
394                if (n > 3) {
395                    lua_rawgeti(ls, -1, 4);
396                    docinfo->rev_seq = (uint64_t) luaL_checknumber(ls, -1);
397                    lua_pop(ls, 1);
398                }
399
400                if (n > 4) {
401                    lua_rawgeti(ls, -1, 5);
402                    revbuf.fields.cas = (uint64_t) luaL_checknumber(ls, -1);
403                    revbuf.fields.cas = ntohll(revbuf.fields.cas);
404                    lua_pop(ls, 1);
405                }
406
407                if (n > 5) {
408                    lua_rawgeti(ls, -1, 6);
409                    revbuf.fields.exp = static_cast<uint32_t>(luaL_checklong(ls, -1));
410                    revbuf.fields.exp = ntohl(revbuf.fields.exp);
411                    lua_pop(ls, 1);
412                }
413
414                if (n > 6) {
415                    lua_rawgeti(ls, -1, 7);
416                    revbuf.fields.flags = static_cast<uint32_t>(luaL_checklong(ls, 8));
417                    revbuf.fields.flags = ntohl(revbuf.fields.flags);
418                    lua_pop(ls, 1);
419                }
420
421                docinfo->rev_meta.size = sizeof(revbuf);
422                docinfo->rev_meta.buf = revbuf.bytes;
423
424            }
425        }
426
427        Db *db = getDb(ls);
428        CHECK_NULL(db)
429
430        int rc = couchstore_save_documents(db, bs.docs, bs.infos,
431                                           bs.size, COMPRESS_DOC_BODIES);
432        if (rc < 0) {
433            char buf[256];
434            snprintf(buf, sizeof(buf), "error storing document: %s", couchstore_strerror(rc));
435            lua_pushstring(ls, buf);
436            lua_error(ls);
437            return 1;
438        }
439
440        return 0;
441    }
442
443    // couch:save(key, value, content_meta, [rev_seq], [cas], [exp], [flags]
444    static int couch_save(lua_State *ls)
445    {
446
447        if (lua_gettop(ls) < 4) {
448            lua_pushstring(ls, "couch:save takes at least three arguments: "
449                           "\"key\" \"value\" meta_flags [rev_seq] [cas] [exp] [flags]");
450            lua_error(ls);
451            return 1;
452        }
453
454        Doc doc;
455        DocInfo docinfo = DOC_INFO_INITIALIZER;
456
457        revbuf_t revbuf;
458        memset(&revbuf, 0, sizeof(revbuf));
459
460        // These really should be const char*
461        doc.id.buf = const_cast<char *>(luaL_checklstring(ls, 2, &doc.id.size));
462        doc.data.buf = const_cast<char *>(luaL_checklstring(ls, 3, &doc.data.size));
463        docinfo.id = doc.id;
464
465        docinfo.content_meta = static_cast<uint8_t>(luaL_checkint(ls, 4));
466
467        if (lua_gettop(ls) > 4) {
468            docinfo.rev_seq = (uint64_t) luaL_checknumber(ls, 5);
469        }
470
471        if (lua_gettop(ls) > 5) {
472            revbuf.fields.cas = (uint64_t) luaL_checknumber(ls, 6);
473            revbuf.fields.cas = ntohll(revbuf.fields.cas);
474        }
475
476        if (lua_gettop(ls) > 6) {
477            revbuf.fields.exp = static_cast<uint32_t>(luaL_checklong(ls, 7));
478            revbuf.fields.exp = ntohl(revbuf.fields.exp);
479        }
480
481        if (lua_gettop(ls) > 7) {
482            revbuf.fields.flags = static_cast<uint32_t>(luaL_checklong(ls, 8));
483            revbuf.fields.flags = ntohl(revbuf.fields.flags);
484        }
485
486        docinfo.rev_meta.size = sizeof(revbuf);
487        docinfo.rev_meta.buf = revbuf.bytes;
488
489        Db *db = getDb(ls);
490        CHECK_NULL(db)
491
492        int rc = couchstore_save_document(db, &doc, &docinfo,
493                                          COMPRESS_DOC_BODIES);
494        if (rc < 0) {
495            char buf[256];
496            snprintf(buf, sizeof(buf), "error storing document: %s", couchstore_strerror(rc));
497            lua_pushstring(ls, buf);
498            lua_error(ls);
499            return 1;
500        }
501
502        return 0;
503    }
504
505    static int couch_save_local(lua_State *ls)
506    {
507        if (lua_gettop(ls) < 3) {
508            lua_pushstring(ls, "couch:save_local takes two arguments: "
509                           "\"key\" \"value\"");
510            lua_error(ls);
511            return 1;
512        }
513
514        LocalDoc doc;
515        doc.id.buf = const_cast<char *>(luaL_checklstring(ls, 2, &doc.id.size));
516        doc.json.buf = const_cast<char *>(luaL_checklstring(ls, 3, &doc.json.size));
517        doc.deleted = 0;
518
519        Db *db = getDb(ls);
520        CHECK_NULL(db)
521
522        int rc = couchstore_save_local_document(db, &doc);
523        if (rc < 0) {
524            char buf[256];
525            snprintf(buf, sizeof(buf), "error storing local document: %s",
526                     couchstore_strerror(rc));
527            lua_pushstring(ls, buf);
528            lua_error(ls);
529            return 1;
530        }
531        return 0;
532    }
533
534    static int couch_delete_local(lua_State *ls)
535    {
536        if (lua_gettop(ls) < 2) {
537            lua_pushstring(ls, "couch:delete_local takes one argument: \"key\"");
538            lua_error(ls);
539            return 1;
540        }
541
542        LocalDoc doc;
543        doc.id.buf = const_cast<char *>(luaL_checklstring(ls, 2, &doc.id.size));
544        doc.json.size = 0;
545        doc.deleted = 1;
546
547        Db *db = getDb(ls);
548        CHECK_NULL(db)
549
550        int rc = couchstore_save_local_document(db, &doc);
551        if (rc < 0) {
552            char buf[256];
553            snprintf(buf, sizeof(buf), "error deleting local document: %s",
554                     couchstore_strerror(rc));
555            lua_pushstring(ls, buf);
556            lua_error(ls);
557            return 1;
558        }
559        return 0;
560    }
561
562    // couch:get_local(key) -> val
563    static int couch_get_local(lua_State *ls)
564    {
565        if (lua_gettop(ls) < 1) {
566            lua_pushstring(ls, "couch:get_local takes one argument: \"key\"");
567            lua_error(ls);
568            return 1;
569        }
570
571        LocalDoc *doc;
572        Db *db = getDb(ls);
573        CHECK_NULL(db)
574
575        size_t klen;
576        // Should be const :/
577        const char *key = luaL_checklstring(ls, 2, &klen);
578
579        int rc = couchstore_open_local_document(db, static_cast<const void*>(key), klen, &doc);
580        if (rc < 0) {
581            char buf[256];
582            snprintf(buf, sizeof(buf), "error getting local doc: %s",
583                     couchstore_strerror(rc));
584            lua_pushstring(ls, buf);
585            lua_error(ls);
586            return 1;
587        }
588
589        lua_pushlstring(ls, doc->json.buf, doc->json.size);
590
591        couchstore_free_local_document(doc);
592
593        return 1;
594    }
595
596    class ChangesState
597    {
598    public:
599        ChangesState(lua_State *s, const char *f) : ls(s), fun_name(f) {
600            assert(lua_isfunction(ls, -1));
601            lua_setfield(ls, LUA_REGISTRYINDEX, fun_name);
602        }
603
604        ~ChangesState() {
605            lua_pushnil(ls);
606            lua_setfield(ls, LUA_REGISTRYINDEX, fun_name);
607        }
608
609        void invoke(DocInfo *di) {
610            lua_getfield(ls, LUA_REGISTRYINDEX, fun_name);
611            push_docinfo(ls, di);
612            if (lua_pcall(ls, 1, 0, 0) != 0) {
613                // Not *exactly* sure what to do here.
614                std::cerr << "Error running function: "
615                          << lua_tostring(ls, -1) << std::endl;
616            }
617        }
618
619    private:
620        lua_State *ls;
621        const char *fun_name;
622    };
623
624    static int couch_changes_each(Db *, DocInfo *di, void *ctx)
625    {
626        ChangesState *st(static_cast<ChangesState *>(ctx));
627        st->invoke(di);
628        return 1;
629    }
630
631    static int couch_func_id(0);
632
633    // db:changes(since, options, function(docinfo) something end)
634    static int couch_changes(lua_State *ls)
635    {
636        if (lua_gettop(ls) < 4) {
637            lua_pushstring(ls, "couch:changes takes three arguments: "
638                           "rev_seq, options, function(docinfo)...");
639            lua_error(ls);
640            return 1;
641        }
642
643        Db *db = getDb(ls);
644        CHECK_NULL(db)
645
646        uint64_t since((uint64_t)luaL_checknumber(ls, 2));
647        couchstore_docinfos_options options((uint64_t)luaL_checknumber(ls, 3));
648
649        if (!lua_isfunction(ls, 4)) {
650            lua_pushstring(ls, "I need a function to iterate over.");
651            lua_error(ls);
652            return 1;
653        }
654
655        char fun_buf[64];
656        snprintf(fun_buf, sizeof(fun_buf), "couch_fun_%d", ++couch_func_id);
657        ChangesState changes_state(ls, fun_buf);
658
659        int rc = couchstore_changes_since(db, since, options,
660                                          couch_changes_each, &changes_state);
661        if (rc != 0) {
662            char buf[128];
663            snprintf(buf, sizeof(buf), "error iterating: %s", couchstore_strerror(rc));
664            lua_pushstring(ls, buf);
665            lua_error(ls);
666            return 1;
667        }
668
669        return 0;
670    }
671
672    static int couch_gc(lua_State *ls) {
673        Db *db = getDb(ls);
674        if(db != nullptr) {
675            couchstore_close_db(db);
676            nullDb(ls);
677        }
678        return 1;
679    }
680
681    static const luaL_Reg couch_funcs[] = {
682        {"open", couch_open},
683        {NULL, NULL}
684    };
685
686    static const luaL_Reg couch_methods[] = {
687        {"save", couch_save},
688        {"save_bulk", couch_save_bulk},
689        {"delete", couch_delete},
690        {"get", couch_get},
691        {"get_from_docinfo", couch_get_from_docinfo},
692        {"changes", couch_changes},
693        {"save_local", couch_save_local},
694        {"delete_local", couch_delete_local},
695        {"get_local", couch_get_local},
696        {"commit", couch_commit},
697        {"close", couch_close},
698        {"truncate", couch_truncate},
699        {"__gc", couch_gc},
700        {NULL, NULL}
701    };
702
703    static int docinfo_id(lua_State *ls)
704    {
705        DocInfo *di = getDocInfo(ls);
706        lua_pushlstring(ls, di->id.buf, di->id.size);
707        return 1;
708    }
709
710    static int docinfo_db_seq(lua_State *ls)
711    {
712        DocInfo *di = getDocInfo(ls);
713        lua_pushnumber(ls, di->db_seq);
714        return 1;
715    }
716
717    static int docinfo_rev_seq(lua_State *ls)
718    {
719        DocInfo *di = getDocInfo(ls);
720        lua_pushnumber(ls, di->rev_seq);
721        return 1;
722    }
723
724    static int docinfo_deleted(lua_State *ls)
725    {
726        DocInfo *di = getDocInfo(ls);
727        lua_pushinteger(ls, di->deleted);
728        return 1;
729    }
730
731    static int docinfo_content_meta(lua_State *ls)
732    {
733        DocInfo *di = getDocInfo(ls);
734        lua_pushinteger(ls, di->content_meta);
735        return 1;
736    }
737
738    static int docinfo_len(lua_State *ls)
739    {
740        DocInfo *di = getDocInfo(ls);
741        lua_pushinteger(ls, di->size);
742        return 1;
743    }
744
745    static int docinfo_cas(lua_State *ls)
746    {
747        DocInfo *di = getDocInfo(ls);
748        if (di->rev_meta.size >= sizeof(revbuf_t)) {
749            revbuf_t *rbt(reinterpret_cast<revbuf_t *>(di->rev_meta.buf));
750            lua_pushnumber(ls, ntohll(rbt->fields.cas));
751        } else {
752            lua_pushnumber(ls, 0);
753        }
754        return 1;
755    }
756
757    static int docinfo_exp(lua_State *ls)
758    {
759        DocInfo *di = getDocInfo(ls);
760        if (di->rev_meta.size >= sizeof(revbuf_t)) {
761            revbuf_t *rbt(reinterpret_cast<revbuf_t *>(di->rev_meta.buf));
762            lua_pushnumber(ls, ntohl(rbt->fields.exp));
763        } else {
764            lua_pushnumber(ls, 0);
765        }
766        return 1;
767    }
768
769    static int docinfo_flags(lua_State *ls)
770    {
771        DocInfo *di = getDocInfo(ls);
772        if (di->rev_meta.size >= sizeof(revbuf_t)) {
773            revbuf_t *rbt(reinterpret_cast<revbuf_t *>(di->rev_meta.buf));
774            lua_pushnumber(ls, ntohl(rbt->fields.flags));
775        } else {
776            lua_pushnumber(ls, 0);
777        }
778        return 1;
779    }
780
781    static int docinfo_gc(lua_State *ls)
782    {
783        DocInfo *di = getDocInfo(ls);
784        couchstore_free_docinfo(di);
785        return 1;
786    }
787
788    static const luaL_Reg docinfo_methods[] = {
789        {"id", docinfo_id},
790        {"rev", docinfo_rev_seq},
791        {"db_seq", docinfo_db_seq},
792        {"cas", docinfo_cas},
793        {"exp", docinfo_exp},
794        {"flags", docinfo_flags},
795        {"deleted", docinfo_deleted},
796        {"content_meta", docinfo_content_meta},
797        {"size", docinfo_len},
798        {"__len", docinfo_len},
799        {"__gc", docinfo_gc},
800        {NULL, NULL}
801    };
802
803}
804
805static void initCouch(lua_State *ls)
806{
807    luaL_newmetatable(ls, "couch");
808
809    lua_pushstring(ls, "__index");
810    lua_pushvalue(ls, -2);  /* pushes the metatable */
811    lua_settable(ls, -3);  /* metatable.__index = metatable */
812
813    luaL_openlib(ls, NULL, couch_methods, 0);
814
815    luaL_openlib(ls, "couch", couch_funcs, 0);
816}
817
818static void initDocInfo(lua_State *ls)
819{
820    luaL_newmetatable(ls, "docinfo");
821
822    lua_pushstring(ls, "__index");
823    lua_pushvalue(ls, -2);  /* pushes the metatable */
824    lua_settable(ls, -3);  /* metatable.__index = metatable */
825
826    luaL_openlib(ls, NULL, docinfo_methods, 0);
827}
828
829int main(int argc, char **argv)
830{
831    if (argc < 2) {
832        std::cerr << "Give me a filename or give me death." << std::endl;
833        exit(EX_USAGE);
834    }
835
836    if (getenv("LUA_PATH") == NULL) {
837        std::string path = argv[1];
838        static char lua_path[LUA_PATH_MAX_SIZE];
839        size_t pos = path.find_last_of("/\\");
840        if (pos != std::string::npos) {
841            path.resize(pos);
842            std::stringstream ss;
843            ss << "LUA_PATH=" << path << "/?.lua;;";
844            memcpy(lua_path, ss.str().c_str(), ss.str().length() + 1);
845            putenv(lua_path);
846        }
847    }
848
849    lua_State *ls = luaL_newstate();
850    luaL_openlibs(ls);
851
852    initCouch(ls);
853    initDocInfo(ls);
854
855    int rv(luaL_dofile(ls, argv[1]));
856    if (rv != 0) {
857        std::cerr << "Error running stuff:  " << lua_tostring(ls, -1) << std::endl;
858        return rv;
859    }
860
861    int top = lua_gettop(ls);
862    int rc = 1;
863    if (top && lua_isnumber(ls, top)) {
864        rc = static_cast<int>(lua_tonumber(ls, top));
865    }
866    lua_close(ls);
867    return rc;
868}
869