Skip to content

Commit 6c8b38d

Browse files
authored
Merge pull request #540 from ydb-platform/is-table-exists
* Fixed `sugar.IsTableExists` with recursive check directory exists
2 parents 95a5b9a + b3923df commit 6c8b38d

File tree

8 files changed

+324
-31
lines changed

8 files changed

+324
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
* Fixed `sugar.IsTableExists` with recursive check directory exists
2+
* Added `sugar.IsDirectoryExists`
3+
14
## v3.42.6
25
* Implemented `driver.RowsColumnTypeDatabaseTypeName` interface in `internal/xsql.rows` struct
36
* Extended `internal/xsql.conn` struct with methods for getting `YDB` metadata

connection.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ func (c *connection) Scheme() scheme.Client {
250250
append(
251251
// prepend common params from root config
252252
[]schemeConfig.Option{
253+
schemeConfig.WithDatabaseName(c.Name()),
253254
schemeConfig.With(c.config.Common),
254255
},
255256
c.schemeOptions...,

internal/scheme/client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@ import (
1919
//nolint:nolintlint
2020
var (
2121
errNilClient = xerrors.Wrap(errors.New("scheme client is not initialized"))
22+
23+
_ interface {
24+
Database() string
25+
} = (*Client)(nil)
2226
)
2327

2428
type Client struct {
2529
config config.Config
2630
service Ydb_Scheme_V1.SchemeServiceClient
2731
}
2832

33+
func (c *Client) Database() string {
34+
return c.config.Database()
35+
}
36+
2937
func (c *Client) Close(_ context.Context) error {
3038
if c == nil {
3139
return xerrors.WithStackTrace(errNilClient)

internal/scheme/config/config.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ import (
1111
type Config struct {
1212
config.Common
1313

14-
trace trace.Scheme
14+
databaseName string
15+
trace trace.Scheme
1516
}
1617

1718
// Trace returns trace over scheme client calls
1819
func (c Config) Trace() trace.Scheme {
1920
return c.trace
2021
}
2122

23+
// Database returns database name
24+
func (c Config) Database() string {
25+
return c.databaseName
26+
}
27+
2228
type Option func(c *Config)
2329

2430
// WithTrace appends scheme trace to early defined traces
@@ -28,6 +34,13 @@ func WithTrace(trace trace.Scheme, opts ...trace.SchemeComposeOption) Option {
2834
}
2935
}
3036

37+
// WithDatabaseName applies database name
38+
func WithDatabaseName(dbName string) Option {
39+
return func(c *Config) {
40+
c.databaseName = dbName
41+
}
42+
}
43+
3144
// With applies common configuration params
3245
func With(config config.Common) Option {
3346
return func(c *Config) {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package helpers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"path"
7+
"strings"
8+
9+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
10+
"github.com/ydb-platform/ydb-go-sdk/v3/scheme"
11+
)
12+
13+
type schemeClient interface {
14+
ListDirectory(ctx context.Context, path string) (d scheme.Directory, err error)
15+
}
16+
17+
func databaseName(c schemeClient) string {
18+
if name, has := c.(interface {
19+
Database() string
20+
}); has {
21+
return name.Database()
22+
}
23+
return "/"
24+
}
25+
26+
func IsDirectoryExists(ctx context.Context, c schemeClient, directory string) (exists bool, _ error) {
27+
parentDirectory, childDirectory := path.Split(directory)
28+
parentDirectory = strings.TrimRight(parentDirectory, "/")
29+
if parentDirectory != databaseName(c) {
30+
if exists, err := IsDirectoryExists(ctx, c, parentDirectory); err != nil {
31+
return false, xerrors.WithStackTrace(err)
32+
} else if !exists {
33+
return false, nil
34+
}
35+
}
36+
d, err := c.ListDirectory(ctx, parentDirectory)
37+
if err != nil {
38+
return false, xerrors.WithStackTrace(err)
39+
}
40+
for _, e := range d.Children {
41+
if e.Name != childDirectory {
42+
continue
43+
}
44+
if e.Type != scheme.EntryDirectory {
45+
return false, xerrors.WithStackTrace(fmt.Errorf(
46+
"entry '%s' in path '%s' is not a direectory: %s",
47+
childDirectory, parentDirectory, e.Type.String(),
48+
))
49+
}
50+
return true, nil
51+
}
52+
return false, nil
53+
}
54+
55+
func IsTableExists(ctx context.Context, c schemeClient, absTablePath string) (exists bool, _ error) {
56+
if !strings.HasPrefix(absTablePath, databaseName(c)) {
57+
return false, xerrors.WithStackTrace(fmt.Errorf(
58+
"table path '%s' must be inside database '%s'",
59+
absTablePath, databaseName(c),
60+
))
61+
} else if absTablePath == databaseName(c) {
62+
return false, xerrors.WithStackTrace(fmt.Errorf(
63+
"table path '%s' cannot be equals database name '%s'",
64+
absTablePath, databaseName(c),
65+
))
66+
}
67+
directory, tableName := path.Split(absTablePath)
68+
if exists, err := IsDirectoryExists(ctx, c, strings.TrimRight(directory, "/")); err != nil {
69+
return false, xerrors.WithStackTrace(err)
70+
} else if !exists {
71+
return false, nil
72+
}
73+
d, err := c.ListDirectory(ctx, directory)
74+
if err != nil {
75+
return false, err
76+
}
77+
for _, e := range d.Children {
78+
if e.Name != tableName {
79+
continue
80+
}
81+
if e.Type != scheme.EntryTable {
82+
return false, fmt.Errorf(
83+
"entry '%s' in path '%s' is not a table: %s",
84+
tableName, directory, e.Type.String(),
85+
)
86+
}
87+
return true, nil
88+
}
89+
return false, nil
90+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package helpers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/ydb-platform/ydb-go-sdk/v3/scheme"
12+
)
13+
14+
type isDirectoryExistsSchemeClient struct {
15+
dbName string
16+
existingPath string
17+
}
18+
19+
func (c isDirectoryExistsSchemeClient) Database() string {
20+
return c.dbName
21+
}
22+
23+
func (c isDirectoryExistsSchemeClient) ListDirectory(ctx context.Context, path string) (
24+
d scheme.Directory, err error,
25+
) {
26+
if c.existingPath == path {
27+
return scheme.Directory{
28+
Entry: scheme.Entry{
29+
Name: path,
30+
Type: scheme.EntryDirectory,
31+
},
32+
}, nil
33+
}
34+
if strings.HasPrefix(c.existingPath, path) {
35+
children := strings.Split(strings.TrimLeft(c.existingPath, path), "/")
36+
return scheme.Directory{
37+
Entry: scheme.Entry{
38+
Name: path,
39+
Type: scheme.EntryDirectory,
40+
},
41+
Children: []scheme.Entry{
42+
{
43+
Name: children[0],
44+
Type: scheme.EntryDirectory,
45+
},
46+
},
47+
}, nil
48+
}
49+
return d, fmt.Errorf("path '%s' not found in '%s'", path, c)
50+
}
51+
52+
func TestIsDirectoryExists(t *testing.T) {
53+
for _, tt := range []struct {
54+
checkPath string
55+
client isDirectoryExistsSchemeClient
56+
exists bool
57+
err bool
58+
}{
59+
{
60+
checkPath: "/a/b/c/d",
61+
client: isDirectoryExistsSchemeClient{"/a", "/a/"},
62+
exists: false,
63+
err: false,
64+
},
65+
{
66+
checkPath: "/a/b/c/d",
67+
client: isDirectoryExistsSchemeClient{"/a", "/a/b/"},
68+
exists: false,
69+
err: false,
70+
},
71+
{
72+
checkPath: "/a/b/c/d",
73+
client: isDirectoryExistsSchemeClient{"/a", "/a/b/c/"},
74+
exists: false,
75+
err: false,
76+
},
77+
{
78+
checkPath: "/a/b/c/d",
79+
client: isDirectoryExistsSchemeClient{"/a", "/a/b/c/d/"},
80+
exists: true,
81+
err: false,
82+
},
83+
{
84+
checkPath: "/a/b/c/d",
85+
client: isDirectoryExistsSchemeClient{"/a", "/a/b/c/d/e/"},
86+
exists: true,
87+
err: false,
88+
},
89+
} {
90+
t.Run("", func(t *testing.T) {
91+
exists, err := IsDirectoryExists(context.Background(), tt.client, tt.checkPath)
92+
if tt.err {
93+
require.Error(t, err)
94+
} else {
95+
require.NoError(t, err)
96+
}
97+
require.Equal(t, tt.exists, exists)
98+
})
99+
}
100+
}
101+
102+
type isTableExistsSchemeClient struct {
103+
dbName string
104+
tablePath string
105+
}
106+
107+
func (c isTableExistsSchemeClient) Database() string {
108+
return c.dbName
109+
}
110+
111+
func (c isTableExistsSchemeClient) ListDirectory(ctx context.Context, path string) (
112+
d scheme.Directory, err error,
113+
) {
114+
if strings.HasPrefix(c.tablePath, path) {
115+
children := strings.Split(strings.TrimLeft(c.tablePath, path), "/")
116+
switch {
117+
case len(children) == 1:
118+
return scheme.Directory{
119+
Entry: scheme.Entry{
120+
Name: path,
121+
Type: scheme.EntryDirectory,
122+
},
123+
Children: []scheme.Entry{
124+
{
125+
Name: children[0],
126+
Type: scheme.EntryTable,
127+
},
128+
},
129+
}, nil
130+
case len(children) > 1:
131+
return scheme.Directory{
132+
Entry: scheme.Entry{
133+
Name: path,
134+
Type: scheme.EntryDirectory,
135+
},
136+
Children: []scheme.Entry{
137+
{
138+
Name: children[0],
139+
Type: scheme.EntryDirectory,
140+
},
141+
},
142+
}, nil
143+
default:
144+
return scheme.Directory{
145+
Entry: scheme.Entry{
146+
Name: "",
147+
Type: scheme.EntryDirectory,
148+
},
149+
}, nil
150+
}
151+
}
152+
return d, fmt.Errorf("path '%s' not found in '%s'", path, c)
153+
}
154+
155+
func TestIsTableExists(t *testing.T) {
156+
for _, tt := range []struct {
157+
checkPath string
158+
client isTableExistsSchemeClient
159+
exists bool
160+
err bool
161+
}{
162+
{
163+
checkPath: "/a/b/c/d",
164+
client: isTableExistsSchemeClient{"/b", "/a/b"},
165+
exists: false,
166+
err: true,
167+
},
168+
{
169+
checkPath: "/a/b/c/d",
170+
client: isTableExistsSchemeClient{"/a", "/a/b"},
171+
exists: false,
172+
err: true,
173+
},
174+
{
175+
checkPath: "/a/b/c/d",
176+
client: isTableExistsSchemeClient{"/a", "/a/b/c"},
177+
exists: false,
178+
err: true,
179+
},
180+
{
181+
checkPath: "/a/b/c/d",
182+
client: isTableExistsSchemeClient{"/a", "/a/b/c/d"},
183+
exists: true,
184+
err: false,
185+
},
186+
{
187+
checkPath: "/a/b/c/d",
188+
client: isTableExistsSchemeClient{"/a", "/a/b/c/d/e"},
189+
exists: false,
190+
err: true,
191+
},
192+
} {
193+
t.Run("", func(t *testing.T) {
194+
exists, err := IsTableExists(context.Background(), tt.client, tt.checkPath)
195+
if tt.err {
196+
require.Error(t, err)
197+
} else {
198+
require.NoError(t, err)
199+
}
200+
require.Equal(t, tt.exists, exists)
201+
})
202+
}
203+
}

internal/scheme/helpers/table_exists.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)