Tải bản đầy đủ - 0 (trang)
Chapter 3. The Apache Module Architecture and API

Chapter 3. The Apache Module Architecture and API

Tải bản đầy đủ - 0trang

5.6StoringStateInformationinSQLDatabases

Persistentmemoryisonlysuitableforstoringsmallamountsofstate

informationforrelativelyshortperiodsoftime.Ifyouneedtoreliablystore

lotsofinformationforalongtime,youneedaserver-sidedatabase.

TheDBIlibrary,designedbyTimBunceandothers,isagenericPerl

interfacetorelationaldatabasemanagementsystems(DBMSs)that

speakSQL(StandardQueryLanguage).TheDBIlibraryspeaksto

specificdatabasesbywayofDBD(DatabaseDriver)modules.Youcan

makequeriesonanydatabasethathasaDBDmoduleavailableforit.

Thesemodulesaresometimesprovidedbythedatabasevendorand

sometimesbythirdparties.DBDmodulesforOracle,Sybase,Illustra,

mSQL,MySQL,andotherscanbefoundatCPAN.

FullinformationonusingDBIcanbefoundinitsmanualpagesandin

AdvancedPerlProgrammingbySriramSrinivasan(O'Reilly&

Associates,1997).We'llsummarizejustenoughheresothatyoucan

followtheexamplesifyou'renotalreadyfamiliarwithDBI.

BeforeyoucanworkwiththeDBIinterface,youmustselectandinstalla

relationaldatabase.IfyouhaveaccesstoaUnixsystemanddonot

alreadyhavesuchadatabaseinstalled,agoodonetostartwithis

MySQL,apopulardatabasemanagementsystemthatyoucanfreely

downloadfromhttp://www.tcx.se/.[7]

[7]MySQLcanbeusedfreelyforsomepurposesbutmustbe



licensed(forareasonableprice)forothers.Pleaseseethelicensing

termsforfulldetails.

Inrelationaldatabases,allinformationisorganizedintables.Eachrowof

thetableisadatarecord,andeachcolumnisafieldoftherecord.For

example,hereisonewaytorepresentthehangmandata:



table:hangman

+----------+--------+-------+------+---+------------+-----+---|session_id|WORD|GUESSED|GAMENO|WON|GUESSES_LEFT|TOTAL|

+----------+--------+-------+------+---+------------+-----+----



|fd2c95dd1|entice|e|10|6|6|34|1998

|97aff0de2|bifocals|aeilort|4|2|3|20|1998

+----------+--------+-------+------+---+------------+-----+---Mostofthecolumnsinthetableabovedirectlycorrespondtothefieldsin

thenow-familiarhangmanstateobject.Inadditiontothesefieldsweadd

twomorecolumns.session_idisastringthatuniquelyidentifieseach

usersessionandisusedasakeyintothetableforfastrecordlookup.

Forreasonsthatwillbecomeapparentsoon,weuseashorthexadecimal

stringasthesessionID.Wealsoaddatimestampfieldnamedmodified

whichholdsthedateandtimeatwhichtherecordwaslastchanged.If

youlookcarefully,you'llseethatthecolumnconsistsofthefour-digityear

andtwodigitseachforthemonth,day,hour,minute,andsecond.This

timestampwillcomeinhandyfordetectingoldunusedsessionsand

clearingthemoutperiodically.

InSQLdatabases,eachtablecolumnhasadefineddatatypeanda

maximumfieldlength.Availabledatatypesincludeintegers,floatingpoint

numbers,characterstrings,date/timetypes,andsometimesmore

esoterictypes.Unfortunatelythedatatypessupportedbydatabase

managementsystemsvaryconsiderably,limitingtheportabilityof

applicationsamongdifferentvendors'products.Inthisandthenext

chapter,ourexamplesuseMySQLdatatypesandfunctions.Youmay

havetomakesomemodificationsinordertosupportanotherdatabase

system.

ThemostbasicwaytocommunicatewithaSQLdatabaseisviaatext

monitorasmallterminal-likeapplicationinwhichyoutypeSQLqueriesto

thedatabaseandviewtheresults.Tocreatethedefinitionforthetable

shownabove,youcouldissuetheSQLCREATEcommand:

mysql>CREATETABLEhangman(

session_idchar(8)primarykey,

WORDchar(30),

GUESSEDchar(26),

GAMENOint,

WONint,

GUESSES_LEFTint,

TOTALint,



modifiedtimestamp

);

