Hlavní navigace

Programovací jazyk Go a relační databáze

Pavel Tišnovský

Dnes se seznámíme se základními postupy a knihovnami, které se používají pro práci s relačními databázemi. Nejprve si ukážeme použití nízkoúrovňového přístupu s využitím balíčku database/sql a pak se seznámíme i s ORM.

Doba čtení: 47 minut

Sdílet

11. Víceřádkové dotazy

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

20. Odkazy na Internetu

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
Poznámka: instalace tohoto balíčku může trvat poněkud delší dobu, než jsme obvykle u programovacího jazyka Go zvyklí. Je tomu tak z toho důvodu, že se interně volá překladač gcc pro překlad implementace databázového engine.

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)
);
Poznámka: ve skutečnosti dnes využijeme pouze první a částečně i druhou tabulku, ovšem v navazující části tohoto seriálu již budeme používat všechny tabulky, včetně deklarovaných vazeb mezi nimi.

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á:

  1. Program se pokusí inicializovat připojení k lokální databázi s využitím ovladače „sqlite3“
  2. Dále zkontroluje, zda se inicializace připojení podařila s případným ohlášením chyby
  3. A následně zajistí, aby se program od databáze odpojil na konci své činnosti
Poznámka: ve skutečnosti se v případě ovladače pro databázi SQLite skutečně pouze inicializuje „pool“ připojení k databázi a pokud neprovedeme žádnou další operaci (SELECT, INSERT, UPDATE, DELETE), nemusíme vlastně ani zjistit, že například soubor s databází vůbec neexistuje.

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)
}
Poznámka: na této kostře budou postaveny všechny další příklady uvedené v dnešním článku.

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
Poznámka: povšimněte si, že SQL subsystém neměl problém s tím, že ID načítáme do proměnné typu řetězec, protože tuto konverzi je možné bez problémů provést automaticky. Ovšem konverze řetězce „Anders“ na celé číslo už pochopitelně možná není a proto byla detekována a vrácena chyba.

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
}
Poznámka: povšimněte si, že typ prvků této struktury přímo odpovídá struktuře tabulky, alespoň do té míry, do jaké to umožňuje SQL a typový systém programovacího jazyka Go:
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)
        }
}
Poznámka: výsledek běhu tohoto příkladu je naprosto shodný s předchozím příkladem, takže si ho zde nebudeme uvádět.

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)
        }
}
Poznámka: výsledky si opět nemusíme uvádět, protože se neliší od předchozích kapitol.

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.

Poznámka: v praxi by se ovšem jednalo o složitější operaci, která by vyžadovala kaskádní vymazání objednávek atd.

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:

  1. Připojení k databázi je realizováno funkcígorm.Open.
  2. Dále se zajistí automatické namapování datové strukturyCustomer na příslušnou databázovou tabulku.
  3. 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)
Poznámka: opět platí, že některé záznamy bude nutné vytvářet složitějším způsobem, protože se zápis bude provádět do několika tabulek. Ovšem prozatím nám bude dostačovat takto jednoduchá operace se zápisem do jediné tabulky.

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:

# Příklad Stručný popis Cesta
1 01_basic_connection.go připojení k databázi https://github.com/tisnik/go-root/blob/master/article39/01_ba­sic_connection.go
2 02_connection_params.go specifikace databázového ovladače a řetězce s připojovacími informacemi https://github.com/tisnik/go-root/blob/master/article39/02_con­nection_params.go
3 03_customers.go získání seznamu zákazníků z databáze https://github.com/tisnik/go-root/blob/master/article39/03_cus­tomers.go
4 04_type_checking.go kontrola datových typů a datové konverze při načítání https://github.com/tisnik/go-root/blob/master/article39/04_ty­pe_checking.go
5 05_customer_type.go uživatelský datový typ Customer https://github.com/tisnik/go-root/blob/master/article39/05_cus­tomer_type.go
6 06_pointer_to_struct_item.go využití ukazatele na prvky struktury pro zjednodušení předchozího příkladu https://github.com/tisnik/go-root/blob/master/article39/06_po­inter_to_struct_item.go
7 07_query_parameter.go databázový dotaz s parametry, první varianta https://github.com/tisnik/go-root/blob/master/article39/07_qu­ery_parameter.go
8 08_query_parameter2.go databázový dotaz s parametry, druhá varianta https://github.com/tisnik/go-root/blob/master/article39/08_qu­ery_parameter2.go
9 09_query_in_raw_string.go použití takzvaných „raw“ řetězců https://github.com/tisnik/go-root/blob/master/article39/09_qu­ery_in_raw_string.go
10 10_insert_customer.go přidání nového zákazníka do databáze https://github.com/tisnik/go-root/blob/master/article39/10_in­sert_customer.go
11 11_delete_customer.go vymazání zákazníka z databáze https://github.com/tisnik/go-root/blob/master/article39/11_de­lete_customer.go
12 12_gorm_basics.go základy použití knihovny GORM https://github.com/tisnik/go-root/blob/master/article39/12_gor­m_basics.go
13 13_column_name.go specifikace jména sloupce v databázové tabulce https://github.com/tisnik/go-root/blob/master/article39/13_co­lumn_name.go
14 14_create_customer_improper.go přidání nového zákazníka přes ORM (GORM), nevalidní varianta https://github.com/tisnik/go-root/blob/master/article39/14_cre­ate_customer_improper.go
15 15_create_customer_proper.go přidání nového zákazníka přes ORM (GORM), validní varianta https://github.com/tisnik/go-root/blob/master/article39/15_cre­ate_customer_proper.go
16 16_delete_customer.go vymazání zákazníka přes ORM (GORM) https://github.com/tisnik/go-root/blob/master/article39/16_de­lete_customer.go
       
