Sledování počtu dotazů SQL v MySQL

26. 7. 2008 v 01.18   Roman Mackovcak

Vytvořil jsem novou aplikaci na platformě Grails a k mému překvapení byla v produkčním prostředí pomalejší než na notebooku. Administrátor z provozu zjistil, že aplikace generuje příliš mnoho dotazů SQL. Zpomalení bylo způsobované hlavně latencí sítě. Musel jsem tedy najít způsob, jak sledovat počet dotazů SQL na serveru MySQL.

Ke sledování aktuální relace (session) se hodí následující příkaz:

SHOW STATUS WHERE variable_name='Com_select'

Ke sledování všech relací (sessions) existuje přepínač:

SHOW global STATUS WHERE variable_name='Com_select'

Všechny proměnné lze vypsat pomocí

SHOW STATUS

Živé vyhledávání v textu u Ruby on Rails

18. 7. 2008 v 23.46   Roman Mackovcak

Před časem jsem slíbil napsat krátký kurz k živému vyhledávání. Vyhledávání v textech, které průběžně zobrazuje výsledky během psaní dotazu.

Připravte si:

  • Ruby on rails
  • gem ferret (gem install ferret)
  • gem acts_as_ferret (gem install acts_as_ferret)
  • plugin auto_complete (v kořenovém adresáři aplikace spustit: ruby script/plugin install auto_complete)

Jak budeme postupovat

  1. Vytvoříme prázdnou aplikaci - jednoduchou databázi knížek
  2. Přidáme možnost vyhledávat v textech
  3. Zprovozníme živé vyhledávání
    1. Vytvoříme dílčí formulář (partial) pro vyhledávání - tam se bude zadávat dotaz
    2. Vytvoříme dílčí panel (partial) pro výsledky - tam budou nabízeny mezivýsledky hledání
    3. Upravíme controller tak, aby reagoval na vyhledávací dílčí formulář

Vytvoření databáze knížek

Vytvoříme malou aplikaci pro správu knihovničky. Bude uchovávat seznamy knížek, umožní nám je vypisovat a aktualizovat a nabídne i živé vyhledávání.
Pojďme tedy vytvořit kostru aplikace:

# Vytvor aplikaci v Rails
rails books
# vytvor databazi knih
echo "create database books"  | mysql -u root -p
cd books

Nyní nastavíme jméno a heslo do databáze v souboru app/config/database.yml.

development:
  adapter: mysql
  database: books
  username: root
  password: heslo
  host: localhost
  port: 3306

Dále vytvoříme kostru aplikace. V kořenovém adresáři aplikace spustíme:

ruby script/generate scaffold Book title:string abstract:text

Vytvoříme tabulku knih

rake db:migrate

Nastartujeme vývojový server

ruby script/server

A teď namíříme prohlížeč na http://127.0.0.1:3000/books a pořídíme nějaká ukázková data.

Přidání možnosti vyhledávat v textech

Změníme app/models/book.rb tak, aby podporoval vyhledávání v textu

require "acts_as_ferret"
 
class Book < ActivoRecord::Base
    acts_as_ferret
end

V konzoli si můžete ověřit, že vyhledávání je už opravdu povolené. Nastartujte konzolu příkazem ruby script/console a zadejte tam

Book.find_by_contents("kniha").

Mělo by to vrátit podobný seznam výsledků jako u nás:

=> #<ActsAsFerret::SearchResults:0x2540f54 @results=[#<Book id: 2, title: 
"Kniha promen", abstract: "Kniha o knize", created_at: "2008-07-07 23:16:38", 
updated_at: "2008-07-07 23:16:38">, #<Book id: 1, title: "Prvni kniha", 
abstract: "Toto je ma prvni knizka", created_at: "2008-07-07 23:16:23", 
updated_at: "2008-07-07 23:16:23">], @total_hits=2>

Vytvoření živého hledání

Nakonec vytvoříme živé vyhledávání.

Dílčí formulář pro vyhledávání

K zobrazení vyhledávacího formuláře se použije partial search_pane.

