Skip to content

Commit e92a3ee

Browse files
authored
Merge pull request #25 from LostImagin4tion/LostImagin4tion/go-lesson-3.1 lesson 3.1: added go example
Спасибо за отличный пример :)
2 parents ed0d318 + f6b49a4 commit e92a3ee

File tree

7 files changed

+555
-0
lines changed

7 files changed

+555
-0
lines changed

dev-1/lesson-3.1/go/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
.PHONY: run
3+
run:
4+
go run .

dev-1/lesson-3.1/go/go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module ydb-sample
2+
3+
go 1.25.3
4+
5+
require github.com/ydb-platform/ydb-go-sdk/v3 v3.117.1
6+
7+
require (
8+
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
9+
github.com/google/uuid v1.6.0 // indirect
10+
github.com/jonboulle/clockwork v0.5.0 // indirect
11+
github.com/ydb-platform/ydb-go-genproto v0.0.0-20250911135631-b3beddd517d9 // indirect
12+
golang.org/x/net v0.46.0 // indirect
13+
golang.org/x/sync v0.17.0 // indirect
14+
golang.org/x/sys v0.37.0 // indirect
15+
golang.org/x/text v0.30.0 // indirect
16+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
17+
google.golang.org/grpc v1.76.0 // indirect
18+
google.golang.org/protobuf v1.36.10 // indirect
19+
)

dev-1/lesson-3.1/go/go.sum

Lines changed: 176 additions & 0 deletions
Large diffs are not rendered by default.