ThisdeclaresatablenamedhangmanusingtheMySQLsyntax.The

session_idcolumnisdeclaredtobeastringofatmosteightcharacters,

anditisalsodeclaredtobetheprimarykeyforthetable.Thisensures

thatagivensessionIDisunique,andspeedsuptablelookups

considerably.TheWORDandGUESSEDcolumnsaredeclaredtobe

stringsofatmost30and26characters,respectively,andGAMENO,

WON,GUESSES_LEFT,andTOTALaredeclaredtobeintegers(using

thedefaultlength).Wedeclarethecolumnnamedmodifiedtobea

timestamp,takingadvantageofaMySQL-specificfeaturethatupdates

thefieldautomaticallywhenevertherecordthatcontainsitischanged.

YoucanthenloadsomesampledataintothedatabaseusingaSQL

INSERTstatement:



mysql>INSERTINTOhangman(session_id,WORD,GUESSED,GAMENO,WON,

GUESSES_LEFT,TOTAL)

VALUES('a0000001','spruce','',1,0,6,0);

Thisinsertstheindicatedvaluesforthecolumnssession_idthrough

TOTAL.Wedon'texplicitlysetthevalueofthemodifiedcolumnbecause

MySQLtakescareofthatforus.

WecannowperformsomequeriesoverthedatabaseusingtheSQL

SELECTstatement.

Toseeeverythinginthehangmantable:



mysql>SELECT*FROMhangman;

+----------+--------+-------+------+---+------------+-----+---|session_id|WORD|GUESSED|GAMENO|WON|GUESSES_LEFT|TOTAL|

+----------+--------+-------+------+---+------------+-----+---|fd2c95dd1|entice|e|10|6|6|34|1998

|a0000001|spruce||1|0|6|0|1998

|97aff0de2|bifocals|aeilort|4|2|3|20|1998

+----------+--------+-------+------+---+------------+-----+---ThepartofthequeryfollowingtheSELECTcommandchooseswhich



columnstodisplay.Inthiscaseweuse*toindicateallcolumns.The

FROMkeywordnamesthetabletoselectthedatafrom.

Ifwewishedtolookatjustthesession_id,WORD,andGAMENOfields

fromthetable,wecouldusethisquery:

mysql>SELECTsession_id,WORD,GAMENOFROMhangman;

+------------+----------+--------+

|session_id|WORD|GAMENO|

+------------+----------+--------+

|fd2c95dd|entice|10|

|a0000001|spruce|1|

|97aff0de|bifocals|4|

+------------+----------+--------+

AnoptionalWHEREclauseallowsustofiltertherecordssothatonly

recordsmatchingasetofcriteriaaredisplayed.Forexample,thisquery

showsonlysessionrecordsfromplayerswhohaveplayedfivegamesor

more:



mysql>SELECTsession_id,WORD,GAMENOFROMhangmanWHEREGAMENO

+------------+--------+--------+

|session_id|WORD|GAMENO|

+------------+--------+--------+

|fd2c95dd|entice|10|

+------------+--------+--------+

ThisqueryretrievesthesessionwiththeIDa0000001:



mysql>SELECTsession_id,WORD,GAMENOFROMhangmanWHEREsession

+------------+--------+--------+

|session_id|WORD|GAMENO|

+------------+--------+--------+

|a0000001|spruce|1|

+------------+--------+--------+

Finally,thisqueryretrievesallsessionsthatweremodifiedwithinthepast

24hours:



mysql>SELECTsession_id,WORD,GAMENOFROMhangman

WHEREunix_timestamp()-unix_timestamp(modified)<60*60

+------------+--------+--------+

|session_id|WORD|GAMENO|

+------------+--------+--------+

|a0000001|spruce|1|

+------------+--------+--------+

ThelastexampleshowstheuseoftheMySQL-specificunix_timestamp(

)function.Calledwithoutarguments,unix_timestamp()returnsthe

currenttimeanddateasthenumberofsecondssincethestartofthe

Unixepoch.Thefunctioncanalsobecalledwithatimestampfieldasthe

argument,inwhichcaseitoperatesonthetimestampratherthanthe

currenttime.Theeffectofthequeryaboveistosubtractthemodified

fieldfromthecurrenttimeandcomparethedifferencetooneday.The