Vytvořte dílčí formulář (partial) _search_pane.html.erb v podadresáři app/views/books a dejte do něj jednoduchou značku. Tato značka generuje automatický doplňovač v Ajaxu, který volá metodu auto_complete_for_search_query standardního controlleru (v našem případě books)

<%= text_field_with_auto_complete :search, :query %>

Přidejte do šablony knihovny app/views/layouts/books.html.erb deklaraci include pro javascript a zobrazování dílčího formuláře.

Nezapomeňte, že include pro javascript musí být v hlavě (head) šablony.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Books: <%= controller.action_name %></title>
  <!-- TADY -->
  <%= stylesheet_link_tag 'scaffold' %>
	<%= javascript_include_tag :defaults %> 
</head>
<body>
 
<!-- A TADY -->
<%= render :partial=>"books/search_pane" %>
 
<p style="color: green"><%= flash[:notice] %></p>
 
<%= yield  %>
 
</body>
</html>

Vytvoření dílčího panelu pro výsledky

Dílčí panel search_results zformátuje výsledky vyhledávání v textu a „nabídne“ uživateli mezivýsledky. Vytvořte partial app/views/books/_search_results.html.erb a přidejte do něj formátovací kód:

<ul>
	<% for book in @books %>
	<li><%= link_to h(book.title), :controller=>"books", :action=>"show", :id=>book %></li>
  	<% end %>
</ul>

Úprava controlleru

Přidejte následující řádek na začátek souboru books_controller.

protect_from_forgery :only => [:create, :update, :destroy]

Vytvořte metodu controlleru, která bude hledat knížky

 def auto_complete_for_search_query
   @books = Book.find_by_contents(params["search"]["query"]+"*", {:limit => 5})
   render :partial => "search_results"
 end

Nechceme generovat kompletně sesazenou stránku, což musíme upřesnit v controlleru books:

layout 'books', :except => [:auto_complete_for_search_query]

Tak, a nyní nasměrujte prohlížeč na http://127.0.0.1:3000/books a začněte hledat. Jakmile zadáte do políčka první znaky, zobrazí se výsledky. Klepněte na jeden z nabízených odkazů a sledujte, co se stane. Zdrojový kód je zde.

Inicializace integračních testů Grails

26. 6. 2008 v 23.30   Roman Mackovcak

Testovací prostředí lze snadno nainicializovat tak, aby bylo stejné jako vývojové. Stačí použít Bootstrap.

class BootstrappedTests extends GroovyTestCase {
  void testBootstrap() {
    new BootStrap().init(null) 
    assert MyDomain.count() == 5   // Zde uz lze provadet testy
    }
}

Vlastní iterační značka v Grails s pojmenovanou proměnnou

18. 6. 2008 v 23.50   Roman Mackovcak

Ukážu Vám, jak vytvořit iterační značku v Grails, která může obsahovat jinou značku. Vnitřní značka pak použije proměnnou z iterační značky. Vytvoříme si tedy například značku, která vytvoří x odkazů ve tvaru „/show/1“, „/show/2“, atd. s popisem „Příspěvek číslo 1“, „Příspěvek číslo 2“ atd.:

Pro začátek existuje pěkný a šikovný příklad jednoduché iterační značky na stránkách Grails.

Definice:

def repeat = { attrs, body ->
  def i = Integer.valueOf( attrs["times"] )
  def current = 0
  i.times {
    // predej cislo aktualni iterace standardnim argumentem groovy nazvanym "it"
    // a vysledek pote predej do "out", odkud se vypise do pohledu
    out << body( ++current )
  }
}

Příklad použití:

<g:repeat times="3">
  <p>Opakuj 3-krát! Toto je opakování č. ${it}</p>
</g:repeat>

Jednoduchá úprava značky nevede k požadovanému chování:

<g:repeat times="3"> 
  <g:link action="show" id="${it}">Ahoj číslo  ${it}</g:link> 
</g:repeat>

Takto se vygeneruje jenom třikrát řetězec „Ahoj číslo “, protože proměnná „it“ je tu neznámá.
Je proto zapotřebí upravit definici značky. Přidejme další parametr „var“ jako symbolické jméno aktuálního člena kolekce pro danou iteraci.

