h1. Vyhledávání {{>toc}} Komplexní podpora dotazování tvoří jednu ze základních funkcionalit databází. Oproti tomu většina běžných RESTových služeb nenabízí moc silné prostředky pro dotazování a omezuje se pouze na triviální předdefinované dotazy, příp. fulltextové vyhledávání. Ovšem mají-li webové služby IS(informačního systému) sloužit aplikacím jako přímý ^1^ zdroj dat, tak je komplexnější podpora vyhledávání prakticky nezbytná. h2. Předdefinované dotazy Všechny zdroje s parametrem v URI(Uniform Resource Identifier) jsou v podstatě předdefinované vyhledávací dotazy. Kupříkladu "/units/18000":https://kosapi.fit.cvut.cz/api/3/units/18000/ vyhledá _organizační jednotku_ s kódem _18000_. Na pozadí dojde k vygenerování _SELECTu_ nad tabulkou nákladových středisek, kde kód střediska je rovný 18000. To je poměrně triviální dotaz. Trochu složitější se skrývá například za "/programmes/MI/courses":https://kosapi.fit.cvut.cz/api/3/programmes/MI/courses, který vyhledá všechny _předměty_ patřící pod _studijní program_ s kódem _MI_. Zde se vygeneruje polospojení nad programy, spojovou tabulkou a předměty, kde program má kód rovný MI. Omezení takovýchto dotazů jsou zjevná. Co když potřebujeme například vyhledat všechny předměty, které se vyučují v zimním semestru, zajišťuje je Katedra softwarového inženýrství FIT a jejich název obsahuje slovo „prog“? Tady už potřebujeme nějaký dotazovací jazyk, který nám umožní kombinovat podmínky. h2. RSQL *UPOZORNĚNÍ: Integrace RSQL ještě není úplně dokončená, takže pro některé zdroje a konkrétní atributy nemusí fungovat správně!* RSQL(RESTful Service Query Language) je dotazovací jazyk a knihovna, jež jsem vyvinul pro KOSapi, která umožňuje vyhledávat záznamy (Atom Entry) podle jejich strukturovaných elementů (atributů) v Atom Content. Všechny zdroje KOSapi jsou koncipované tak, že Atom elementy využívají pouze pro metadata a vlastní data z KOSu jsou obsažená v Atom Content ve strukturované podobě (závisí na _Content-Type_, výchozí je XML(Extensible Markup Language)). RSQL(RESTful Service Query Language) dotazy se v KOSapi překládají na SQL(Structured Query Language) dotazy do KOSu. Inspiroval jsem se "Feed Item Query Language":http://tools.ietf.org/html/draft-nottingham-atompub-fiql-00 (FIQL), což je IETF(Internet Engineering Task Force) návrh dotazovacího jazyka určeného (pouze) pro vyhledávání záznamů podle „metadat“ v Atom Entry. Syntaxe FIQL(Feed Item Query Language) je výhodná svým prvoplánovým určením pro zápis v URI(Uniform Resource Identifier), díky čemuž ji není potřeba zakódovávat. Na druhou stranu je tím poněkud neobvyklá a ne příliš intuitivní. Jelikož jsem si stejně musel napsat vlastní parser, rozhodl jsem se tuto syntaxi využít a rozšířit ji ještě o alternativní zápis. Proč jsem vlastně vyvíjel vlastní řešení a nevyužil nějaké standardizované? Důvod je prostý, žádné takové kupodivu zatím neexistuje nebo jsem ho nenašel. Tedy kromě standardu "Open Data Protocol":http://www.odata.org, který mimo jiné zahrnuje komplexní podporu pro dotazování. Ovšem využití OData pro KOSapi jsem z několika důvodů zavrhl a vzhledem k tomu, že podpora vyhledávání je jeho „inherentní“ součástí, tak mi její _samostatné_ využití nepřišlo přínosné. h3. Gramatika a sémantika RSQL výraz se skládá z jednoho či více _kritérií_, které se spojují logickými (Booleovskými) operátory. expression = [ "(" ], ( constraint | expression ), [ logical-operator, ( constraint | expression ) ], [ ")" ]; Logické operátory jsou: * AND : "@;@" podle FIQL, nebo alternativní "@ and @" * OR : "@,@" podle FIQL, nebo alternativní "@ or @" logical-operator = ";" | " and " | "," | " or "; Operátor AND má standardně přednost, tj. všechny operátory OR se vyhodnocují až po něm. Toto chování lze samozřejmě změnit pomocí uzávorkování výrazů. Kritérium se skládá ze selektoru, který identifikuje element v Atom Content, operátoru porovnání a argumentu. constraint = selector, comparison-operator, argument; Operátory porovnání jsou: |_. Název |_. FIQL |_. Alternativní |_. Platné datové typy | | rovná se | @==@ | @=@ | textový řetězec, číslo, datum, výčtový typ, XLink | | nerovná se | @!=@ | @!=@ | textový řetězec, číslo, datum, výčtový typ, XLink | | menší než | @=lt=@ | @<@ | číslo, datum | | menší nebo rovno | @=le=@ | @<=@ | číslo, datum | | větší než | @=gt=@ | @>@ | číslo, datum | | větší nebo rovno | @=ge=@ | @>=@ | číslo, datum | comparison-operator = "==" | "=" | "!=" | "=lt=" | "<" | "=le=" | "<=" | "=gt=" | ">" | "=ge=" | ">="; Selektor odpovídá názvu elementu v Atom Content nebo jeho relativní cestě, pakliže je zanořený. Může také obsahovat [[Query#„Dereference“-vazeb-aka-JOIN|„dereferenci“]] XLink vazby pomocí tečkové notace. selector = identifier, { ("/" | "."), identifier }; identifier = ? ["a"-"z","A"-"Z","_","0"-"9","-"]+ ? Argumenty mohou být dvojího typu. Libovolná sekvence znaků uzavřená mezi jednoduché či dvojité uvozovky, nebo sekvence znaků bez mezer, kulatých závorek, čárek a středníků. argument = arg_ws | arg_sq | arg_dq; argument-ws = ? ( ~["(", ")", ";", ",", " "] )+ ?; argument-sq = ? "'" ~["'"]+ "'" ?; argument-dq = ? "\"" ~["\""]+ "\"" ?; Porovnávání textových řetězců nezohledňuje velikost písmen (je _case insensitive_). Pokud je URL(Uniform Resource Locator) parametr [[URLParameters#multilang|multilang]] nastaven na @true@, tak zohledňuje texty v obou jazycích (neplatí pro [[Query#„Dereference“-vazeb-aka-JOIN|dereferencované]] atributy). V opačném případě vyhledává pouze ve zvoleném jazyce (podle Accept-Language, nebo [[URLParameters#lang|lang]]). Při porovnávání řetězců lze využít i _divoké karty_ a hledat pomocí nich i jen podle části řetězce. Způsob zápisu je stejný jako v SQL _LIKE_, pouze s tím rozdílem, že místo @%@ se zde používá @*@. Například podmínce @name=prog_am*@ vyhoví všechny předměty, jejichž název začíná na „prog“, následuje jeden libovolný znak, pak „am“ a cokoli (opět bez ohledu na velikost písmen). V případě elementů, které reprezentují výčtový typ, je nutné jako argument uvádět _výčtový název_ (enum), nikoli jeho lokalizovaný popis. Argumentem pro XLink je identifikátor záznamu použitý v URI(Uniform Resource Identifier), což většinou bývá kód, nebo ID. h3. „Dereference“ vazeb (aka JOIN) V dotazu je možné přistupovat i k atributům _odkazovaných_ zdrojů (na které vede XLink) pomocí tzv. „dereference“. Jinak řečeno umožňuje zápis podmínky s _implicitním_ spojením (_JOINem_) entit, mezi kterými existuje explicitní průchozí vazba (obdobně jako v HQL(Hibernate Query Language)). Vazbami se prochází pomocí tečkové notace a je možné i zanořování. Kupříkladu @unit.unitType==FACULTY@ vybere všechny záznamy, které jsou ve vztahu s organizační jednotkou _typu_ fakulta. Na pozadí dojde k vygenerování polospojení (_LEFT JOIN_) tabulky předmětů s tabulkou nákladových středisek a podmínky unitType=FACULTY. Mějte prosím na paměti, že tyto dotazy mohou generovat velkou zátěž databáze. Jakmile bude KOSapi napojené přímo na KOS, bude tento problém dost citlivý. Používejte je proto obezřetně a vyhýbejte se zbytečně neefektivním dotazům. Z těchto důvodů jsem také omezil maximální počet implicitních _JOINů_ pro dotaz na 3. h3. Parametry RSQL(RESTful Service Query Language) výraz se zapisuje do URL(Uniform Resource Locator) parametru [[URLParameters#query|query]] a je možné ho efektivně kombinovat s parametry [[URLParameters#startIndex|startIndex]], [[URLParameters#maxResults|maxResults]] a [[URLParameters#orderBy|orderBy]]. h3. Příklady * "/courses?query=name==*prog*":https://kosapi.fit.cvut.cz/api/3/courses?query=name=%2Aprog%2A - vrátí předměty, jejichž název obsahuje „prog“ * "/courses?query=name=='programování v*'":https://kosapi.fit.cvut.cz/api/3/courses?query=name==%27programov%C3%A1n%C3%AD%20v*%27 - vrátí předměty, jejichž název začíná na „programování v“ * "/courses?query=credits>5":https://kosapi.fit.cvut.cz/api/3/courses?query=credits%3E5 - vrátí předměty za více než 5 kreditů * "/courses?query=season==WINTER;(completion==CLFD_CREDIT,completion==CREDIT)":https://kosapi.fit.cvut.cz/api/3/courses?query=season==WINTER;%28completion==CLFD_CREDIT,completion==CREDIT%29 - vrátí předměty, které se vyučují v zimním semestru a jsou zakončené klasifikovaným zápočtem nebo zápočtem * "/courses?query=department.unitType==FACULTY":https://kosapi.fit.cvut.cz/api/3/courses?query=department.unitType==FACULTY - vrátí předměty, které zajišťuje přímo libovolná fakulta (tzn. organizační jednotka typu fakulta) * "/teachers?query=extern==true&orderBy=lastName&maxResults=50":https://kosapi.fit.cvut.cz/api/3/teachers?query=extern==true&orderBy=lastName&maxResults=50 - vrátí vyučující externisty, seřadí je podle příjmení a výstup omezí na max. 50 záznamů h3. Pár slov k implementaci RSQL(RESTful Service Query Language) jsem vyvinul speciálně pro KOSapi, ale jeho návrh a implementace je dostatečně obecná i pro použití v jiných RESTových službách postavených nad relační databází. Skládá se ze dvou navazujících knihoven. První je _RSQL-parser_, který provádí lexikální analýzu, parsování a sestavení objektové reprezentace zadaného RSQL(RESTful Service Query Language) výrazu. Součástí je gramatika zapsaná v "JavaCC":http://javacc.java.net/, ze které je vygenerován vlastní parser. Druhou knihovnou je _RSQL-hibernate_. Ta zajišťuje převod dotazu na "Hibernate Criteria Query":http://docs.jboss.org/hibernate/core/3.5/reference/en/html/querycriteria.html (objektová reprezentace HQL(Hibernate Query Language), resp. SQL(Structured Query Language) dotazu), z něhož se následně generuje SQL(Structured Query Language) dotaz do relační databáze. V tomto procesu hrají hlavní roli _RSQLCriteriaBuilder_, sada _CriterionBuilders_ a _Mapper_. _CriteriaBuilder_ prochází strom výrazu, generuje _Criterion_ pro logické výrazy (AND, OR) a deleguje _kritéria_ (porovnání) na odpovídající _CriterionBuilder_. Ty má připravené v kolekci, jíž iteruje dokud nenalezne takový, který umí obsloužit daný selektor a operátor. Kromě obecného _CriterionBuilder_ obsahuje například takový, který umí vytvořit _Criterion_ pro atribut vazby (i s NaturalID), multijazyčný text, selektor s implicitním JOINem, příp. speciální pro nestandardní entity. _Mapper_ zajišťuje mapování _selektorů_ (názvů v XML, příp. cest) na názvy příslušných _atributů_ v entitách. Většina odpovídá 1:1, ale v některých případech je nutné použít přemapování (např. multijazyčné texty). Obě knihovny jsem uvolnil pod licencí LGPL a umístil na GitHub - "RSQL-parser":https://github.com/jirutka/rsql-parser a "RSQL-hibernate":https://github.com/jirutka/rsql-hibernate. __ ^1^ To znamená, že aplikace si nebudou uchovávat lokální kopii celé ani části databáze IS (cache se tím nevylučuje), ale budou je přímo získávat z webové služby.