HTML

ÉnKép - takacsot

Friss topikok

  • Karbonade: Az első résszel egyáltalán nem értek egyet, Európában az Operának egyelőre jóval nagyobb részesedé... (2009.04.02. 14:07) My browser war
  • takacsot: @Bővíz László: Természetesen lehet. Javasolni tudom a könyvesboltok polcain az önfejlesztés részl... (2009.01.01. 12:30) Az IQ-m
  • natalie: szerintem nagyon jo könyv. ajánlom midenkinek! (2007.09.12. 01:21) Stephen King: A mobil
  • Ismeretlen_28084: zseniális könyv tényleg (2007.04.10. 15:24) Matt Beaumont: E-sztori
  • Ismeretlen_29217: Pontosan az gond, hogy a teszt kódrészletet nem tudják értelmezni. Konkrétan a paraméterátadási m... (2006.10.18. 08:02) Internyúzás

Query Object

2006.03.12. 19:26 takacsot

Korábban már írtma arról, hogy elkezdtem egy nagyobb lélegzetvételű irományt. Híreket ugyan nem hoztanfelőle, hanem egyből egy részletet. Nem csak ennyi késült el belőle, de ez az első, ami már tartalmaz mindent. Probléma leírást, elmélkedést arról, hogyan is kellene megoldani és persze a megoldás példával. Imhol

Query Object és Interpreter