SQLlanguageallowsyoutoformqueriesthataresubstantiallymore

complexthanthese,includingonesthatcombinetheresultsofmultiple

tables.Wewon'tdelveintothefullSQLsyntax,butyou'llfindthe

definitivereferenceinAGuidetotheSQLStandardbyC.J.Datewith

HughDarwen(Addison-Wesley,1997),andplentyofpracticalexamples

inAdvancedPerlProgrammingbySriramSrinivasan.

TheINSERTstatementcanonlybeusedtocreateanewrecord(orrow)

ofthetable.Ifweweretotrytoexecutetheinsertionstatementshown

earlierasecondtime,theattemptwouldfailbecauseanygivensession

IDcanonlyoccuronceinthetable.Thisfeatureguaranteesthe

uniquenessofsessionIDs.Tochangethevaluesinanexistingrecord,

wewoulduseanUPDATEstatementinstead.AtypicalUPDATE

statementlookslikethis:

mysql>UPDATEhangmanSETGAMENO=GAMENO+1

WHEREsession_id='a0000001';

QueryOK,1rowaffected(0.09sec)

LiketheSELECTstatement,UPDATEcanhaveaWHEREclausewhich

limitswhatrecordsitaffects.Foreachselectedrecord,columnsare

updatedaccordingtooneormorecolumn=newvaluepairs.Inthe

exampleshownabove,we'reincrementingtheGAMENOcolumnbyone.

ASELECTstatementshowsthattheupdateworked.



mysql>SELECTsession_id,WORD,GAMENOFROMhangman

WHEREsession_id='a0000001';

+------------+--------+--------+

|session_id|WORD|GAMENO|

+------------+--------+--------+

|a0000001|spruce|2|

+------------+--------+--------+

Lastly,theDELETEstatementcanbeusedtodeleteallrecordsthat

satisfythecriteriasetoutintheWHEREclause.Thisquerydeletesall

sessionsolderthanaday:



mysql>DELETEFROMhangman

WHEREunix_timestamp()-unix_timestamp(modified)>60*60*24

QueryOK,2rowsaffected(0.00sec)

IfyouforgettoincludeaWHEREclauseintheUPDATEandDELETE

statements,everyrecordinthedatabasewillbeaffectedbythe

operation.Thisisgenerallytobeavoided.

5.6.1UsingDBI

TheDBIinterfaceprovidesmethodsforopeningSQLdatabases,sending

queriestotheopeneddatabase,andreadingtheanswersreturnedby

thosequeries.

Toopenadatabase,youcallDBI->connect()withthe"datasource

name,"astringthattellsthedatabasedriverwherethedatabaseis

located.Ifthedatabaserequiresausernameandpasswordforaccess,

youcanpassthatinformationintheconnect()callaswell.Theformatof

thedatasourcenameisDBMS-specific.ForaMySQLdatabase,itlooks

likethis:

"dbi:mysql:$database:$hostname:$port"

AllMySQLdatasourcesbeginwith"dbi:mysql".Theyarefollowedbythe

nameofthedatabase,and,optionally,bythenameandportofthe

remotehostonwhichtheDBMSisrunning.Ifthehostnameandportare

omitted,thedriverdefaultstousingastandardportonthelocalhost.To



connecttoadatabasenamedwwwonthelocalhostusingtheusername

gamesandthepasswordgrok,you'dmakethiscall:

$dbh=DBI->connect('dbi:mysql:www','games','grok');

Ifsuccessful,connect()returnsadatabasehandle,$dbh,whichisused

forsubsequentcommunicationwiththedatabase.Theconnect()method

alsoacceptsanoptionalfourthargumentwhichconsistsofahash

referenceofparametername=valuepairs.Thesecontrolavarietyof

databaseoptions,suchaswhethertoautomaticallycommitallchanges

madetothedatabase.Theonlyoptionthatwe'lluseintheexamplesthat

followisPrintError,whichwhensettofalse,suppressestheprintingof

unwanteddatabasewarningstotheservererrorlog.

Thedatabasehandlehasseveralmethods,themostimportantofwhich

aredo(),prepare(),anderrstr().do()isusedtoexecuteSQL

statementswhichdonotreturnalistofrecords,suchasINSERT,

DELETE,UPDATE,orCREATE.Iftheoperationissuccessful,do()