dev-1/lesson-3.1/go/issue.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
import (
4+
"time"
5+
)
6+
7+
type Issue struct {
8+
Id int64 `sql:"id"`
9+
Title string `sql:"title"`
10+
Timestamp time.Time `sql:"created_at"`
11+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"math/rand"
8+
"time"
9+
10+
ydb "github.com/ydb-platform/ydb-go-sdk/v3"
11+
"github.com/ydb-platform/ydb-go-sdk/v3/query"
12+
"github.com/ydb-platform/ydb-go-sdk/v3/sugar"
13+
)
14+
15+
var (
16+
random = rand.New(
17+
rand.NewSource(time.Now().UnixNano()),
18+
)
19+
)
20+
21+
// Репозиторий для работы с тикетами в базе данных YDB
22+
// Реализует операции добавления и чтения тикетов
23+
type IssueRepository struct {
24+
driver *ydb.Driver
25+
}
26+
27+
func NewIssueRepository(driver *ydb.Driver) *IssueRepository {
28+
return &IssueRepository{
29+
driver: driver,
30+
}
31+
}
32+
33+
// Добавление нового тикета в БД
34+
// ctx [context.Context] - контекст для управления исполнением запроса (например, можно задать таймаут)
35+
// title [string] - название тикета
36+
// Возвращает созданный тикет или ошибку
37+
func (repo *IssueRepository) AddIssue(ctx context.Context, title string) (*Issue, error) {
38+
// Генерируем случайный id для тикета
39+
id := random.Int63() // do not repeat in production
40+
timestamp := time.Now()
41+
42+
// Выполняем UPSERT запрос для добавления тикета
43+
err := repo.driver.Query().Do(
44+
ctx,
45+
func(ctx context.Context, s query.Session) error {
46+
err := s.Exec(
47+
ctx,
48+
`
49+
DECLARE $id AS Int64;
50+
DECLARE $title AS Text;
51+
DECLARE $created_at AS Timestamp;
52+
53+
UPSERT INTO issues (id, title, created_at)
54+
VALUES ($id, $title, $created_at);
55+
`,
56+
query.WithParameters(
57+
ydb.ParamsBuilder().
58+
Param("$id").Int64(id).
59+
Param("$title").Text(title).
60+
Param("$created_at").Timestamp(timestamp).
61+
Build(),
62+
),
63+
)
64+
return err
65+
},
66+
)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
return &Issue{
72+
Id: id,
73+
Title: title,
74+
Timestamp: timestamp,
75+
}, nil
76+
}
77+
78+
// Возвращает тикет по заданному id
79+
// ctx [context.Context] - контекст для управления исполнением запроса (например, можно задать таймаут)
80+
// id [int64] - id тикета
81+
// Возвращает найденный тикет или ошибку
82+
func (repo *IssueRepository) FindById(ctx context.Context, id int64) (*Issue, error) {
83+
resultIssues := make([]Issue, 0)
84+
85+
// Выполняем SELECT запрос в режиме [Snapshot Read-Only] для чтения данных
86+
// Этот режим сообщает серверу, что эта транзакция только для чтения.
87+
// Это позволяет снизить накладные расходы на подготовку к изменениям
88+
// и просто читать данные из одного "слепка" базы данных.
89+
err := repo.driver.Query().Do(
90+
ctx,
91+
func(ctx context.Context, s query.Session) error {
92+
// Если на предыдущих итерациях функции-ретраера
93+
// возникла ошибка во время чтения результата,
94+
// то необходимо очистить уже прочитанные результаты,
95+
// чтобы избежать дублирования при следующем выполнении функции-ретраера
96+
resultIssues = make([]Issue, 0)
97+
98+
queryResult, err := s.Query(
99+
ctx,
100+
`
101+
DECLARE $id AS Int64;
102+
103+
SELECT
104+
id,
105+
title,
106+
created_at
107+
FROM issues
108+
WHERE id=$id;
109+
`,
110+
query.WithTxControl(query.SnapshotReadOnlyTxControl()),
111+
query.WithParameters(
112+
ydb.ParamsBuilder().
113+
Param("$id").Int64(id).
114+
Build(),
115+
),
116+
)
117+
118+
if err != nil {
119+
return err
120+
}
121+
122+
defer func() { _ = queryResult.Close(ctx) }()
123+
124+
for {
125+
resultSet, err := queryResult.NextResultSet(ctx)
126+
if err != nil {
127+
if errors.Is(err, io.EOF) {
128+
break
129+
}
130+
131+
return err
132+
}
133+
134+
for row, err := range sugar.UnmarshalRows[Issue](resultSet.Rows(ctx)) {
135+
if err != nil {
136+
return err
137+
}
138+
139+
resultIssues = append(resultIssues, row)
140+
}
141+
}
142+
143+
return nil
144+
},
145+
)
146+
if err != nil {
147+
return nil, err
148+
}
149+
150+
if len(resultIssues) > 1 {
151+
return nil, errors.New("Multiple rows with the same id (lol)")
152+
}
153+
if len(resultIssues) == 0 {
154+
return nil, errors.New("Did not find any issues")
155+
}
156+
return &resultIssues[0], nil
157+
}
158+
159+
// Получает все тикеты из базы данных
160+
// ctx [context.Context] - контекст для управления исполнением запроса (например, можно задать таймаут)
161+
func (repo *IssueRepository) FindAll(ctx context.Context) ([]Issue, error) {
162+
resultIssues := make([]Issue, 0)
163+
164+
// Выполняем SELECT запрос в режиме [Snapshot Read-Only] для чтения данных
165+
// Этот режим сообщает серверу, что эта транзакция только для чтения.
166+
// Это позволяет снизить накладные расходы на подготовку к изменениям
167+
// и просто читать данные из одного "слепка" базы данных.
168+
err := repo.driver.Query().Do(
169+
ctx,
170+
func(ctx context.Context, s query.Session) error {
171+
// Если на предыдущих итерациях функции-ретраера
172+
// возникла ошибка во время чтения результата,
173+
// то необходимо очистить уже прочитанные результаты,
174+
// чтобы избежать смешивания результатов текущей и предыдущей попыток выполнения запросов при ретраях
175+
resultIssues = make([]Issue, 0)
176+
177+
queryResult, err := s.Query(
178+
ctx,
179+
`
180+
SELECT
181+
id,
182+
title,
183+
created_at
184+
FROM issues;
185+
`,
186+
query.WithTxControl(query.SnapshotReadOnlyTxControl()),
187+
query.WithParameters(
188+
ydb.ParamsBuilder().Build(),
189+
),
190+
)
191+
192+
if err != nil {
193+
return err
194+
}
195+
196+
defer func() { _ = queryResult.Close(ctx) }()
197+
198+
for {
199+
resultSet, err := queryResult.NextResultSet(ctx)
200+
if err != nil {
201+
if errors.Is(err, io.EOF) {
202+
break
203+
}
204+
205+
return err
206+
}
207+
208+
for row, err := range sugar.UnmarshalRows[Issue](resultSet.Rows(ctx)) {
209+
if err != nil {
210+
return err
211+
}
212+
213+
resultIssues = append(resultIssues, row)
214+
}
215+
}
216+
217+
return nil
218+
},
219+
)
220+
if err != nil {
221+
return resultIssues, err
222+
}
223+
224+
return resultIssues, nil
225+
}