def repeat = { attrs, body -> 
  def pars = [:]
 
  attrs.times?.toInteger().times { n -> 
    pars[attrs.var] = n
    out << body(pars) 
  } 
}

Teď můžeme použít značku g:link i uvnitř naší nové značky:

<g:repeat times="3" var="num"> 
  <g:link action="show" id="${num}">Ahoj číslo ${num}</g:link> 
</g:repeat>

Nejsem si jistý, zda je toto chování podporované, ale v každém případě funguje.

Z Mysql na Oracle: migrace aplikace v Grails

30. 5. 2008 v 23.19   Roman Mackovcak

Nedávno jsem dokončil migraci našeho prototypu v Grails. Původně jsme ho vyvíjeli na MySQL, ale finální systém musí pracovat na Oracle. V tomto příspěvku shrnu potíže a rozdíly, na které jsem při migraci DB narazil.

Vytvoření uživatele

Jelikož nejsem administrátorem Oracle, chvíli mi trvalo, než jsem dal dohromady skript, který vytvoří uživatele a příslušná práva pro aplikaci v Grails. Tento skript je pouze pro vývoj.

CREATE user grails IDENTIFIED BY grassword;
GRANT connect  TO habilion;
GRANT CREATE TABLE TO habilion;
GRANT CREATE sequence TO habilion;
GRANT unlimited tablespace TO habilion;

Omezení délky identifikátoru

Obvykle nepoužívám identifikátory dlouhé 30 znaků. Ale je tu chyták.
Představte si dvě třídy: Author a Content. Autor může tvořit různý textový obsah.
Představte si teď vícero různých typů obsahu - kniha, noviny, elektronické noviny, vědecký článek…
Tuto situaci můžeme tedy namodelovat následujícími třídami:

Class Content {
  static belongsTo = [ contentAuthor : Author ]
}
 
Class Book extends Content {}
Class Newspaper extends Content {}
Class ElectronicNewspaper extends Content {}
Class ScientificArticle extends Content {}
 
Class Author{
  static hasMany = [ contents : Content ]
}

A teď ten chyták: databázová tabulka ‘content’ obsahuje dlouhé sloupce:

content_author_id
newspaper_content_author_id
electronic_newspaper_content_author_id
book_content_author_id
scientific_article_author_id

Jak je vidět, není vůbec těžké dostat se na hranici 30 znaků. Když se na to podíváte podrobněji, uvidíte řešení. Názvy sloupců jsou složeny z názvu třídy dědící od Content a ze jména proměnné ukazující na nadřízenou entitu.
Přejmenoval jsem tedy contentAuthor na ca, vytvořil getter a setter pro vlastnost contentAuthor a zkontroloval všechny kusy kódu typu “new Book…”. Ať žije MVC!

Velikost sloupce

Druhým velkým problémem byl sloupec String. Měl jsem sloupec s řetězci do 10 000 znaků.

Class Content {
  String abstract
  static constraints = { abstract(maxSize:10000) }
}

Ten se přeložil do datového typu Long. Bohužel je v ovladači Oracle JDBC chyba, která vyvolává při zpracování datového typu Long velmi ošklivé chybové hlášky. Na webu je nějaké náhradní řešení, ale funguje jen s některými verzemi Oracle. Navíc je povolen nejvýš jeden sloupec typu Long v každé tabulce. Změnil jsem tedy velikost na 4000 znaků a sloupec se vytvořil jako VARCHAR2.

A je to!

Aktualizováno: Prázdné řetězce

Musím ještě zmínit fakt, že Oracle nerozlišuje mezi hodnotou null a prázdným řetězcem. Když se tedy pokoušíte uložit do databáze Oracle prázdný řetězec, uloží se ve skutečnosti jako hodnota null.

Problém nastává, když máte sloupec, který nesmí obsahovat null a pokusíte se uložit prázdný řetězec „“. Pro Grails to není hodnota null a nepoužije se tedy předdefinovaná hodnota. Pro Oracle je to null a tudíž porušení integritního omezení…

Vítejte

30. 5. 2008 v 17.03   Miroslav Škultéty

Vítejte na stránkách Zmok napsal CZ!