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