returnsacountofthenumberofrowsmodified.Forexample,the

followingquerysetstheGAMENOfieldofallsessionsto1andreturns

thenumberofrowsaffected:

$count=$dbh->do('UPDATEhangmanSETGAMENO=1');

die$dbh->errstrunlessdefined$count;

Ifthedatabaseencounteredanerrorwhileprocessingthestatement(for

example,theSQLcontainedasyntaxerror),itwillreturnundef.The

errstr()methodcanbeusedtoretrieveaninformativeerrormessage

fromthedriver.

SELECTqueriescanreturnapotentiallylargenumberofrecords,often

morethanwillfitintomemoryatonce.Forthisreason,theresultsfrom

SELECTqueriesarereturnedintheformofstatementhandleobjects.

Youthencallthestatementhandle'sfetch()methodrepeatedlyto

retrieveeachrowoftheresult.

Here'sanexampleofretrievingthesession_idandWORDfieldsfrom

eachsessioninthehangmandatabase:

$sth=$dbh->prepare('SELECTsession_id,WORDFROMhangman')



||die$dbh->errstr;

$sth->execute()||die$sth->errstr;

while(my$row=$sth->fetch){

my($session,$word)=@$row;

print"session=>$session,word=>$word\n";

}

$sth->finish;

Theexamplestartswithacalltothedatabasehandle'sprepare()

methodwiththetextoftheSQLSELECTstatement.prepare()parses

theSQLandchecksitforsyntacticcorrectnessbutdoesnotactually

executeit.Thequeryisreturnedasastatementhandlerwhichwestore

intothevariable$sth.Ifsomeerroroccurredwhilepreparingthe

statement,prepare()returnsundef,inwhichcasewereturntheerrstr()

errortext.

Nextwecallthestatementhandler'sexecute()method.Thisperforms

thequeryandreturnseitherthenumberofrowsretrievedorundefifan

erroroccurred.Inthecaseofasyntacticallycorrectquerythathappens

toreturnnorows(becausethetableisemptyorbecausenorecords

satisfiedthecriteriaintheWHEREclause),execute()returnsthevalue

0E0whichPerlregardsastrueinalogicalcontext,butaszeroina

numericone.

Nowweenteraloopinwhichwecallthestatementhandler'sfetch()

method.Eachtimeit'scalled,fetch()returnstherequestedcolumnsin

theformofanarrayreference.Toretrievethevaluesthemselves,wejust

dereferencethevalueintoalist.Becausewerequestedthecolumns

session_idandWORD,wegetareferencetoatwo-itemarraybackfrom

fetch().Whentherearenomorerowsleft,fetch()returnsundef.

DBIactuallyoffersafamilyoffetchfunctions.fetchrow_array()islike

fetch(),butitdereferencestherowfirstandreturnsanarray

correspondingtothelistofrequestedcolumns.Anotherfunction,

fetchrow_hashref(),turnsthecurrentrowintoahashofthecolumn

namesandtheirvaluesandreturnsthehash'sreferencetothecaller.

Thisallowsustomaketheexampleabovemorereadableatthecostof

makingitsomewhatlessefficient:



$sth=$dbh->prepare('SELECTsession_id,WORDFROMhangman')

||die$dbh->errstr;

$sth->execute||die$sth->errstr;

while(my$row=$sth->fetchrow_hashref){

print"session=>$row->{session_id},word=>$row->{WORD}\n

}

$sth->finish;

DBIalsoprovidesafetchrow_arrayref()methodforfetchingtherowas

anarrayreference.Itisidenticalineveryrespecttofetch().

Whenyouarefinishedwithastatementhandler,youshouldcallits

finish()methodinordertofreeuptheresourcesituses.

Thelastthingyouneedtoknowaboutstatementhandlersisthatmany

DBIdriversallowyoutoputplaceholders,indicatedbythe?character,

insideSQLstatements.prepare()compilesthestatementandreturnsa

statementhandlerasbefore,butwhenyoulatercallexecute()youpass

inthevaluestobesubstitutedintotheplaceholders.Thisallowsyouto

treatstatementhandlersmuchasyouwouldasubroutinebycallingit

repeatedlywithdifferentruntimearguments.Forexample,wecancreate

astatementhandlerforreturningtheentirerowofagivensessionwith

thisbitofcode:



