Skip to content

Commit 726aabb

Browse files
author
Moguchev Leonid Alekseevich
committed
update examples
1 parent f49d806 commit 726aabb

24 files changed

+994
-572
lines changed
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"database/sql/driver"
6+
"fmt"
7+
)
8+
9+
type MyCustomType struct {
10+
Number int
11+
Valid bool
12+
}
13+
14+
// наш тип MyCustomType удовлетовряет интерфейсу sql.Scanner
15+
var _ sql.Scanner = (*MyCustomType)(nil)
16+
17+
// Scan implements the Scanner interface.
18+
func (n *MyCustomType) Scan(src interface{}) error {
19+
// The src value will be of one of the following types:
20+
//
21+
// int64
22+
// float64
23+
// bool
24+
// []byte
25+
// string
26+
// time.Time
27+
// nil - for NULL values
28+
if src == nil {
29+
n.Number, n.Valid = 0, false
30+
return nil
31+
}
32+
n.Valid = true
33+
34+
// some fantastic logic here
35+
switch src := src.(type) {
36+
case int64:
37+
n.Number = int(src)
38+
case bool:
39+
n.Number = 1
40+
default:
41+
return fmt.Errorf("can't scan %#v into MyCustomType", src)
42+
}
43+
44+
return nil
45+
}
46+
47+
// Если мы хотим наш тип как-то хитро мапить в null/value, то реализуем Value()
48+
49+
// наш тип MyCustomType удовлетовряет интерфейсу driver.Valuer
50+
var _ driver.Valuer = (*MyCustomType)(nil)
51+
52+
// Value implements the driver Valuer interface.
53+
func (n MyCustomType) Value() (driver.Value, error) {
54+
if !n.Valid {
55+
return nil, nil
56+
}
57+
return int64(n.Number), nil
58+
}

1/database-sql/exec_example.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
)
9+
10+
func exampleExec(db *sql.DB) {
11+
// Ex. 1:
12+
const notExistedStudentID = 1234567
13+
result, err := db.Exec("UPDATE students SET age = age+1 WHERE id = $1", notExistedStudentID)
14+
if err != nil {
15+
log.Fatal(err)
16+
}
17+
18+
var (
19+
rowsAffected, lastInsertId int64
20+
)
21+
22+
rowsAffected, err = result.RowsAffected()
23+
if err != nil {
24+
fmt.Println("sql.Result.RowsAffected():", err) // ok
25+
}
26+
lastInsertId, err = result.LastInsertId()
27+
if err != nil {
28+
fmt.Println("sql.Result.LastInsertId():", err) // LastInsertId is not supported by "postgres" driver
29+
}
30+
fmt.Printf("rows affected: %d, last insert id: %d\n", rowsAffected, lastInsertId)
31+
}
32+
33+
func exampleExecContext(ctx context.Context, db *sql.DB) {
34+
const studentID = 1
35+
result, err := db.ExecContext(ctx, "UPDATE students SET age = age+1 WHERE id = $1", studentID)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
40+
rowsAffected, err := result.RowsAffected()
41+
if err != nil {
42+
fmt.Println("sql.Result.RowsAffected():", err)
43+
}
44+
fmt.Printf("rows affected: %d\n", rowsAffected)
45+
}