17 create_database.sh skript pro vytvoření testovací databáze https://github.com/tisnik/go-root/blob/master/article39/cre­ate_database.sh
18 schema.sql schéma testovací databáze https://github.com/tisnik/go-root/blob/master/article39/sche­ma.sql
19 test_data.sql vstupní data pro testovací databázi https://github.com/tisnik/go-root/blob/master/article39/tes­t_data.sql

20. Odkazy na Internetu

  1. The fantastic ORM library for Golang
    http://gorm.io/
  2. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  3. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  4. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  5. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  6. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  7. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  8. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  9. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  10. Sémantické verzování
    https://semver.org/
  11. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  12. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  13. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  14. Modules
    https://github.com/golang/go/wi­ki/Modules
  15. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  16. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  17. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  18. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  19. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  20. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  21. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  22. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  23. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  24. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  25. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  26. gopls
    https://github.com/golang/go/wi­ki/gopls
  27. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  28. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  29. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  30. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  31. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  32. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  33. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  34. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  35. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  36. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  37. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  38. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  39. Package trace
    https://golang.org/pkg/runtime/trace/
  40. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  41. Command trace
    https://golang.org/cmd/trace/
  42. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  43. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  44. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  45. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  46. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  47. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  48. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  49. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  50. 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/
  51. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  52. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  53. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  54. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  55. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  56. The LLDB Debugger
    http://lldb.llvm.org/
  57. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  58. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  59. 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
  60. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  61. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  62. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  63. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  64. 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/
  65. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  66. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  67. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  68. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  69. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  70. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  71. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  72. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  73. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  74. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  75. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  76. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  77. go-cron
    https://github.com/rk/go-cron
  78. gocron
    https://github.com/jasonlvhit/gocron
  79. clockwork
    https://github.com/whiteShtef/cloc­kwork
  80. clockwerk
    https://github.com/onatm/clockwerk
  81. JobRunner
    https://github.com/bamzi/jobrunner
  82. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  83. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  84. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  85. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  86. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  87. go-prompt
    https://github.com/c-bata/go-prompt
  88. readline
    https://github.com/chzyer/readline
  89. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  90. go-readline
    https://github.com/fiorix/go-readline
  91. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  92. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  93. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  94. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  95. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  96. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  97. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  98. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  99. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  100. Editline Library (libedit)
    http://thrysoee.dk/editline/
  101. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  102. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  103. WinEditLine
    http://mingweditline.sourceforge.net/
  104. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  105. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  106. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  107. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  108. history(3) – Linux man page
    https://linux.die.net/man/3/history
  109. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  110. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  111. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  112. Balíček ogletest
    https://github.com/jacobsa/ogletest
  113. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  114. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  115. package testing
    https://golang.org/pkg/testing/
  116. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  117. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  118. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  119. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  120. GoConvey
    http://goconvey.co/
  121. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  122. 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
  123. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  124. package gg
    https://godoc.org/github.com/fo­gleman/gg
  125. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  126. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  127. The Go image package
    https://blog.golang.org/go-image-package
  128. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  129. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  130. YAML
    https://yaml.org/
  131. edn
    https://github.com/edn-format/edn
  132. Smile
    https://github.com/FasterXML/smile-format-specification
  133. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  134. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  135. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  136. Introducing JSON
    http://json.org/
  137. Package json
    https://golang.org/pkg/encoding/json/
  138. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  139. Go by Example: JSON
    https://gobyexample.com/json
  140. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  141. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  142. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  143. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  144. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  145. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  146. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  147. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  148. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  149. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  150. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  151. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  152. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  153. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  154. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  155. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  156. Algorithms to Go
    https://yourbasic.org/
  157. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  158. 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/
  159. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  160. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  161. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  162. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  163. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  164. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  165. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  166. The Go Programming Language (home page)
    https://golang.org/
  167. GoDoc
    https://godoc.org/
  168. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  169. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  170. The Go Programming Language Specification
    https://golang.org/ref/spec
  171. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  172. Package builtin
    https://golang.org/pkg/builtin/
  173. Package fmt
    https://golang.org/pkg/fmt/
  174. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  175. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  176. Learning Go
    https://www.miek.nl/go/
  177. Go Bootcamp
    http://www.golangbootcamp.com/
  178. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  179. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  180. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  181. The Go Blog
    https://blog.golang.org/
  182. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  183. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  184. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  185. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  186. 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
  187. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  188. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  189. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  190. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  191. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  192. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  193. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  194. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  195. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  196. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  197. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  198. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  199. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  200. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  201. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  202. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  203. 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/
  204. 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
  205. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  206. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  207. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  208. Go vs. Python
    https://www.peterbe.com/plog/govspy
  209. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  210. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  211. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  212. Go by Example: Slices
    https://gobyexample.com/slices
  213. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  214. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  215. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  216. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  217. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  218. nils In Go
    https://go101.org/article/nil.html
  219. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  220. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  221. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  222. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  223. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  224. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  225. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  226. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  227. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  228. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  229. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  230. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  231. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  232. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  233. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  234. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  235. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  236. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  237. 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
  238. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  239. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  240. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  241. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  242. Selectors
    https://golang.org/ref/spec#Selectors
  243. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  244. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  245. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  246. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  247. Part 21: Goroutines
    https://golangbot.com/goroutines/
  248. Part 22: Channels
    https://golangbot.com/channels/
  249. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  250. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  251. 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/
  252. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  253. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  254. Control Structures
    https://www.golang-book.com/books/intro/5
  255. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  256. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  257. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  258. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  259. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  260. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  261. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  262. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  263. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  264. Effective Go
    https://golang.org/doc/ef­fective_go.html
  265. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  266. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  267. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  268. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  269. Nils in Go
    https://www.doxsey.net/blog/nils-in-go