diff --git a/internal/backends/mysql/backend.go b/internal/backends/mysql/backend.go index 08ecfcdeb992..8234cff16919 100644 --- a/internal/backends/mysql/backend.go +++ b/internal/backends/mysql/backend.go @@ -21,12 +21,15 @@ import ( "go.uber.org/zap" "github.com/FerretDB/FerretDB/internal/backends" + "github.com/FerretDB/FerretDB/internal/backends/mysql/metadata" "github.com/FerretDB/FerretDB/internal/util/lazyerrors" "github.com/FerretDB/FerretDB/internal/util/state" ) // backend implements backends.Backend interface. -type backend struct{} +type backend struct { + r *metadata.Registry +} // NewBackendParams represents the parameters of NewBackend function. // @@ -45,7 +48,7 @@ func NewBackend(params *NewBackendParams) (backends.Backend, error) { // Close implements backends.Backend interface. func (b *backend) Close() { - // b.r.Close() + b.r.Close() } // Status implements backends.Backend interface. @@ -55,7 +58,7 @@ func (b *backend) Status(ctx context.Context, params *backends.StatusParams) (*b // Database implements backends.Backend interface. func (b *backend) Database(name string) (backends.Database, error) { - return nil, lazyerrors.New("not yet implemented.") + return newDatabase(b.r, name), nil } // ListDatabases implements backends.Database interface. diff --git a/internal/backends/mysql/collection.go b/internal/backends/mysql/collection.go new file mode 100644 index 000000000000..aa45cef8a1c8 --- /dev/null +++ b/internal/backends/mysql/collection.go @@ -0,0 +1,94 @@ +// Copyright 2021 FerretDB Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mysql + +import ( + "context" + + "github.com/FerretDB/FerretDB/internal/backends" + "github.com/FerretDB/FerretDB/internal/backends/mysql/metadata" + "github.com/FerretDB/FerretDB/internal/util/lazyerrors" +) + +// collection implements backends.Collection interface. +type collection struct { + r *metadata.Registry + dbName string + name string +} + +// newCollection creates a new Collection. +func newCollection(r *metadata.Registry, dbName, name string) backends.Collection { + return backends.CollectionContract(&collection{ + r: r, + dbName: dbName, + name: name, + }) +} + +// Query implements backends.Collection interface. +func (c *collection) Query(ctx context.Context, params *backends.QueryParams) (*backends.QueryResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// InsertAll implements backends.Collection interface. +func (c *collection) InsertAll(ctx context.Context, params *backends.InsertAllParams) (*backends.InsertAllResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// UpdateAll implements backend.Collection interface. +func (c *collection) UpdateAll(ctx context.Context, params *backends.UpdateAllParams) (*backends.UpdateAllResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// DeleteAll implements backend.Collection interface. +func (c *collection) DeleteAll(ctx context.Context, params *backends.DeleteAllParams) (*backends.DeleteAllResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// Explain implements backends.Collection interface. +func (c *collection) Explain(ctx context.Context, params *backends.ExplainParams) (*backends.ExplainResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// Stats implements backends.Collection interface. +func (c *collection) Stats(ctx context.Context, params *backends.CollectionStatsParams) (*backends.CollectionStatsResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// Compact implements backends.Collection interface. +func (c *collection) Compact(ctx context.Context, params *backends.CompactParams) (*backends.CompactResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// ListIndexes implements backends.Collection interface. +func (c *collection) ListIndexes(ctx context.Context, params *backends.ListIndexesParams) (*backends.ListIndexesResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// CreateIndexes implements backends.Collection interface. +func (c *collection) CreateIndexes(ctx context.Context, params *backends.CreateIndexesParams) (*backends.CreateIndexesResult, error) { //nolint:lll // for readability + return nil, lazyerrors.New("not yet implemented") +} + +// DropIndexes implements backends.Collection interface. +func (c *collection) DropIndexes(ctx context.Context, params *backends.DropIndexesParams) (*backends.DropIndexesResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// check interfaces +var ( + _ backends.Collection = (*collection)(nil) +) diff --git a/internal/backends/mysql/database.go b/internal/backends/mysql/database.go new file mode 100644 index 000000000000..9093da4d88fe --- /dev/null +++ b/internal/backends/mysql/database.go @@ -0,0 +1,74 @@ +// Copyright 2021 FerretDB Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mysql + +import ( + "context" + + "github.com/FerretDB/FerretDB/internal/backends" + "github.com/FerretDB/FerretDB/internal/backends/mysql/metadata" + "github.com/FerretDB/FerretDB/internal/util/lazyerrors" +) + +// database implements backends.Database interface. +type database struct { + r *metadata.Registry + name string +} + +// newDatabase creates a new Database. +func newDatabase(r *metadata.Registry, name string) backends.Database { + return backends.DatabaseContract(&database{ + r: r, + name: name, + }) +} + +// Collection implements backends.Database interface. +func (db *database) Collection(name string) (backends.Collection, error) { + return newCollection(db.r, db.name, name), nil +} + +// ListCollections implements backends.Database interface. +// +//nolint:lll // for readability +func (db *database) ListCollections(ctx context.Context, params *backends.ListCollectionsParams) (*backends.ListCollectionsResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// CreateCollection implements backends.Database interface. +func (db *database) CreateCollection(ctx context.Context, params *backends.CreateCollectionParams) error { + return lazyerrors.New("not yet implemented") +} + +// DropCollection implements backends.Database interface. +func (db *database) DropCollection(ctx context.Context, params *backends.DropCollectionParams) error { + return lazyerrors.New("not yet implemented") +} + +// RenameCollection implements backends.Database interface. +func (db *database) RenameCollection(ctx context.Context, params *backends.RenameCollectionParams) error { + return lazyerrors.New("not yet implemented") +} + +// Stats implements backends.Database interface. +func (db *database) Stats(ctx context.Context, params *backends.DatabaseStatsParams) (*backends.DatabaseStatsResult, error) { + return nil, lazyerrors.New("not yet implemented") +} + +// check interfaces +var ( + _ backends.Database = (*database)(nil) +) diff --git a/internal/backends/mysql/query_iterator.go b/internal/backends/mysql/query_iterator.go new file mode 100644 index 000000000000..093de3d7d2be --- /dev/null +++ b/internal/backends/mysql/query_iterator.go @@ -0,0 +1,143 @@ +// Copyright 2021 FerretDB Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mysql + +import ( + "context" + "fmt" + "slices" + "sync" + + "github.com/FerretDB/FerretDB/internal/backends/mysql/metadata" + "github.com/FerretDB/FerretDB/internal/handler/sjson" + "github.com/FerretDB/FerretDB/internal/types" + "github.com/FerretDB/FerretDB/internal/util/fsql" + "github.com/FerretDB/FerretDB/internal/util/iterator" + "github.com/FerretDB/FerretDB/internal/util/lazyerrors" + "github.com/FerretDB/FerretDB/internal/util/must" + "github.com/FerretDB/FerretDB/internal/util/observability" + "github.com/FerretDB/FerretDB/internal/util/resource" +) + +// queryIterator implements iterator.Interface to fetch documents from the database. +type queryIterator struct { + // the order of fields is weird to make the struct smaller due to alignment + + ctx context.Context + rows *fsql.Rows // protected by m + token *resource.Token + m sync.Mutex + onlyRecordIDs bool +} + +// Next implements iterator.Interface. +func (iter *queryIterator) Next() (struct{}, *types.Document, error) { + defer observability.FuncCall(iter.ctx)() + + iter.m.Lock() + defer iter.m.Unlock() + + var unused struct{} + + // ignore context error, if any, if iterator is already closed + if iter.rows == nil { + return unused, nil, iterator.ErrIteratorDone + } + + if err := context.Cause(iter.ctx); err != nil { + iter.close() + return unused, nil, lazyerrors.Error(err) + } + + if !iter.rows.Next() { + err := iter.rows.Err() + + iter.close() + + if err == nil { + err = iterator.ErrIteratorDone + } + + return unused, nil, lazyerrors.Error(err) + } + + columns, err := iter.rows.Columns() + if err != nil { + iter.close() + return unused, nil, lazyerrors.Error(err) + } + + var recordID int64 + var b []byte + var dest []any + + switch { + case slices.Equal(columns, []string{metadata.RecordIDColumn, metadata.DefaultColumn}): + dest = []any{&recordID, &b} + case slices.Equal(columns, []string{metadata.RecordIDColumn}): + dest = []any{&recordID} + case slices.Equal(columns, []string{metadata.DefaultColumn}): + dest = []any{&b} + default: + panic(fmt.Sprintf("cannot scan unknown columns: %v", columns)) + } + + if err = iter.rows.Scan(dest...); err != nil { + iter.close() + return unused, nil, lazyerrors.Error(err) + } + + doc := must.NotFail(types.NewDocument()) + + if !iter.onlyRecordIDs { + if doc, err = sjson.Unmarshal(b); err != nil { + iter.close() + return unused, nil, lazyerrors.Error(err) + } + } + + doc.SetRecordID(recordID) + + return unused, doc, nil +} + +// Close implements iterator.Interface. +func (iter *queryIterator) Close() { + defer observability.FuncCall(iter.ctx)() + + iter.m.Lock() + defer iter.m.Unlock() + + iter.close() +} + +// close closes iterator without holding mutex. +// +// This should be called only when the caller already holds the mutex. +func (iter *queryIterator) close() { + defer observability.FuncCall(iter.ctx)() + + if iter.rows != nil { + iter.rows.Close() + iter.rows = nil + } + + resource.Untrack(iter, iter.token) +} + +// check interfaces +var ( + _ types.DocumentsIterator = (*queryIterator)(nil) +)