1/database-sql/main.go

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql" // https://go.dev/src/database/sql/doc.txt
6+
"encoding/json"
7+
"fmt"
8+
"log"
9+
"time"
10+
11+
// database/sql — это набор интерфейсов для работы с базой
12+
// Чтобы эти интерфейсы работали, для них нужна реализация. Именно за реализацию и отвечают драйверы.
13+
14+
_ "github.com/lib/pq" // импортируем драйвер для postgres
15+
// Обратите внимание, что мы загружаем драйвер анонимно, присвоив его квалификатору пакета псевдоним, _ ,
16+
// чтобы ни одно из его экспортированных имен не было видно нашему коду.
17+
// Под капотом драйвер регистрирует себя как доступный для пакета database/sql с помощью функции init()
18+
)
19+
20+
const (
21+
// название регистрируемоего драйвера github.com/lib/pq
22+
stdPostgresDriverName = "postgres"
23+
/*
24+
PostgreSQL:
25+
* github.com/lib/pq -> postgres
26+
* github.com/jackc/pgx -> pgx
27+
MySQL:
28+
* github.com/go-sql-driver/mysql -> mysql
29+
SQLite3:
30+
* github.com/mattn/go-sqlite3 -> sqlite3
31+
Oracle:
32+
* github.com/godror/godror -> godror
33+
MS SQL:
34+
* github.com/denisenkom/go-mssqldb -> sqlserver
35+
36+
See more drivers: https://zchee.github.io/golang-wiki/SQLDrivers/
37+
*/
38+
)
39+
40+
const (
41+
host = "localhost"
42+
port = 5432
43+
user = "user"
44+
password = "password"
45+
dbname = "playground"
46+
)
47+
48+
func main() {
49+
// connection string
50+
psqlConn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
51+
52+
// open database
53+
db, err := sql.Open(stdPostgresDriverName, psqlConn) // returns *sql.DB, error
54+
if err != nil {
55+
log.Fatal(err)
56+
}
57+
defer db.Close() // Обязательно при завершении работы приложения мы должны освободить все ресурсы, иначе соединения к базе останутся висеть.
58+
59+
/*
60+
sql.DB - не является соединением с базой данных! Это абстракция интерфейса.
61+
62+
sql.DB выполняет некоторые важные задачи для вас за кулисами:
63+
* открывает и закрывает соединения с фактической базовой базой данных через драйвер.
64+
* управляет пулом соединений по мере необходимости.
65+
66+
Абстракция sql.DB предназначена для того, чтобы вы не беспокоились о том, как управлять одновременным
67+
доступом к базовому хранилищу данных. Соединение помечается как используемое, когда вы используете
68+
его для выполнения задачи, а затем возвращается в доступный пул, когда оно больше не используется.
69+
*/
70+
71+
// После установления соединеия пингуем базу. Проверяем, что она отвечает нашему приложению.
72+
if err := db.Ping(); err != nil {
73+
log.Fatal(err)
74+
}
75+
76+
fmt.Println("Connection with database successfully established!")
77+
78+
/* Настройка пула соединений */
79+
db.SetConnMaxIdleTime(time.Minute) // время, в течение которого соединение может быть бездействующим.
80+
db.SetConnMaxLifetime(time.Hour) // время, в течение которого соединение может быть повторно использовано.
81+
db.SetMaxIdleConns(2) // максимум 2 простаивающих соединения
82+
db.SetMaxOpenConns(4) // максимум 4 открытых соединений с БД
83+
84+
/* статистика пула соединений */
85+
statistics := db.Stats()
86+
bytes, err := json.Marshal(statistics)
87+
if err != nil {
88+
log.Fatal(err)
89+
}
90+
fmt.Printf("db connection statistics: %s\n", string(bytes))
91+
92+
/* примеры работы c БД */
93+
exampleQueryRow(db)
94+
exampleQueryRowNoRows(db)
95+
exampleQuery(db)
96+
exampleExec(db)
97+
98+
/* примеры работы c БД c контекстом */
99+
// Совет: используйте запросы с контекстом
100+
ctx := context.Background()
101+
102+
exampleQueryRowContext(ctx, db)
103+
exampleQueryContext(ctx, db)
104+
exampleExecContext(ctx, db)
105+
106+
/* примеры работы c транзакциями */
107+
exampleTransaction(ctx, db)
108+
109+
/* пример работы с nullable полями*/
110+
exampleWithNullableFields(ctx, db)
111+
112+
// Более подробный туториал (правда там с MySQL, но суть та же)
113+
// Go database/sql tutorial: http://go-database-sql.org/
114+
}
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
)
9+
10+
func exampleWithNullableFields(ctx context.Context, db *sql.DB) {
11+
var number int
12+
if err := db.QueryRowContext(ctx, "SELECT null").Scan(&number); err != nil {
13+
fmt.Println("error scan:", err) // вернет ошибку, так как нельзя NULL сложить в нессылочные типы
14+
}
15+
// Как быть?
16+
17+
// Вариант 1: COALESCE(field, <default_value>)
18+
if err := db.QueryRowContext(ctx, "SELECT COALESCE(null, -1) AS some_field").Scan(&number); err != nil {
19+
log.Fatal(err)
20+
}
21+
fmt.Println("number =", number) // number = -1
22+
// Преимущества:
23+
// - ничего не меням в коде
24+
// Недостатски:
25+
// - можно забыть в запросе использовать COALESCE, и тогда запросы будут валиться с ошибкой
26+
// - иногда нам важно отличать NULL от значения по умолчанию
27+
28+
// Вариант 2: давайте сделаем из int ссылочный тип - указатель
29+
var ptrNumber *int // теперь у нас не int, а указатель на int
30+
if err := db.QueryRowContext(ctx, "SELECT null").Scan(&ptrNumber); err != nil {
31+
log.Fatal(err)
32+
}
33+
fmt.Println("ptrNumber =", ptrNumber) // тут мы увидем, что numberCanStoreNul == nil
34+
if ptrNumber != nil { // делаем постоянную проверку на nil
35+
fmt.Println("value of ptrNumber =", *ptrNumber) // разыменовываем указатель
36+
}
37+
38+
// Преимущества:
39+
// - легко из обычного типа сделать указатель
40+
// Недостатски:
41+
// - везде в приложении нам надо делать проверки на nil и разыменовывать указатель
42+
// - Наша переменная будет теперь аллоцировать в куче, что создает накладки для работы приложения на Go
43+
44+
// Вариант 3: за нас уже позаботились и сделали специальные типы в пакете database/sql:
45+
/*
46+
sql.NullInt16
47+
sql.NullInt32
48+
sql.NullInt64
49+
sql.NullByte
50+
sql.NullBool
51+
sql.NullFloat64
52+
sql.NullString
53+
sql.NullTime
54+
*/
55+
56+
var sqlNullNumber sql.NullInt32
57+
if err := db.QueryRowContext(ctx, "SELECT null").Scan(&sqlNullNumber); err != nil {
58+
log.Fatal(err)
59+
}
60+
fmt.Println("sqlNullNumber =", sqlNullNumber) // sqlNullNumber это структура
61+
if sqlNullNumber.Valid { // поле Valid = true сообщает нам, что поле не Null и можно брать занчение
62+
fmt.Println("value of ptrNumber =", sqlNullNumber.Int32) // получаем значение
63+
}
64+
// Преимущества:
65+
// - выделяется на стеке переменная
66+
// - все так же имеем возможность отличить NULL от 0
67+
// Недостатки:
68+
// - А что если мы хоти такое поведение для нашего кастомного типа? Нам не хватает стандартного набора.
69+
70+
// Нам необходмо, чтобы наш тип удовлетоварял sql.Scanner интерфейсу
71+
}

