1// PyStorage.cpp --
2// $Id: PyStorage.cpp 1669 2007-06-16 00:23:25Z jcw $
3// This is part of MetaKit, the homepage is http://www.equi4.com/metakit.html
4// Copyright (C) 1999-2004 Gordon McMillan and Jean-Claude Wippler.
5//
6//  Storage class implementation and main entry point
7
8#include <Python.h>
9#include "PyStorage.h"
10#include <PWOSequence.h>
11#include <PWONumber.h>
12#include "PyView.h"
13#include "PyProperty.h"
14#include "PyRowRef.h"
15#include "mk4str.h"
16#include "mk4io.h"
17
18#if !defined _WIN32
19#define __declspec(x)
20#endif
21
22static char mk4py_module_documentation[] =
23  "This is the Python interface of the embeddable MetaKit database library.\n"
24  "Example of use:\n""\n""  import Mk4py\n""  mk = Mk4py\n""\n"
25  "  s = mk.storage('demo.dat', 1)\n"
26  "  v = s.getas('people[first:S,last:S,shoesize:I]')\n""\n"
27  "  v.append(first='John',last='Lennon',shoesize=44)\n"
28  "  v.append(first='Flash',last='Gordon',shoesize=42)\n""  s.commit()\n""\n"
29  "  def dump(v):\n""    print len(v)\n"
30  "    for r in v: print r.first, r.last, r.shoesize\n""\n"
31  "  v2 = v.sort(v.last)\n""  dump(v2)\n""  v[0].last = 'Doe'\n""  dump(v2)\n"
32  "  v2 = v.select(last='Doe')\n""  dump(v2)\n""  del s\n""\n"
33  "See the website at http://www.equi4.com/metakit.html for full details.\n";
34
35///////////////////////////////////////////////////////////////////////////////
36
37class c4_PyStream: public c4_Stream {
38    PyObject *_stream;
39
40  public:
41    c4_PyStream(PyObject *stream_);
42
43    virtual int Read(void *buffer_, int length_);
44    virtual bool Write(const void *buffer_, int length_);
45};
46
47c4_PyStream::c4_PyStream(PyObject *stream_): _stream(stream_){}
48
49int c4_PyStream::Read(void *buffer_, int length_) {
50  PyObject *o = PyObject_CallMethod(_stream, "read", "i", length_);
51  int n = o != 0 ? PyString_Size(o): 0;
52  if (n > 0)
53    memcpy(buffer_, PyString_AsString(o), n);
54  return n;
55}
56
57bool c4_PyStream::Write(const void *buffer_, int length_) {
58  PyObject_CallMethod(_stream, "write", "s#", buffer_, length_);
59  return true; //!! how do we detect write errors here?
60}
61
62///////////////////////////////////////////////////////////////////////////////
63// A "storage in a storage" strategy class for MetaKit
64
65class SiasStrategy: public c4_Strategy {
66    c4_Storage &_storage;
67    c4_View _view;
68    c4_BytesProp _memo;
69    int _row;
70
71  public:
72    SiasStrategy(c4_Storage &storage_, const c4_View &view_, const c4_BytesProp
73      &memo_, int row_): _storage(storage_), _view(view_), _memo(memo_), _row
74      (row_) {
75        // set up mapping if the memo itself is mapped in its entirety
76        c4_Strategy &strat = storage_.Strategy();
77        if (strat._mapStart != 0) {
78            c4_RowRef r = _view[_row];
79            c4_Bytes data = _memo(r).Access(0);
80            const t4_byte *ptr = data.Contents();
81            if (data.Size() == _memo(r).GetSize() && strat._mapStart != 0 &&
82              ptr >= strat._mapStart && ptr - strat._mapStart < strat._dataSize)
83              {
84                _mapStart = ptr;
85                _dataSize = data.Size();
86            }
87        }
88    }
89
90    virtual ~SiasStrategy() {
91        _view = c4_View();
92        _mapStart = 0;
93        _dataSize = 0;
94    }
95
96    virtual int DataRead(t4_i32 pos_, void *buffer_, int length_) {
97        int i = 0;
98
99        while (i < length_) {
100            c4_Bytes data = _memo(_view[_row]).Access(pos_ + i, length_ - i);
101            int n = data.Size();
102            if (n <= 0)
103              break;
104            memcpy((char*)buffer_ + i, data.Contents(), n);
105            i += n;
106        }
107
108        return i;
109    }
110
111    virtual void DataWrite(t4_i32 pos_, const void *buffer_, int length_) {
112        c4_Bytes data(buffer_, length_);
113        if (!_memo(_view[_row]).Modify(data, pos_))
114          ++_failure;
115    }
116
117    virtual void DataCommit(t4_i32 newSize_) {
118        if (newSize_ > 0)
119          _memo(_view[_row]).Modify(c4_Bytes(), newSize_);
120    }
121
122    virtual void ResetFileMapping() {
123        _mapStart = 0; // never called, but just in case
124    }
125};
126
127///////////////////////////////////////////////////////////////////////////////
128
129static char *autocommit__doc =
130  "autocommit() -- turn on autocommit (i.e. commit when storage object is deleted)";
131
132static PyObject *PyStorage_Autocommit(PyStorage *o, PyObject *_args) {
133  try {
134    o->AutoCommit();
135    Py_INCREF(Py_None);
136    return Py_None;
137  } catch (...) {
138    return 0;
139  }
140}
141
142static char *contents__doc =
143  "contents() -- return view with one row, representing entire storage (internal use)";
144
145static PyObject *PyStorage_Contents(PyStorage *o, PyObject *_args) {
146  try {
147    return new PyView(*o);
148  } catch (...) {
149    return 0;
150  }
151}
152
153static char *description__doc =
154  "description(name='') -- return a description of named view, or of entire storage";
155
156static PyObject *PyStorage_Description(PyStorage *o, PyObject *_args) {
157  try {
158    PWOSequence args(_args);
159    PWOString nm("");
160    if (args.len() > 0)
161      nm = args[0];
162    const char *descr = o->Description(nm);
163    if (descr) {
164      PWOString rslt(descr);
165      return rslt.disOwn();
166    }
167    Fail(PyExc_KeyError, nm);
168  } catch (...){}
169  return 0; /* satisfy compiler */
170}
171
172static char *commit__doc =
173  "commit(full=0) -- permanently commit data and structure changes to disk";
174
175static PyObject *PyStorage_Commit(PyStorage *o, PyObject *_args) {
176  try {
177    PWOSequence args(_args);
178    PWONumber flag(0);
179    if (args.len() > 0)
180      flag = args[0];
181    if (!o->Commit((int)flag != 0))
182      Fail(PyExc_IOError, "commit failed");
183    Py_INCREF(Py_None);
184    return Py_None;
185  } catch (...) {
186    return 0;
187  }
188}
189
190static char *rollback__doc =
191  "rollback(full=0) -- revert data and structure as was last committed to disk";
192
193static PyObject *PyStorage_Rollback(PyStorage *o, PyObject *_args) {
194  try {
195    PWOSequence args(_args);
196    PWONumber flag(0);
197    if (args.len() > 0)
198      flag = args[0];
199    if (!o->Rollback((int)flag != 0))
200      Fail(PyExc_IOError, "rollback failed");
201    Py_INCREF(Py_None);
202    return Py_None;
203  } catch (...) {
204    return 0;
205  }
206}
207
208static char *aside__doc =
209  "aside() -- revert data and structure as was last committed to disk";
210
211static PyObject *PyStorage_Aside(PyStorage *o, PyObject *_args) {
212  try {
213    PWOSequence args(_args);
214    if (!PyStorage_Check((PyObject*)args[0]))
215      Fail(PyExc_TypeError, "First arg must be a storage");
216    c4_Storage &storage = *(PyStorage*)(PyObject*)args[0];
217    if (!o->SetAside(storage))
218      Fail(PyExc_IOError, "aside failed");
219    Py_INCREF(Py_None);
220    return Py_None;
221  } catch (...) {
222    return 0;
223  }
224}
225
226static char *view__doc =
227  "view(viewname) -- return top-level view in storage, given its name";
228
229static PyObject *PyStorage_View(PyStorage *o, PyObject *_args) {
230  try {
231    PWOSequence args(_args);
232    PWOString nm(args[0]);
233    return new PyView(o->View(nm));
234  } catch (...) {
235    return 0;
236  }
237}
238
239static char *getas__doc =
240  "getas(description) -- return view, create / restructure as needed to match";
241
242static PyObject *PyStorage_GetAs(PyStorage *o, PyObject *_args) {
243  try {
244    PWOSequence args(_args);
245    PWOString descr(args[0]);
246    return new PyView(o->GetAs(descr));
247  } catch (...) {
248    return 0;
249  }
250}
251
252static char *load__doc =
253  "load(file) -- replace storage object contents from file (or any obj supporting read)";
254
255static PyObject *PyStorage_load(PyStorage *o, PyObject *_args) {
256  try {
257    PWOSequence args(_args);
258    if (args.len() != 1)
259      Fail(PyExc_ValueError, "load requires a file-like object");
260
261    c4_PyStream stream(args[0]);
262    o->LoadFrom(stream);
263
264    Py_INCREF(Py_None);
265    return Py_None;
266  } catch (...) {
267    return 0;
268  }
269}
270
271static char *save__doc =
272  "save(file) -- store storage object contents to file (or any obj supporting write)";
273
274static PyObject *PyStorage_save(PyStorage *o, PyObject *_args) {
275  try {
276    PWOSequence args(_args);
277    if (args.len() != 1)
278      Fail(PyExc_ValueError, "save requires a file-like object");
279
280    c4_PyStream stream(args[0]);
281    o->SaveTo(stream);
282
283    Py_INCREF(Py_None);
284    return Py_None;
285  } catch (...) {
286    return 0;
287  }
288}
289
290static PyMethodDef StorageMethods[] =  {
291   {
292    "getas", (PyCFunction)PyStorage_GetAs, METH_VARARGS, getas__doc
293  }
294  ,  {
295    "view", (PyCFunction)PyStorage_View, METH_VARARGS, view__doc
296  }
297  ,  {
298    "rollback", (PyCFunction)PyStorage_Rollback, METH_VARARGS, rollback__doc
299  }
300  ,  {
301    "commit", (PyCFunction)PyStorage_Commit, METH_VARARGS, commit__doc
302  }
303  ,  {
304    "aside", (PyCFunction)PyStorage_Aside, METH_VARARGS, aside__doc
305  }
306  ,  {
307    "description", (PyCFunction)PyStorage_Description, METH_VARARGS,
308      description__doc
309  }
310  ,  {
311    "contents", (PyCFunction)PyStorage_Contents, METH_VARARGS, contents__doc
312  }
313  ,  {
314    "autocommit", (PyCFunction)PyStorage_Autocommit, METH_VARARGS,
315      autocommit__doc
316  }
317  ,  {
318    "load", (PyCFunction)PyStorage_load, METH_VARARGS, load__doc
319  }
320  ,  {
321    "save", (PyCFunction)PyStorage_save, METH_VARARGS, save__doc
322  }
323  ,  {
324    0, 0, 0, 0
325  }
326};
327
328static void PyStorage_dealloc(PyStorage *o) {
329  //o->~PyStorage();
330  delete o;
331}
332
333static int PyStorage_print(PyStorage *o, FILE *f, int) {
334  fprintf(f, "<PyStorage object at %lx>", (long)o);
335  return 0;
336}
337
338static PyObject *PyStorage_getattr(PyStorage *o, char *nm) {
339  return Py_FindMethod(StorageMethods, o, nm);
340}
341
342PyTypeObject PyStoragetype =  {
343  PyObject_HEAD_INIT(&PyType_Type)0, "PyStorage", sizeof(PyStorage), 0,
344    (destructor)PyStorage_dealloc,  /*tp_dealloc*/
345  (printfunc)PyStorage_print,  /*tp_print*/
346  (getattrfunc)PyStorage_getattr,  /*tp_getattr*/
347  0,  /*tp_setattr*/
348  (cmpfunc)0,  /*tp_compare*/
349  (reprfunc)0,  /*tp_repr*/
350  0,  /*tp_as_number*/
351  0,  /*tp_as_sequence*/
352  0,  /*tp_as_mapping*/
353};
354
355static char *storage__doc =
356  "storage() -- create a new in-memory storage (can load/save, but not commit/rollback)\n""storage(file) -- attach a storage object to manage an already opened stdio file\n""storage(filename, rw) -- open file, rw=0: r/o, rw=1: r/w, rw=2: extend";
357
358static PyObject *PyStorage_new(PyObject *o, PyObject *_args) {
359  try {
360    PWOSequence args(_args);
361    PyStorage *ps = 0;
362    switch (args.len()) {
363      case 0:
364        ps = new PyStorage;
365        break;
366      case 1:
367        if (!PyFile_Check((PyObject*)args[0])) {
368          if (PyString_Check((PyObject*)args[0]))
369            Fail(PyExc_TypeError, "rw parameter missing");
370          else
371            Fail(PyExc_TypeError, "argument not an open file");
372          break;
373        }
374        ps = new PyStorage(*new c4_FileStrategy(PyFile_AsFile(args[0])), true);
375        break;
376      case 4:
377         { // Rrrrrr...
378          if (!PyStorage_Check((PyObject*)args[0]))
379            Fail(PyExc_TypeError, "First arg must be a storage object");
380          c4_Storage &storage = *(PyStorage*)(PyObject*)args[0];
381          if (!PyView_Check((PyObject*)args[1]))
382            Fail(PyExc_TypeError, "Second arg must be a view object");
383          c4_View &view = *(PyView*)(PyObject*)args[1];
384          if (!PyProperty_Check((PyObject*)args[2]))
385            Fail(PyExc_TypeError, "Third arg must be a property object");
386          c4_BytesProp &prop = *(c4_BytesProp*)(c4_Property*)(PyProperty*)
387            (PyObject*)args[2];
388          int row = PWONumber(args[3]);
389
390          ps = new PyStorage(*new SiasStrategy(storage, view, prop, row), true);
391          break;
392        }
393      case 2:
394         {
395          char *fnm;
396          int mode;
397          if (!PyArg_ParseTuple(args, "esi", "utf_8", &fnm, &mode))
398            Fail(PyExc_TypeError, "bad argument type");
399          ps = new PyStorage(fnm, mode);
400          PyMem_Free(fnm);
401          if (!ps->Strategy().IsValid()) {
402            delete ps;
403            ps = 0;
404            Fail(PyExc_IOError, "can't open storage file");
405          }
406          break;
407        }
408      default:
409        Fail(PyExc_ValueError, "storage() takes at most 4 arguments");
410    }
411    return ps;
412  } catch (...) {
413    return 0;
414  }
415}
416
417class PyViewer: public c4_CustomViewer {
418    PWOSequence _data;
419    c4_View _template;
420    c4_Row _tempRow;
421    bool _byPos;
422
423  public:
424    PyViewer(const PWOSequence &data_, const c4_View &template_, bool byPos_);
425    virtual ~PyViewer();
426
427    virtual c4_View GetTemplate();
428    virtual int GetSize();
429    virtual bool GetItem(int row_, int col_, c4_Bytes &buf_);
430    virtual bool SetItem(int row_, int col_, const c4_Bytes &buf_);
431};
432
433PyViewer::PyViewer(const PWOSequence &data_, const c4_View &template_, bool
434  byPos_): _data(data_), _template(template_), _byPos(byPos_){}
435
436PyViewer::~PyViewer(){}
437
438c4_View PyViewer::GetTemplate() {
439  return _template;
440}
441
442int PyViewer::GetSize() {
443  return _data.len();
444}
445
446bool PyViewer::GetItem(int row_, int col_, c4_Bytes &buf_) {
447  const c4_Property &prop = _template.NthProperty(col_);
448  if (_byPos) {
449    PWOSequence item(_data[row_]);
450    PyRowRef::setFromPython(_tempRow, prop, item[col_]);
451    return prop(_tempRow).GetData(buf_);
452  }
453  PyObject *item = _data[row_];
454  if (PyInstance_Check(item)) {
455    PyObject *attr = PyObject_GetAttrString(item, (char*)prop.Name());
456    PyRowRef::setFromPython(_tempRow, prop, attr);
457    return prop(_tempRow).GetData(buf_);
458  }
459  if (PyDict_Check(item)) {
460    PyObject *attr = PyDict_GetItemString(item, (char*)prop.Name());
461    PyRowRef::setFromPython(_tempRow, prop, attr);
462    return prop(_tempRow).GetData(buf_);
463  }
464  if (_template.NumProperties() == 1) {
465    PyRowRef::setFromPython(_tempRow, prop, _data[row_]);
466    return prop(_tempRow).GetData(buf_);
467  }
468  Fail(PyExc_ValueError, "Object has no usable attributes");
469  return false;
470  // create a row with just this single property value
471  // this detour handles dicts and objects, because makeRow does
472  /*  c4_Row one;
473  PyView v (prop); // nasty, stack-based temp to get at makeRow
474  v.makeRow(one, _data[row_]);
475  return prop (one).GetData(buf_); */
476}
477
478bool PyViewer::SetItem(int row_, int col_, const c4_Bytes &buf_) {
479  const c4_Property &prop = _template.NthProperty(col_);
480  c4_Row one;
481  prop(one).SetData(buf_);
482
483  PyRowRef r(one); // careful, stack-based temp
484  PyObject *item = r.asPython(prop);
485
486  if (_byPos) {
487    PWOSequence item(_data[row_]);
488    item[col_] = item;
489  } else if (PyDict_Check((PyObject*)_data))
490    PyDict_SetItemString(_data, (char*)prop.Name(), item);
491  else
492    PyObject_SetAttrString(_data, (char*)prop.Name(), item);
493
494  Py_DECREF(item);
495  return true;
496}
497
498static PyObject *PyView_wrap(PyObject *o, PyObject *_args) {
499  try {
500    PWOSequence args(_args);
501    PWOSequence seq(args[0]);
502    PWOSequence types(args[1]);
503    PWONumber usetuples(0);
504    if (args.len() > 2)
505      usetuples = args[2];
506
507    c4_View templ;
508    for (int i = 0; i < types.len(); ++i) {
509      const c4_Property &prop = *(PyProperty*)(PyObject*)types[i];
510      templ.AddProperty(prop);
511    }
512
513    c4_View cv = new PyViewer(seq, templ, (int)usetuples != 0);
514    return new PyView(cv, 0, ROVIEWER);
515  } catch (...) {
516    return 0;
517  }
518}
519
520static PyMethodDef Mk4Methods[] =  {
521   {
522    "view", PyView_new, METH_VARARGS, "view() - create a new unattached view"
523  }
524  ,  {
525    "storage", PyStorage_new, METH_VARARGS, storage__doc
526  }
527  ,  {
528    "property", PyProperty_new, METH_VARARGS,
529      "property(type, name) -- create a property of given type and name"
530  }
531  ,  {
532    "wrap", PyView_wrap, METH_VARARGS,
533      "wrap(seq,props,usetuples=0) - wrap a sequence as a read-only view"
534  }
535  ,  {
536    0, 0, 0, 0
537  }
538};
539
540extern "C"__declspec(dllexport)
541void initMk4py() {
542  PyObject *m = Py_InitModule4("Mk4py", Mk4Methods, mk4py_module_documentation,
543    0, PYTHON_API_VERSION);
544  PyObject_SetAttrString(m, "version", PyString_FromString("2.4.9.7"));
545  PyObject_SetAttrString(m, "ViewType", (PyObject*) &PyViewtype);
546  PyObject_SetAttrString(m, "ViewerType", (PyObject*) &PyViewertype);
547  PyObject_SetAttrString(m, "ROViewerType", (PyObject*) &PyROViewertype);
548  PyObject_SetAttrString(m, "RowRefType", (PyObject*) &PyRowReftype);
549  PyObject_SetAttrString(m, "RORowRefType", (PyObject*) &PyRORowReftype);
550}
551