$sth=$dbh->prepare('SELECT*FROMhangmanWHEREsession_id=?'

Nowwecanfetchinformationonsessionfd2c95dd,bycallingthe

statementhandler'sexecute()methodthisway:

$sth->execute('fd2c95dd');

Thesamestatementhandlercanlaterbeusedtofetchinformationfrom

othernamedsessions.Youshouldstillcallfinish()attheendofeach

seriesoffetches,eventhoughyouaregoingtoreusethestatement

handler.Failuretodosocanleadtomemoryleaks.

Whenyouarecompletelyfinishedwithadatabasehandle,youshould

callitsdisconnect()methodinordertosevertheconnectionandclean

up.



5.6.2Apache::DBIandmod_perl

OneoftheproblemswithusingDBIdatabasesfromconventionalCGI

scriptsisthatthere'softenasignificantamountofoverheadassociated

withopeningadatabaseconnection.Whenyourunamod_perl-enabled

versionofApache,youcantakeadvantageofpersistentdatabase

connections.Insteadofcreatinganewdatabasehandleeachtimeyour

ApachePerlmoduleorApache::Registryscriptruns,youcheckaglobal

variableforapreviouslyopenedhandle.Iftheglobalisempty,youopen

anewdatabaseconnection.Otherwise,youusethecontentsofthe

global.Aconcisewayofexpressingthislogiciswiththissnippetofcode:

$DBH||=DBI->connect($data_source,$user,$password);

Apache::DBI,amodulewrittenbyEdmundMergl,makeshandling

persistentdatabaseconnectionseveneasier.ItreplacesDBI'sconnect()

anddisconnect()methodswithversionsthathandlepersistent

connectionsbehindthescenes.connect()maintainsacacheof

databasehandlesandreturnsoneoftheminresponsetoattemptsto

openthesamedatabasemultipletimes.Italsochecksthatthedatabase

handleisstill"live"(somedatabaseshaveanastyhabitoftimingout

inactivesessions)andreconnectsifnecessary.disconnect()isreplaced

byano-opsothatdatabasehandlesarenotinadvertentlyclosed.

ToactivateApache::DBI,youneedonlyuseitsometimebeforeloading

themoduleormodulesthatneedDBIservices.Oneconvenientplaceto

loadApache::DBIisinthePerlstartupfile:

#perlstartupfile

useApache::DBI();

useApache::Registry();

useCGI::Cookie();

...etc.

Ifyoudon'thaveaPerlstartupfile,youcanalsoloadthemoduleat

serverstartuptimebyaddingthisdirectivetooneoftheserver

configurationfiles:



PerlModuleApache::DBI

Youwillnowhavepersistentdatabaseconnectionswhenusing

mod_perl,andconventionaluse-once-and-throw-awayconnectionswhen

usingstandardCGI.

5.6.3ADBIBackendforHangman

Likethepersistentmemoryversionofthehangmangame,theDBI

implementationhastohavecodetoopenthedatabase,tosetandfetch

sessionrecordsfromthedatabase,togenerateuniquesessionIDsfor

eachincomingconnection,andtoexpireoldsessionsthatwe'reno

longerinterestedin.Example5.6showswhat'snewanddifferentonthe

serverside.Therearenovisiblechangesintheuserinterface.

Thisscriptassumesadatabasehasalreadybeensetupthatcontainsa

tablenamedhangmanwiththisstructure:[8]

[8]ThemodifiedfieldisaMySQL-specificdatatype,andlaterwewill



takeadvantageofotherMySQLfeaturesinvolvingthehandlingof

dates.SQLdatabasesvarywidelyintheirhandlingofdatesand

times,andweprefertoshowyouanefficientimplementationofthe

applicationonaspecificdatabasethananinefficientimplementation

thatmightworkmoregenerically.Toportthiscodetothedatabaseof

yourchoice,youwillneedtochangethedatatypeofthemodified

columntoadate/timetypethatyourdatabaseunderstandsand

modifytheexpires()subroutinetoworkwiththischangedtype.

CREATETABLEhangman(

session_idchar(8)primarykey,

WORDchar(30),

GUESSEDchar(26),

GAMENOint,

WONint,

GUESSES_LEFTint,

TOTALint,

modifiedtimestamp,

KEY(modified)

)



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 3. The Apache Module Architecture and API

Tải bản đầy đủ ngay(0 tr)

×