dev-1/lesson-3.1/go/main.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"time"
7+
8+
"github.com/ydb-platform/ydb-go-sdk/v3"
9+
)
10+
11+
// author: Egor Danilov
12+
func main() {
13+
connectionCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
14+
defer cancel()
15+
16+
dsn := "grpc://localhost:2136/local"
17+
18+
db, err := ydb.Open(connectionCtx, dsn)
19+
if err != nil {
20+
log.Fatal(err)
21+
}
22+
defer db.Close(connectionCtx)
23+
24+
schemaRepository := NewSchemaRepository(db)
25+
issuesRepository := NewIssueRepository(db)
26+
27+
queryCtx, queryCancel := context.WithTimeout(context.Background(), 30*time.Second)
28+
defer queryCancel()
29+
30+
schemaRepository.DropSchema(queryCtx)
31+
schemaRepository.CreateSchema(queryCtx)
32+
33+
firstIssue, err := issuesRepository.AddIssue(queryCtx, "Ticket 1")
34+
if err != nil {
35+
log.Fatalf("Some error happened (1): %v\n", err)
36+
}
37+
38+
_, err = issuesRepository.AddIssue(queryCtx, "Ticket 2")
39+
if err != nil {
40+
log.Fatalf("Some error happened (2): %v\n", err)
41+
}
42+
43+
_, err = issuesRepository.AddIssue(queryCtx, "Ticket 3")
44+
if err != nil {
45+
log.Fatalf("Some error happened (3): %v\n", err)
46+
}
47+
48+
issues, err := issuesRepository.FindAll(queryCtx)
49+
if err != nil {
50+
log.Fatalf("Some error happened while finding all: %v\n", err)
51+
}
52+
for _, issue := range issues {
53+
log.Printf("Issue: %v\n", issue)
54+
}
55+
56+
foundFirstIssue, err := issuesRepository.FindById(queryCtx, firstIssue.Id)
57+
if err != nil {
58+
log.Fatal(err)
59+
} else {
60+
log.Printf("First issue: %v\n", foundFirstIssue)
61+
}
62+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/ydb-platform/ydb-go-sdk/v3"
8+
"github.com/ydb-platform/ydb-go-sdk/v3/query"
9+
)
10+
11+
// Репозиторий для управления схемой базы данных YDB
12+
// Отвечает за создание и удаление таблиц
13+
type SchemaRepository struct {
14+
driver *ydb.Driver
15+
}
16+
17+
func NewSchemaRepository(driver *ydb.Driver) *SchemaRepository {
18+
return &SchemaRepository{
19+
driver: driver,
20+
}
21+
}
22+
23+
// Создает таблицу issues в базе данных
24+
// Таблица содержит поля:
25+
// - id: уникальный идентификатор тикета
26+
// - title: название тикета
27+
// - created_at: время создания тикета
28+
// Все поля являются обязательными.
29+
func (repo *SchemaRepository) CreateSchema(ctx context.Context) {
30+
err := repo.driver.Query().Exec(
31+
ctx,
32+
`
33+
CREATE TABLE IF NOT EXISTS issues (
34+
id Int64 NOT NULL,
35+
title Text NOT NULL,
36+
created_at Timestamp NOT NULL,
37+
PRIMARY KEY (id)
38+
);
39+
`,
40+
query.WithParameters(ydb.ParamsBuilder().Build()),
41+
)
42+
if err != nil {
43+
log.Fatal(err)
44+
}
45+
}
46+
47+
// Удаляет таблицу issues из базы данных
48+
// Используется для очистки схемы перед созданием новой
49+
func (repo *SchemaRepository) DropSchema(ctx context.Context) {
50+
err := repo.driver.Query().Exec(
51+
ctx,
52+
"DROP TABLE IF EXISTS issues;",
53+
query.WithParameters(ydb.ParamsBuilder().Build()),
54+
)
55+
if err != nil {
56+
log.Fatal(err)
57+
}
58+
}

0 commit comments

Comments
 (0)