1/database-sql/query_example.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
)
9+
10+
func exampleQuery(db *sql.DB) {
11+
const minAge = 18
12+
13+
rows, err := db.Query("SELECT first_name, last_name, age FROM students WHERE age >= $1", minAge)
14+
if err != nil {
15+
log.Fatal(err)
16+
}
17+
defer rows.Close() // Обязательно закрываем иначе соединение с БД повиснет
18+
19+
type Student struct {
20+
FirstName string
21+
LastName string
22+
Age uint
23+
}
24+
25+
var students []Student
26+
for rows.Next() {
27+
var st Student
28+
if err := rows.Scan(&st.FirstName, &st.LastName, &st.Age); err != nil {
29+
log.Fatal(err)
30+
}
31+
students = append(students, st)
32+
}
33+
34+
if err = rows.Err(); err != nil {
35+
// handle the error here
36+
log.Fatal(err)
37+
}
38+
39+
fmt.Printf("students: %v\n", students)
40+
}
41+
42+
func exampleQueryContext(ctx context.Context, db *sql.DB) {
43+
const minAge = 18
44+
45+
rows, err := db.QueryContext(ctx, "SELECT first_name, last_name, age FROM students WHERE age >= $1", minAge)
46+
if err != nil {
47+
log.Fatal(err)
48+
}
49+
defer rows.Close() // Обязательно закрываем иначе соединение с БД повиснет
50+
51+
type Student struct {
52+
FirstName string
53+
LastName string
54+
Age uint
55+
}
56+
57+
var students []Student
58+
for rows.Next() {
59+
var st Student
60+
if err := rows.Scan(&st.FirstName, &st.LastName, &st.Age); err != nil {
61+
log.Fatal(err)
62+
}
63+
students = append(students, st)
64+
}
65+
// Внутри драйвера мы получаем данные, накапливая их в буфер размером 4KB.
66+
// rows.Next() порождает поход в сеть и наполняет буфер. Если буфера не хватает,
67+
// то мы идём в сеть за оставшимися данными. Больше походов в сеть – меньше скорость обработки.
68+
69+
fmt.Printf("students: %v\n", students)
70+
}

0 commit comments

Comments
 (0)