(http://martinfowler.com/eaaCatalog/queryObject.html)

Miért is van szükségünk? Bizonyos feltételek alapján szeretnénk adatokat kinyerni. Az SQL felétetele sokszor nem jöhet szóba.

Miért is?

Alapvető, hogy nem mindenki ért az SQL-hez. Másrészről amúgy sem jó, ha a kliens kódból engedélyezzük a SQL direkt használatát. De vegyük azt is, hogy nem mindig relációs adatbázis az adatforrásunk.

Megoldás lehet, hogy annyi variációt készítünk a lekérdező eljárásból, amennyi lehetséges lekérdezési variáció lehetséges. De ez sem a legjobb megoldás, hiszen ez is korlátot szab nekünk. De mi van akkor, ha nem akarunk ezzel a limitációval együtt élni? És akkor, ha a lekérdezés dinamikusan készül, mondjuk a felhasználó mindenféle kattintgatással, egérhúzogatással állítja össze a lekérdezést? Igen-igen, ez nem csak adatbázist kotorászó rendszereknél működik, hanem akár készletnyilvántartónál is. Igaz itt nem azt fogja összeállítani, hogy a KSZLT tábla NM mezője, hanem valami olyasmit, hogy Készlet->Name (ez a példa persze csak szöveges reprezentációja a tényleges objektumoknak).

Ok! Szépen felépítjük a Query Objectumot majd miután átkerült a célrendszerbe (gatewayen keresztül) ott a rendszer sajátos igénye alapján feldolgozzuk, azaz interpretáljuk, mint egy sajátos szintaxisú nyelvet. Ha adatbázisból kérdezünk, akkor megfelelő SQL lekérdezéssé alakul, de ha valami más, akkor „kézzel” interpretáljuk a filtert.

A fentiek alapján egyértelmű, hogy egy QO feldolgozása az Interpreter tervezési minta egy megvalósítása. Könnyen arra az elhatározásra is juthat az ember, hogy ha különböző rendszerekben, implementációkban használjunk egy az egyikben sem jelen lévő interfészt akkor akár Adapter is lehet de nem így van. Igaz, hogy különféle külső rendszert egy új, közös interfész mögé tesszük, de itt nem a külső szolgáltatást használjuk, hanem az használja a mi objektumainkat. Kicsit fordított a helyzet. Persze ha arról lenne szó, hogy egy adott SQL dialektusba alakítjuk, akkor már szóba jöhet az adapter is, de csak az interpreteren belül.

Az alábbiakban egy egyszerű implementációt mutatok be. Sajátosan nem a szótárprogram keretein belül, mert ott nem használtam ilyen megoldást. Az implementációs nyelv a Python (ahány nyelvet beszélsz annyi ember vagy, ez igaz a programozási nyelvekre is, minden nyelv tágítja a szakmai nézőpontot).

Kezdjük a domain objectumainkkal és a többivel. Ahol kellhet adok magyarázatot.

 

 """  create table Task(      task_id integer,      task_name varchar(256),      task_desc clob,      task_due date,      task_finished char(1)  );  """  #Domain Objects    class Task:      def __init__(self,id,name,description,dueDate,finished):          self._id = id          self._name=name          self._description=description          self._dueDate=dueDate          self._finished=finished      def id(self):          return self._id       def name(self):          return self._name          def description(self):          return self._description         def dueDate(self):          return self._dueDate          def finished(self):          return self._finished      def setId(self,id):          self._id = id      def setName(self,name):          self._name  = name        def setDescription(self,description):          self._description   = description      def setDueDate(self,dueDate):          self._dueDate   = dueDate      def setFinished(self,finished):          self._finished  = finished                      # Query Objects  class Where:      def __init__(self, condition):          self._cond = condition      def condition(self):          return self._cond                      class Comparison:      def __init__(self,left,right):          self._left = left          self._right = right      def left(self):          return self._left      def right(self):          return self._right    class Equal(Comparison):      pass    class GreaterThen(Comparison):      pass        class LessThen(Comparison):      pass  class And(Comparison):      pass  class Or(Comparison):      pass 

 

Jelen esetben csak levélelemből építjük fel a lekérdezés feltételét. Az egyik a konstans a másik pedig a mező definíció. Az elős nem sok gondot okozhat. A mező definició értelem szerűen lehet egymáshoz és konstanshoz hasonlítani. Fentebb már látható volt, hogy egyenlőség, kissebb és nagyobb relációkban használható. A példák alapján nagyon egyszerűen lehet implementálni a többit is. Egy kissebb-egyenlő például összeépíthető egy Egyual és LessThen Or-ral kombinálva.

 

 class Constant:      def __init__(self, value):          self._value = value      def value(self):          return self._value    class FieldDef:      def __init__(self,className,attribute):          self._className = className          self._attr = attribute      def className(self):          return self._className      def attribute(self):          return self._attr  

 

 

Csak egy interpretert készítettem, azt, ami SQL WHERE kódrészletre fordítja le a QO-t. Fő eljárás az sqlFreg(). A fregParsers egy map, amiben a konkrét query object-hez rendelt sql fregment generálókat rendeljük össze. A továbbiakban pedig a konkét generáló eljárások találhatóak. Kb úgy képzelhető el, mint egy ha eljárásra mutató pointerek lennének a map value-i, de itt valójában egyszerübb a helyzet, hiszen ay eljárás is csak egy objektum pythonban.

 

 fregParsers = {  'Where': whereFreg  ,'Equal': equalFreg  ,'FieldDef': fieldDefFreg  ,'GreaterThen': greaterThenFreg  ,'LessThen':lessThenFreg  ,'Constant': constantFreg  ,'And': andFreg  ,'Or': orFreg  }       #Interpreters  def sqlFregment(freg):          className = freg.__class__.__name__      f = fregParsers[className]      return f(freg)    def whereFreg(where):      s = 'where '      s+=sqlFregment(where.condition())      return s        def equalFreg(equal):      s = '('      s+=sqlFregment(equal.left())      s+='='      s+=sqlFregment(equal.right())      s+=')'      return s    def greaterThenFreg(gthen):      s = '('      s+=sqlFregment(gthen.left())      s+='>'      s+=sqlFregment(gthen.right())      s+=')'      return s        def lessThenFreg(lthen):      s = '('      s+=sqlFregment(lthen.left())      s+='<'      s+=sqlFregment(lthen.right())      s+=')'      return s  def andFreg(lthen):      s = '('      s+=sqlFregment(lthen.left())      s+=' and '      s+=sqlFregment(lthen.right())      s+=')'      return s  def orFreg(lthen):      s = '('      s+=sqlFregment(lthen.left())      s+=' or '      s+=sqlFregment(lthen.right())      s+=')'      return s  def constantFreg(const):      if const.value().__class__.__name__ == 'str':          return "'"+const.value()+"'"      return str(const.value())   

 

Az igazán érdekes eljárás az, amelyik összerendeli a business objectumok attribútumait az adatbázis táblák mezőivel.

 

 attr2field = {  'Task.id':'task_id'  ,'Task.name':'task_name'  ,'Task.description':'task_desc'  ,'Task.dueDate':'task_due'  ,'Task.finished':'task_finished'  }    def fieldDefFreg(fieldDef):      return attr2field[fieldDef.className()+'.'+fieldDef.attribute()]    import unittest  class TestQa(unittest.TestCase):      def testFieldDefFreg(self):          self.assertEqual('task_id',fieldDefFreg(FieldDef('Task','id')))          self.assertEqual('task_name',fieldDefFreg(FieldDef('Task','name')))          self.assertEqual('task_desc',fieldDefFreg(FieldDef('Task','description')))          self.assertEqual('task_due',fieldDefFreg(FieldDef('Task','dueDate')))          self.assertEqual('task_finished',fieldDefFreg(FieldDef('Task','finished')))      def testFieldDefFregFail(self):          try:              fieldDefFreg(FieldDef('Project','decription'))              self.fail()          except:              pass      def testConstant(self):          self.assertEqual ('1',constantFreg(Constant(1)))      def testEqual(self):          self.assertEqual('(2=1)',equalFreg(Equal(Constant(2),Constant(1))))      def testGreaterThen(self):          self.assertEqual('(2>1)',greaterThenFreg(GreaterThen(Constant(2),Constant(1))))      def testLessThen(self):          self.assertEqual('(2<1)',lessThenFreg(LessThen(Constant(2),Constant(1))))      def testComplex(self):          self.assertEqual(          "where ((task_id>1) and (task_name='BORGR'))",          sqlFregment(Where(And(GreaterThen(FieldDef('Task','id'),Constant(1)),Equal(FieldDef('Task','name'),Constant("BORGR")))))          )        if __name__ == "__main__":      print 'hello'      unittest.main() 

 

A fenitekkel majdnem végeztem, de azért pár megjegyzést még elejtenék. Van pár hiba, amit teljesen tudatosan hagytam benne. Egyetlen helyen sem vizsgálom, hogy a query obejctumok milyen típusokkal épül össze. Triviálisan behelyezhető, de felesleges foglalta volna a helyett, mert a lényeget akartam bemutatni.

Eredetileg több táblás pédát akartam csinálni de ugyebár ez előhozta a különféle JOIN-ok kérdését. Egy equijoin könnyen megvalósítható, hiszen csak a kapcsolatokat kellett volna nyílván tartani és az egyenlőségeket autómatikusan belegenerálni. De a különféle outer joinok már érdekesebbek lettek volna.

Remélem a lényeget azért rendesen be tudtam mutatni.

Kritikára fel, Fiatalok!

Szólj hozzá!

A bejegyzés trackback címe:

https://takacsot.blog.hu/api/trackback/id/tr58775727

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása