Obsah
1. Programovací jazyk Go a relační databáze
2. Instalace balíčku go-sqlite3
3. Vytvoření testovací databáze používané demonstračními příklady
4. Kostra aplikace, která se připojí k relační databázi
5. Specifikace ovladače databáze i způsobu připojení k databázi
6. Základní typ dotazu – query
7. Typové konverze a hlášení chyb ve chvíli, kdy konverzi nelze provést
9. Vylepšení předchozího příkladu – použití ukazatele na prvky datové struktury
12. Přidání nového zákazníka do databáze
13. Vymazání zákazníka z databáze
14. ORM v programovacím jazyce Go
15. Získání seznamu všech zákazníků
16. Specifikace jména sloupce v databázové tabulce
17. Vytvoření nového zákazníka přes ORM
18. Vymazání zákazníka přes ORM
19. Repositář s demonstračními příklady
1. Programovací jazyk Go a relační databáze
Již v úvodních částech seriálu o programovacím jazyce Go jsme si řekli, že se tento jazyk velmi často a s úspěchem používá pro implementaci různých webových služeb a mikroslužeb (této problematice jsme se ostatně věnovali i minule). Ovšem mnohé služby pochopitelně potřebují používat nějakou formu databáze pro perzistentní data. Může se jednat o relační databáze, dokumentové databáze, grafové databáze, objektové databáze atd. Dnes si ukážeme, jakými způsoby je možné z programovacího jazyka Go přistupovat k relačním databázím. Pro jednoduchost použijeme databázi SQLite (tato databáze nevyžaduje samostatně běžícího démona), ovšem všechny níže uvedené demonstrační příklady budou pracovat i s jinými relačními databázemi; pouze bude pochopitelně nutné změnit jméno ovladače a řetězec specifikující parametry připojení k databázi (popř. upravit sekci import pro načtení nového balíčku s ovladačem).
Nejprve se seznámíme s nízkoúrovňovým přístupem k relačním databázím. Při použití tohoto přístupu je nutné explicitně zapisovat všechny SQL příkazy, předávat jim parametry a popř. explicitně načítat a zpracovávat jednotlivé záznamy vrácené dotazem SELECT. V některých případech je tento přístup velmi užitečný, protože například umožňuje snadné optimalizace dotazů. Ve druhé části článku si ukážeme přístup odlišný, který spočívá ve využití ORM, tedy mapování struktur psaných v Go se záznamy v databázi.
2. Instalace balíčku go-sqlite3
Ve všech dále popsaných demonstračních příkladech budeme využívat databázi SQLite, pro jejíž úspěšné použití je nutné nainstalovat balíček go-sqlite3. To se provede příkazem go get:
$ go get github.com/mattn/go-sqlite3
3. Vytvoření testovací databáze používané demonstračními příklady
Dále je nutné připravit vlastní testovací databázi. Jedná se o velmi zjednodušenou strukturu systému obsahujícího informace o zákaznících (customers), prodávaných výrobcích (products), objednávkách zákazníků (orders) a jednotlivých položek na objednávkách (order_item). Celá databáze bude uložena v souboru pojmenovaném test.db a její struktura bude následující:
create table customers ( ID integer primary key asc, name text not null, surname text not null, address text not null, country text not null, phone text not null ); create table products ( ID integer primary key asc, name text not null, description text not null, price number(6, 2) not null ); create table orders ( ID integer primary key asc, customer_id integer not null, sold datetime not null, total number(6, 2) not null, foreign key(customer_id) references customers(ID) ); create table order_item ( ID integer primary key asc, order_id integer not null, product_id integer not null, quantity integer not null, price number(6,2) not null, foreign key(order_id) references orders(ID), foreign key(product_id) references products(ID) );
Dále naši jednoduchou databázi naplníme testovacími daty:
insert into customers (id, name, surname, address, country, phone) values (0, 'Maria', 'Anders', 'Berlin', 'Germany', '030-0074321'); insert into customers (id, name, surname, address, country, phone) values (1, 'Ana', 'Trujillo', 'México D.F.', 'Mexico', '(5) 555-4729'); insert into customers (id, name, surname, address, country, phone) values (2, 'Antonio', 'Moreno', 'México D.F.', 'Mexico', '(5) 555-3932'); insert into customers (id, name, surname, address, country, phone) values (3, 'Thomas', 'Hardy', 'London', 'UK', '(171) 555-7788'); insert into customers (id, name, surname, address, country, phone) values (4, 'Christina', 'Berglund', 'Luleå', 'Sweden', '0921-12 34 65'); insert into products (id, name, description, price) values (0, 'Matice M5', 'DIN 934', 0.25); insert into products (id, name, description, price) values (1, 'Matice M5 nízká', 'DIN 439', 0.15); insert into products (id, name, description, price) values (2, 'Matice M5 dlouhá', 'DIN 6334', 1.50); insert into products (id, name, description, price) values (3, 'Soustruh', 'S2-B', 10000.00); insert into orders (id, customer_id, sold, total) values (0, 0, CURRENT_TIMESTAMP, 10025); insert into orders (id, customer_id, sold, total) values (1, 1, CURRENT_TIMESTAMP, 150); insert into order_item (id, order_id, product_id, quantity, price) values (0, 0, 0, 100, 25); insert into order_item (id, order_id, product_id, quantity, price) values (1, 0, 3, 1, 10000); insert into order_item (id, order_id, product_id, quantity, price) values (2, 1, 2, 100, 150);
Vytvoření databázového schématu a vložení testovacích dat můžeme provést například tímto skriptem:
#!/bin/sh DATABASE=test.db SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )" cat "${SCRIPT_DIR}/schema.sql" | sqlite3 "${SCRIPT_DIR}/${DATABASE}" cat "${SCRIPT_DIR}/test_data.sql" | sqlite3 "${SCRIPT_DIR}/${DATABASE}"
Obsah databáze je možné otestovat přímo z příkazové řádky interpretrem sqlite3:
$ sqlite3 test.db SQLite version 3.20.1 2017-08-24 16:21:36 Enter ".help" for usage hints. sqlite> select * from customers; 0|Maria|Anders|Berlin|Germany|030-0074321 1|Ana|Trujillo|México D.F.|Mexico|(5) 555-4729 2|Antonio|Moreno|México D.F.|Mexico|(5) 555-3932 3|Thomas|Hardy|London|UK|(171) 555-7788 4|Christina|Berglund|Luleå|Sweden|0921-12 34 65 sqlite> select * from products; 0|Matice M5|DIN 934|0.25 1|Matice M5 nízká|DIN 439|0.15 2|Matice M5 dlouhá|DIN 6334|1.5 3|Soustruh|S2-B|10000 sqlite> select customers.name, orders.total, orders.sold from orders join customers on orders.customer_id=customers.id; Maria|10025|2019-10-20 13:32:28 Ana|150|2019-10-20 13:32:28 sqlite>
4. Kostra aplikace, která se připojí k relační databázi
V této chvíli již máme vše připraveno pro vytvoření základní kostry aplikace, která pouze inicializuje databázový ovladač a dále se pokusí navázat připojení k databázi. Kostra takového příkladu může vypadat následovně (podrobnosti budou vysvětleny pod zdrojovým kódem):
package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" ) func main() { connections, err := sql.Open("sqlite3", "./test.db") if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) }
Povšimněte si, že musíme importovat i balíček go-sqlite3, ovšem nikde nevoláme žádnou funkci deklarovanou v tomto balíčku ani nepoužíváme žádnou jeho konstantu či proměnnou. Aby překladač nenahlásil chybnou strukturu programu, musíme před jméno balíčku vložit znak _:
_ "github.com/mattn/go-sqlite3"
Další činnost programu je zřejmá:
- Program se pokusí inicializovat připojení k lokální databázi s využitím ovladače „sqlite3“
- Dále zkontroluje, zda se inicializace připojení podařila s případným ohlášením chyby
- A následně zajistí, aby se program od databáze odpojil na konci své činnosti
Tento demonstrační příklad by měl po svém spuštění vypsat přibližně následující (dodejme, že ne zcela čitelné) údaje:
2019/10/20 15:21:52 Connected to database &{0 {./test.db 0xc0000a0020} 0 {0 0} [] map[] 0 0 0xc0000820c0 0xc0000aa120 false map[] map[] 0 0 0 <nil> 0 0 0 0x48bd80}
5. Specifikace ovladače databáze i způsobu připojení k databázi
Předchozí demonstrační příklad byl napsán poměrně rigidně, protože například neumožňoval změnit databázový ovladač ani parametry připojení k databázi. Provedeme tedy jeho nepatrnou úpravu, a to takovým způsobem, aby se ovladač a parametry připojení daly specifikovat na příkazové řádce. Využijeme zde standardní balíček flag, v němž budeme specifikovat jednotlivé parametry i jejich výchozí hodnoty použité ve chvíli, kdy program spustíme bez parametrů (o tomto balíčku jsme se již v seriálu o Go zmiňovali):
package main import ( "database/sql" "flag" _ "github.com/mattn/go-sqlite3" "log" ) func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) }
6. Základní typ dotazu – query
Ve třetím demonstračním příkladu si ukážeme, jakým způsobem je možné získat (načíst) data z naší testovací databáze. Jak jsme si již řekli v úvodním textu, použijeme nejprve nízkoúrovňový přístup, v němž budeme muset napsat celý SQL dotaz a posléze zpracovat jeho výsledky. Po připojení k databázi můžeme jednoduchý dotaz bez parametrů vytvořit následujícím způsobem – metodou Query:
rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id")
Výsledkem tohoto příkazu je buď objekt typu Rows nebo objekt obsahující informace o chybě, kterou bychom měli v každém případě zkontrolovat:
if err != nil { log.Fatal(err) }
Objekt typu Rows představuje „kurzor“ ukazující na výsledky dotazu. Zpočátku ukazuje před první výsledek; přesun na výsledek další zajistí metoda Next. Dále nesmíme zapomenout na to, že kurzor je zapotřebí uzavřít (ideálně v bloku defer), takže kostra zpracování všech výsledků dotazu by mohla vypadat například následovně:
defer rows.Close() for rows.Next() { .... čtení jednotlivých záznamů }
Nejtěžší je samotné přečtení záznamů, které je prováděno metodou Rows.Scan a může vypadat takto:
var id int var name string var surname string var address string var country string var phone string if err := rows.Scan(&id, &name, &surname, &address, &country, &phone); err != nil { log.Fatal(err) } fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", id, name, surname, address, country, phone)
Povšimněte si, jakým způsobem se explicitně načítají jednotlivé položky každého záznamu. Na rozdíl od JDBC a podobných knihoven je možné všechny položky načíst jediným zavoláním funkce Rows.Scan, které předáme ukazatele na proměnné, které se naplní načtenými daty (samozřejmě si musíme dát pozor na uspořádání položek za sebou; proto je vhodné v příkazu SELECT explicitně vyjmenovat všechny sloupce). Typy proměnných jsou testovány vůči typům sloupců čtené tabulky/tabulek, i když databázový ovladač dokáže provést některé konverze automaticky. V případě, že počet a typ položek není korektní, vrátí se informace o chybě.
Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně (jedná se o poměrně dlouhý příklad, zvláště když si uvědomíme, že pouze načte několik záznamů z jediné tabulky):
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) func listOfCustomers(connections *sql.DB) { rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id") if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var id int var name string var surname string var address string var country string var phone string if err := rows.Scan(&id, &name, &surname, &address, &country, &phone); err != nil { log.Fatal(err) } fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", id, name, surname, address, country, phone) } if err := rows.Err(); err != nil { log.Fatal(err) } } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) listOfCustomers(connections) }
Po spuštění tohoto demonstračního příkladu by se měla zobrazit tabulka se všemi (pěti) záznamy přečtenými z tabulky customers:
2019/10/20 15:23:55 Connected to database &{0 {./test.db 0xc0000a0020} 0 {0 0} [] map[] 0 0 0xc0000820c0 0xc0000aa120 false map[] map[] 0 0 0 <nil> 0 0 0 0x48bd80} 0 Maria Anders Berlin Germany 030-0074321 1 Ana Trujillo México D.F. Mexico (5) 555-4729 2 Antonio Moreno México D.F. Mexico (5) 555-3932 3 Thomas Hardy London UK (171) 555-7788 4 Christina Berglund Luleå Sweden 0921-12 34 65
7. Typové konverze a hlášení chyb ve chvíli, kdy konverzi nelze provést
Vyzkoušejme si nyní, co se stane v případě, že schválně změníme typy proměnných, do nichž se načítají jednotlivé záznamy z tabulky. Namísto:
var id int var name string var surname string var address string var country string var phone string
Napíšeme:
var id string var name string var surname int var address string var country string var phone string
Upravená (či možná lépe řečeno poškozená) varianta demonstračního příkladu bude vypadat takto:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) func listOfCustomers(connections *sql.DB) { rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id") if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var id string var name string var surname int var address string var country string var phone string if err := rows.Scan(&id, &name, &surname, &address, &country, &phone); err != nil { log.Fatal(err) } fmt.Printf("%2s %-10s %d %-12s %-12s %s\n", id, name, surname, address, country, phone) } if err := rows.Err(); err != nil { log.Fatal(err) } } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) listOfCustomers(connections) }
Po spuštění se ve chvíli, kdy se pokusíme zpracovat první záznam, objeví následující chyba:
2019/10/20 15:38:41 sql: Scan error on column index 2, name "surname": converting driver.Value type string ("Anders") to a int: invalid syntax exit status 1
8. Datový typ Customer
V praktických aplikacích většinou budeme chtít načíst data z tabulky (či ze spojených tabulek) do řezu datových struktur. V našem případě (tabulka customers) se bude jednat o strukturu, která je v programovacím jazyce Go definována následujícím způsobem:
type Customer struct { Id int Name string Surname string Address string Country string Phone string }
create table customers ( ID integer primary key asc, name text not null, surname text not null, address text not null, country text not null, phone text not null );
Nyní vytvoříme funkci určenou pro načtení a vrácení seznamu všech zákazníků. Tato funkce bude vracet řez (slice) s načtenými zákazníky, popř. objekt s informacemi o chybě, která při práci s databází nastala:
func readListOfCustomers(connections *sql.DB) ([]Customer, error) { ... ... ... }
Načtení a zpracování zákazníků bude probíhat naprosto stejným způsobem, jako tomu bylo v předchozích dvou demonstračních příkladech; pouze budeme načítaná data ukládat do datové struktury typu Customer, kterou posléze připojíme k vytvářenému řezu:
for rows.Next() { var id int var name string var surname string var address string var country string var phone string if err := rows.Scan(&id, &name, &surname, &address, &country, &phone); err != nil { return customers, err } customers = append(customers, Customer{id, name, surname, address, country, phone}) }
Zajímavý trik použijeme po načtení všech záznamů – pomocí metody Rows.Err() zjistíme, zda v průběhu načítání nedošlo k chybě:
if err := rows.Err(); err != nil { return customers, err }
Další postup je již snadný, takže si ukažme úplný zdrojový kód takto upraveného příkladu:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func readListOfCustomers(connections *sql.DB) ([]Customer, error) { customers := []Customer{} rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id") if err != nil { return customers, err } defer rows.Close() for rows.Next() { var id int var name string var surname string var address string var country string var phone string if err := rows.Scan(&id, &name, &surname, &address, &country, &phone); err != nil { return customers, err } customers = append(customers, Customer{id, name, surname, address, country, phone}) } if err := rows.Err(); err != nil { return customers, err } return customers, nil } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) customers, err := readListOfCustomers(connections) if err != nil { log.Fatal(err) } for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
Výsledek, který získáme po spuštění tohoto příkladu:
0 Maria Anders Berlin Germany 030-0074321 1 Ana Trujillo México D.F. Mexico (5) 555-4729 2 Antonio Moreno México D.F. Mexico (5) 555-3932 3 Thomas Hardy London UK (171) 555-7788 4 Christina Berglund Luleå Sweden 0921-12 34 65
9. Vylepšení předchozího příkladu – použití ukazatele na prvky datové struktury
Předchozí příklad byl ve skutečnosti zbytečně složitý, protože jsme v něm používali pomocné lokální proměnné použité pouze pro načtení jednotlivých záznamů a pro jejich následnou konverzi do datové struktury Customer:
var id int var name string var surname string var address string var country string var phone string if err := rows.Scan(&id, &name, &surname, &address, &country, &phone); err != nil { return customers, err } customers = append(customers, Customer{id, name, surname, address, country, phone})
Ve skutečnosti není nutné tyto lokální proměnné používat, protože můžeme využít jedné poněkud méně známé vlastnosti programovacího jazyka Go (o které jsme se již v tomto seriálu zmínili) – v Go je totiž možné získat ukazatel na prvek datové struktury. A právě tento ukazatel (přesněji ukazatele) můžeme předat do metody Rows.Scan() a zjednodušit tak výše uvedené řádky na:
var customer Customer if err := rows.Scan(&customer.Id, &customer.Name, &customer.Surname, &customer.Address, &customer.Country, &customer.Phone); err != nil { return customers, err } customers = append(customers, customer)
Upravený zdrojový kód příkladu je tedy o několik programových řádků kratší:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func readListOfCustomers(connections *sql.DB) ([]Customer, error) { customers := []Customer{} rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id") if err != nil { return customers, err } defer rows.Close() for rows.Next() { var customer Customer if err := rows.Scan(&customer.Id, &customer.Name, &customer.Surname, &customer.Address, &customer.Country, &customer.Phone); err != nil { return customers, err } customers = append(customers, customer) } if err := rows.Err(); err != nil { return customers, err } return customers, nil } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) customers, err := readListOfCustomers(connections) if err != nil { log.Fatal(err) } for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
10. Dotaz s parametry
Prozatím jsme používali velmi jednoduchou formu dotazu (query), v níž se nevyskytoval žádný parametr, takže se celý dotaz mohl vložit do řetězce (řetězcového literálu):
rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id")
V praxi se ovšem pochopitelně setkáme s dotazy, v nichž se nějaký parametr vyskytuje. V takovém případě není vhodné a už vůbec ne bezpečné se snažit sestavit řetězec s dotazem programově (už jen z toho důvodu, že je nutné řešit „escapování“ speciálních znaků, zabránit útokům typu SQL injection apod.). Namísto toho se do místa, v němž se má vyskytovat parametr, vloží znak otazníku a metoda Query se doplní o potřebný počet parametrů. Pokud například budeme potřebovat získat seznam zákazníků pouze z jediné země (country), může složení a spuštění dotazu vypadat následovně:
rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers WHERE country=? ORDER BY id", country)
Zbytek programu může zůstat nezměněn:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func customersFromCountry(connections *sql.DB, country string) ([]Customer, error) { customers := []Customer{} rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers WHERE country=? ORDER BY id", country) if err != nil { return customers, err } defer rows.Close() for rows.Next() { var customer Customer if err := rows.Scan(&customer.Id, &customer.Name, &customer.Surname, &customer.Address, &customer.Country, &customer.Phone); err != nil { return customers, err } customers = append(customers, customer) } if err := rows.Err(); err != nil { return customers, err } return customers, nil } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) customers, err := customersFromCountry(connections, "Mexico") if err != nil { log.Fatal(err) } for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
Podobně můžeme vytvořit funkci vracející všechny výrobky, jejichž cena se pohybuje v zadaných mezích:
rows, err := connections.Query("SELECT id, name, description, price FROM products WHERE price between ? and ? ORDER BY id", min, max)
Celý příklad:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Product struct { Id int Name string Description string Price float32 } func productsWithPriceBetween(connections *sql.DB, min float32, max float32) ([]Product, error) { products := []Product{} rows, err := connections.Query("SELECT id, name, description, price FROM products WHERE price between ? and ? ORDER BY id", min, max) if err != nil { return products, err } defer rows.Close() for rows.Next() { var product Product if err := rows.Scan(&product.Id, &product.Name, &product.Description, &product.Price); err != nil { return products, err } products = append(products, product) } if err := rows.Err(); err != nil { return products, err } return products, nil } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) products, err := productsWithPriceBetween(connections, 0.0, 2.0) if err != nil { log.Fatal(err) } for _, product := range products { fmt.Printf("%2d %-20s %-10s %f\n", product.Id, product.Name, product.Description, product.Price) } }
11. Víceřádkové dotazy
Zápis dlouhých dotazů (query) na jediném řádku je většinou dosti nečitelný, což si můžeme ukázat na následujícím (vlastně stále velmi jednoduchém) dotazu:
rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers WHERE country=? ORDER BY id", country)
Výhodnější je v tomto případě použít takzvané „raw“ řetězce, které jsou zapisovány do zpětných apostrofů a mohou obsahovat i konce řádků:
rows, err := connections.Query(` SELECT id, name, surname, address, country, phone FROM customers WHERE country=? ORDER BY id`, country)
Úprava příkladu používajícího „raw“ řetězce je snadná a přímočará:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func customersFromCountry(connections *sql.DB, country string) ([]Customer, error) { customers := []Customer{} rows, err := connections.Query(` SELECT id, name, surname, address, country, phone FROM customers WHERE country=? ORDER BY id`, country) if err != nil { return customers, err } defer rows.Close() for rows.Next() { var customer Customer if err := rows.Scan(&customer.Id, &customer.Name, &customer.Surname, &customer.Address, &customer.Country, &customer.Phone); err != nil { return customers, err } customers = append(customers, customer) } if err := rows.Err(); err != nil { return customers, err } return customers, nil } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) customers, err := customersFromCountry(connections, "Mexico") if err != nil { log.Fatal(err) } for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
12. Přidání nového zákazníka do databáze
Dotazy (query) se tvořily metodou DB.Query, ovšem pro manipulaci s daty (přidání, vymazání, úprava) se používá metoda DB.Prepare. I této metodě se předá řetězec s SQL příkazem, který opět může obsahovat měnitelné parametry:
statement, err := connections.Prepare("INSERT INTO customers(name, surname, address, country, phone) VALUES (?, ?, ?, ?, ?)")
Parametry (jejich konkrétní hodnoty) se příkazu předají při spuštění příkazu metodou Exec:
_, err = statement.Exec(customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone)
V dalším demonstračním příkladu je použit výše uvedený SQL příkaz INSERT pro přidání nového zákazníka do databáze. Povšimněte si, že nemusíme specifikovat ID zákazníka, protože se jedná o automaticky generovaný primární klíč:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func addNewCustomer(connections *sql.DB, customer Customer) error { statement, err := connections.Prepare("INSERT INTO customers(name, surname, address, country, phone) VALUES (?, ?, ?, ?, ?)") if err != nil { return err } defer statement.Close() _, err = statement.Exec(customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) return err } func readListOfCustomers(connections *sql.DB) ([]Customer, error) { customers := []Customer{} rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id") if err != nil { return customers, err } defer rows.Close() for rows.Next() { var customer Customer if err := rows.Scan(&customer.Id, &customer.Name, &customer.Surname, &customer.Address, &customer.Country, &customer.Phone); err != nil { return customers, err } customers = append(customers, customer) } if err := rows.Err(); err != nil { return customers, err } return customers, nil } func printAllCustomers(connections *sql.DB) { customers, err := readListOfCustomers(connections) if err != nil { log.Fatal(err) } for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) fmt.Println("Original list") printAllCustomers(connections) addNewCustomer(connections, Customer{6, "Franta", "Vomáčka", "Horní dolní", "CR", "603 123 456"}) fmt.Println("\nNew list") printAllCustomers(connections) }
13. Vymazání zákazníka z databáze
Prakticky stejným přístupem je možné zákazníka z databáze vymazat:
statement, err := connections.Prepare("DELETE FROM customers WHERE id=?") _, err = statement.Exec(id)
Povšimněte si, že v tomto případě zákazníka specifikujeme pomocí jeho ID, tedy primárního klíče.
Operace UPDATE by vypadala podobně, jako obě popsané operace INSERT a DELETE, takže si ji zde nemusíme uvádět.
Celý příklad, po jehož spuštění se z databáze odstraní zákazník s ID=1:
package main import ( "database/sql" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func addNewCustomer(connections *sql.DB, customer Customer) error { statement, err := connections.Prepare("INSERT INTO customers(name, surname, address, country, phone) VALUES (?, ?, ?, ?, ?)") if err != nil { return err } defer statement.Close() _, err = statement.Exec(customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) return err } func deleteCustomer(connections *sql.DB, id int) error { statement, err := connections.Prepare("DELETE FROM customers WHERE id=?") if err != nil { return err } defer statement.Close() _, err = statement.Exec(id) return err } func readListOfCustomers(connections *sql.DB) ([]Customer, error) { customers := []Customer{} rows, err := connections.Query("SELECT id, name, surname, address, country, phone FROM customers ORDER BY id") if err != nil { return customers, err } defer rows.Close() for rows.Next() { var customer Customer if err := rows.Scan(&customer.Id, &customer.Name, &customer.Surname, &customer.Address, &customer.Country, &customer.Phone); err != nil { return customers, err } customers = append(customers, customer) } if err := rows.Err(); err != nil { return customers, err } return customers, nil } func printAllCustomers(connections *sql.DB) { customers, err := readListOfCustomers(connections) if err != nil { log.Fatal(err) } for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() connections, err := sql.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("Can not connect to data storage", err) } defer connections.Close() log.Printf("Connected to database %v", connections) fmt.Println("Original list") printAllCustomers(connections) deleteCustomer(connections, 1) fmt.Println("\nNew list") printAllCustomers(connections) }
14. ORM v programovacím jazyce Go
Všechny výše uvedené demonstrační příklady jsou poměrně dlouhé, zvláště když si uvědomíme, že prováděné operace jsou ve skutečnosti značně primitivní. Je to způsobeno tím, že jsme použili přímo SQL dotazy/příkazy a namapování na datové struktury Go byly provedeny ručně. Existuje však – podobně jako v mnoha dalších programovacích jazycích – možnost automatického namapování datových struktur Go na záznamy uložené v databázi. Jedná se o známou technologii ORM, která je v případě Go implementována například v knihovně GORM. Tu je nutné nejprve nainstalovat příkazem:
$ go get -u github.com/jinzhu/gorm
Dnes si ukážeme jen základní možnosti použití této knihovny; podrobnosti a složitější příklady budou uvedeny příště.
15. Získání seznamu všech zákazníků
První příklad, v němž knihovnu GORM použijeme, po svém spuštění přečte seznam všech zákazníků a vypíše ho na standardní výstup. Rozdílů oproti předchozím příkladům je hned několik:
- Připojení k databázi je realizováno funkcígorm.Open.
- Dále se zajistí automatické namapování datové strukturyCustomer na příslušnou databázovou tabulku.
- Nalezení všech zákazníků (bez dalších výběrových kritérií) zajišťuje metodaFind, které se předá řez, jenž bude naplněn načtenými daty.
Další operace nejsou v tomto jednoduchém příkladu zapotřebí, takže se jedná o dosti citelné zkrácení zdrojového kódu:
package main import ( "flag" "fmt" "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int Name string Surname string Address string Country string Phone string } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() db, err := gorm.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("failed to connect database") } defer db.Close() db.AutoMigrate(&Customer{}) var customers []Customer db.Find(&customers) for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
16. Specifikace jména sloupce v databázové tabulce
Po spuštění předchozího příkladu jste si mohli všimnout, že se korektně nevyplnily údaje v položce Id, protože knihovna GORM nespárovala jméno položky v datové struktuře Customer a jméno sloupce v tabulce customers. Oba zmíněné identifikátory se mohou v praxi lišit, takže je nutné správné namapování zajistit explicitně. To se provede následovně:
type Customer struct { Id int `gorm:"Column:ID"` Name string Surname string Address string Country string Phone string }
Zbytek zdrojového kódu příkladu již může zůstat beze změny:
package main import ( "flag" "fmt" "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int `gorm:"Column:ID"` Name string Surname string Address string Country string Phone string } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() db, err := gorm.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("failed to connect database") } defer db.Close() db.AutoMigrate(&Customer{}) var customers []Customer db.Find(&customers) for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
17. Vytvoření nového zákazníka přes ORM
S využitím ORM velmi snadno vytvoříme nového zákazníka, a to pouze pomocí dvou programových řádků kódu:
customer := Customer{Name: "Franta", Surname: "Vomáčka", Address: "Horní dolní", Country: "CR", Phone: "603 123 456"} db.Create(&customer)
Ve skutečnosti však budeme muset příklad ještě nepatrně upravit, a to konkrétně tak, že specifikujeme, že položka Id je namapována na primární klíč tabulky a bude se tedy vytvářet automaticky:
type Customer struct { Id int `gorm:"Column:ID;PRIMARY_KEY"` Name string Surname string Address string Country string Phone string }
Pokud tuto úpravu neprovedete, bude se při pokusu o vytvoření nového zákazníka vypisovat chyba, že Id není unikátní.
Upravený demonstrační příklad může vypadat následovně:
package main import ( "flag" "fmt" "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int `gorm:"Column:ID;PRIMARY_KEY"` Name string Surname string Address string Country string Phone string } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() db, err := gorm.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("failed to connect database") } defer db.Close() db.AutoMigrate(&Customer{}) customer := Customer{Name: "Franta", Surname: "Vomáčka", Address: "Horní dolní", Country: "CR", Phone: "603 123 456"} db.Create(&customer) var customers []Customer db.Find(&customers) for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
18. Vymazání zákazníka přes ORM
A konečně se podívejme na to, jak lze zákazníka z tabulky customers vymazat. Je to poměrně snadné, protože nám bude (alespoň v tomto případě) postačovat specifikovat pouze Id zákazníka:
customer := Customer{Id: 1} db.Delete(&customer)
Tento přístup má jednu vážnou nevýhodu – pokud se pokusíme vymazat zákazníka s Id=0, dojde ve skutečnosti k vymazání celé tabulky. Je tomu tak z toho důvodu, že nula je „nulovou hodnotou“ pro typ int a při použití db.Delete() má nulová hodnota stejný význam, jakoby atribut nebyl vůbec vyplněn – interně se totiž vůbec nepřidá do klauzule WHERE:
package main import ( "flag" "fmt" "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" "log" ) type Customer struct { Id int `gorm:"Column:ID;PRIMARY_KEY"` Name string Surname string Address string Country string Phone string } func main() { dbDriver := flag.String("dbdriver", "sqlite3", "database driver specification") storageSpecification := flag.String("storage", "./test.db", "storage specification") flag.Parse() db, err := gorm.Open(*dbDriver, *storageSpecification) if err != nil { log.Fatal("failed to connect database") } defer db.Close() db.AutoMigrate(&Customer{}) customer := Customer{Id: 1} db.Delete(&customer) var customers []Customer db.Find(&customers) for _, customer := range customers { fmt.Printf("%2d %-10s %-10s %-12s %-12s %s\n", customer.Id, customer.Name, customer.Surname, customer.Address, customer.Country, customer.Phone) } }
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně čtyři megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- The fantastic ORM library for Golang
http://gorm.io/ - Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go