diff --git a/cmd/layotto/main.go b/cmd/layotto/main.go index 8067c9ff56..d91b0e199c 100644 --- a/cmd/layotto/main.go +++ b/cmd/layotto/main.go @@ -122,6 +122,7 @@ import ( sequencer_etcd "mosn.io/layotto/components/sequencer/etcd" sequencer_inmemory "mosn.io/layotto/components/sequencer/in-memory" sequencer_mongo "mosn.io/layotto/components/sequencer/mongo" + sequencer_mysql "mosn.io/layotto/components/sequencer/mysql" sequencer_redis "mosn.io/layotto/components/sequencer/redis" sequencer_zookeeper "mosn.io/layotto/components/sequencer/zookeeper" @@ -425,6 +426,9 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp runtime_sequencer.NewFactory("in-memory", func() sequencer.Store { return sequencer_inmemory.NewInMemorySequencer() }), + runtime_sequencer.NewFactory("mysql", func() sequencer.Store { + return sequencer_mysql.NewMySQLSequencer(log.DefaultLogger) + }), ), // secretstores runtime.WithSecretStoresFactory( diff --git a/cmd/layotto_multiple_api/main.go b/cmd/layotto_multiple_api/main.go index 7d1810e7af..1f692a244b 100644 --- a/cmd/layotto_multiple_api/main.go +++ b/cmd/layotto_multiple_api/main.go @@ -130,6 +130,7 @@ import ( sequencer_etcd "mosn.io/layotto/components/sequencer/etcd" sequencer_inmemory "mosn.io/layotto/components/sequencer/in-memory" sequencer_mongo "mosn.io/layotto/components/sequencer/mongo" + sequencer_mysql "mosn.io/layotto/components/sequencer/mysql" sequencer_redis "mosn.io/layotto/components/sequencer/redis" sequencer_zookeeper "mosn.io/layotto/components/sequencer/zookeeper" @@ -436,6 +437,9 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp runtime_sequencer.NewFactory("in-memory", func() sequencer.Store { return sequencer_inmemory.NewInMemorySequencer() }), + runtime_sequencer.NewFactory("mysql", func() sequencer.Store { + return sequencer_mysql.NewMySQLSequencer(log.DefaultLogger) + }), ), // secretstores runtime.WithSecretStoresFactory( diff --git a/components/go.mod b/components/go.mod index cf51728a23..47c0809429 100644 --- a/components/go.mod +++ b/components/go.mod @@ -3,6 +3,7 @@ module mosn.io/layotto/components go 1.14 require ( + github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/alicebob/miniredis/v2 v2.16.0 github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible github.com/apache/dubbo-go-hessian2 v1.10.2 diff --git a/components/go.sum b/components/go.sum index 2c679fe192..d5d2d6977f 100644 --- a/components/go.sum +++ b/components/go.sum @@ -38,6 +38,8 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0p github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnloDp7xxV0YvstAE7nKTaM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= diff --git a/components/pkg/utils/mysql.go b/components/pkg/utils/mysql.go new file mode 100644 index 0000000000..be4b0993e6 --- /dev/null +++ b/components/pkg/utils/mysql.go @@ -0,0 +1,81 @@ +// +// Copyright 2021 Layotto Authors +// 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 utils + +import ( + "database/sql" + "fmt" +) + +var ( + Db *sql.DB +) + +const ( + defaultTableName = "layotto_sequencer" + defaultTableNameKey = "tableName" + dataBaseName = "dataBaseName" + userName = "userName" + defaultPassword = "password" + mysqlUrl = "mysqlUrl" +) + +type MySQLMetadata struct { + TableName string + DataBaseName string + UserName string + Password string + MysqlUrl string + Db *sql.DB +} + +func ParseMySQLMetadata(properties map[string]string) (MySQLMetadata, error) { + m := MySQLMetadata{} + + if val, ok := properties[defaultTableNameKey]; ok && val != "" { + m.TableName = val + } + if val, ok := properties[dataBaseName]; ok && val != "" { + m.DataBaseName = val + } + if val, ok := properties[userName]; ok && val != "" { + m.UserName = val + } + if val, ok := properties[defaultPassword]; ok && val != "" { + m.Password = val + } + if val, ok := properties[mysqlUrl]; ok && val != "" { + m.MysqlUrl = val + } + return m, nil +} + +func NewMySQLClient(meta MySQLMetadata) error { + + val := meta + if val.TableName == "" { + val.TableName = defaultTableName + } + meta.Db.Begin() + createTable := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s ( + sequencer_key VARCHAR(255), + sequencer_value INT, + UNIQUE INDEX (sequencer_key));`, val.TableName) + _, err := meta.Db.Exec(createTable) + defer meta.Db.Close() + if err != nil { + return err + } + return nil +} diff --git a/components/sequencer/mysql/mysql.go b/components/sequencer/mysql/mysql.go new file mode 100644 index 0000000000..fe2502ed91 --- /dev/null +++ b/components/sequencer/mysql/mysql.go @@ -0,0 +1,162 @@ +// +// Copyright 2021 Layotto Authors +// 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 ( + "database/sql" + "fmt" + + "mosn.io/pkg/log" + + "mosn.io/layotto/components/pkg/utils" + "mosn.io/layotto/components/sequencer" +) + +type MySQLSequencer struct { + metadata utils.MySQLMetadata + biggerThan map[string]int64 + logger log.ErrorLogger + db *sql.DB +} + +func NewMySQLSequencer(logger log.ErrorLogger) *MySQLSequencer { + s := &MySQLSequencer{ + logger: logger, + } + + return s +} + +func (e *MySQLSequencer) Init(config sequencer.Configuration) error { + + m, err := utils.ParseMySQLMetadata(config.Properties) + + if err != nil { + return err + } + e.metadata = m + e.metadata.Db = e.db + e.biggerThan = config.BiggerThan + + if err = utils.NewMySQLClient(e.metadata); err != nil { + return err + } + + if len(e.biggerThan) > 0 { + var Key string + var Value int64 + for k, bt := range e.biggerThan { + if bt > 0 { + m.Db.QueryRow(fmt.Sprintf("SELECT sequencer_key,sequencer_value FROM %s WHERE sequencer_key = ?", m.TableName), k).Scan(&Key, &Value) + if Value < bt { + return fmt.Errorf("mysql sequencer error: can not satisfy biggerThan guarantee.key: %s,key in MySQL: %s", k, Key) + } + } + } + } + return nil +} + +func (e *MySQLSequencer) GetNextId(req *sequencer.GetNextIdRequest) (*sequencer.GetNextIdResponse, error) { + + metadata, err := utils.ParseMySQLMetadata(req.Metadata) + metadata.Db = e.db + var Key string + var Value int64 + var Version int64 + var oldVersion int64 + if err != nil { + return nil, err + } + begin, err := metadata.Db.Begin() + if err != nil { + return nil, err + } + err = begin.QueryRow("SELECT sequencer_key, sequencer_value, version FROM ? WHERE sequencer_key = ?", metadata.TableName, req.Key).Scan(&Key, &Value, &oldVersion) + + if err == sql.ErrNoRows { + Value = 1 + Version = 1 + _, err := begin.Exec("INSERT INTO ?(sequencer_key, sequencer_value, version) VALUES(?,?,?)", metadata.TableName, req.Key, Value, Version) + if err != nil { + begin.Rollback() + return nil, err + } + + } else { + _, err := begin.Exec("UPDATE ? SET sequencer_value +=1, version += 1 WHERE sequencer_key = ? and version = ?", metadata.TableName, req.Key, oldVersion) + if err != nil { + begin.Rollback() + return nil, err + } + } + defer e.Close(metadata.Db) + + return &sequencer.GetNextIdResponse{ + NextId: Value, + }, nil +} + +func (e *MySQLSequencer) GetSegment(req *sequencer.GetSegmentRequest) (support bool, result *sequencer.GetSegmentResponse, err error) { + + if req.Size == 0 { + return true, nil, nil + } + + metadata, err := utils.ParseMySQLMetadata(req.Metadata) + + var Key string + var Value int64 + var Version int64 + var oldVersion int64 + if err != nil { + return false, nil, err + } + + metadata.Db = e.db + + begin, err := metadata.Db.Begin() + if err != nil { + return false, nil, err + } + err = begin.QueryRow("SELECT sequencer_key, sequencer_value, version FROM ? WHERE sequencer_key == ?", metadata.TableName, req.Key).Scan(&Key, &Value, &oldVersion) + if err == sql.ErrNoRows { + Value = int64(req.Size) + Version = 1 + _, err := begin.Exec("INSERT INTO ?(sequencer_key, sequencer_value, version) VALUES(?,?,?)", metadata.TableName, req.Key, Value, Version) + if err != nil { + begin.Rollback() + return false, nil, err + } + + } else { + Value += int64(req.Size) + _, err1 := begin.Exec("UPDATE ? SET sequencer_value = ?, version += 1 WHERE sequencer_key = ? AND version = ?", metadata.TableName, Value, req.Key, oldVersion) + if err1 != nil { + begin.Rollback() + return false, nil, err1 + } + } + defer e.Close(metadata.Db) + + return false, &sequencer.GetSegmentResponse{ + From: Value - int64(req.Size) + 1, + To: Value, + }, nil +} + +func (e *MySQLSequencer) Close(db *sql.DB) error { + + return db.Close() +} diff --git a/components/sequencer/mysql/mysql_test.go b/components/sequencer/mysql/mysql_test.go new file mode 100644 index 0000000000..7c6df65b8b --- /dev/null +++ b/components/sequencer/mysql/mysql_test.go @@ -0,0 +1,346 @@ +// +// Copyright 2021 Layotto Authors +// 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 ( + "database/sql" + "database/sql/driver" + "errors" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" + "mosn.io/pkg/log" + + "mosn.io/layotto/components/sequencer" +) + +const ( + MySQLUrl = "localhost:3306" + Value = 1 + Key = "sequenceKey" + Size = 50 + Version = 1 + tableName = "layotto_sequencer" + dataBaseName = "layotto" + userName = "root" + password = "123456" +) + +func TestMySQLSequencer_Init(t *testing.T) { + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + comp := NewMySQLSequencer(log.DefaultLogger) + comp.db = db + + cfg := sequencer.Configuration{ + Properties: make(map[string]string), + BiggerThan: make(map[string]int64), + } + mock.ExpectBegin() + mock.ExpectExec("CREATE TABLE IF NOT EXISTS").WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() + + cfg.Properties["tableName"] = tableName + cfg.Properties["dataBaseName"] = dataBaseName + cfg.Properties["userName"] = userName + cfg.Properties["password"] = password + cfg.Properties["mysqlUrl"] = MySQLUrl + err = comp.Init(cfg) + + assert.Nil(t, err) +} + +func TestMySQLSequencer_GetNextId(t *testing.T) { + + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rows := sqlmock.NewRows([]string{"sequencer_key", "sequencer_value", "version"}).AddRow([]driver.Value{Key, Value, Version}...) + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectExec("UPDATE").WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec("INSERT INTO").WithArgs(tableName, Key, Value, Version).WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + req := &sequencer.GetNextIdRequest{Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + + comp.db = db + _, err = comp.GetNextId(req) + if err != nil { + t.Errorf("error was not expected while updating stats: %s", err) + } + + assert.NoError(t, err) +} + +func TestMySQLSequencer_GetSegment(t *testing.T) { + + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + rows := sqlmock.NewRows([]string{"sequencer_key", "sequencer_value", "version"}).AddRow([]driver.Value{Key, Value, Version}...) + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectExec("UPDATE").WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec("INSERT INTO").WithArgs(tableName, Key, Size, Version).WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + req := &sequencer.GetSegmentRequest{Size: Size, Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + + comp.db = db + _, _, err = comp.GetSegment(req) + if err != nil { + t.Errorf("error was not expected while updating stats: %s", err) + } + + assert.NoError(t, err) +} + +func TestMySQLSequencer_Close(t *testing.T) { + + var MySQLUrl = MySQLUrl + + db, _, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + comp := NewMySQLSequencer(log.DefaultLogger) + + cfg := sequencer.Configuration{ + BiggerThan: nil, + Properties: make(map[string]string), + } + + cfg.Properties["MySQLHost"] = MySQLUrl + + comp.db = db + _ = comp.Init(cfg) + + comp.Close(db) +} + +func TestMySQLSequencer_Segment_Insert(t *testing.T) { + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnError(sql.ErrNoRows) + mock.ExpectExec("INSERT INTO").WithArgs(tableName, Key, Size, Version).WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + segmentReq := &sequencer.GetSegmentRequest{Size: Size, Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + comp.db = db + _, _, err = comp.GetSegment(segmentReq) + if err != nil { + t.Errorf("error was not expected while updating stats: %s", err) + } + + assert.NoError(t, err) +} + +func TestMySQLSequencer_GetNextId_Insert(t *testing.T) { + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnError(sql.ErrNoRows) + mock.ExpectExec("INSERT INTO").WithArgs(tableName, Key, Value, Version).WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + req := &sequencer.GetNextIdRequest{Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + + comp.db = db + _, err = comp.GetNextId(req) + if err != nil { + t.Errorf("error was not expected while updating stats: %s", err) + } + + assert.NoError(t, err) +} + +func TestMySQLSequencer_GetNextId_InsertError(t *testing.T) { + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnError(sql.ErrNoRows) + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + req := &sequencer.GetNextIdRequest{Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + + comp.db = db + _, err = comp.GetNextId(req) + + assert.Error(t, err) +} + +func TestMySQLSequencer_Segment_InsertError(t *testing.T) { + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnError(sql.ErrNoRows) + mock.ExpectExec("INSERT INTO").WithArgs(tableName, Key, Size).WillReturnError(errors.New("insert error")) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + segmentReq := &sequencer.GetSegmentRequest{Size: Size, Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + comp.db = db + _, _, err = comp.GetSegment(segmentReq) + + assert.Error(t, err) +} + +func TestMySQLSequencer_GetNextId_UpdateError(t *testing.T) { + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + + rows := sqlmock.NewRows([]string{"sequencer_key", "sequencer_value", "version"}).AddRow([]driver.Value{Key, Value, Version}...) + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectExec("UPDATE").WillReturnError(errors.New("update error")) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + req := &sequencer.GetNextIdRequest{Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + + comp.db = db + _, err = comp.GetNextId(req) + + assert.Error(t, err) +} + +func TestMySQLSequencer_Segment_UpdateError(t *testing.T) { + comp := NewMySQLSequencer(log.DefaultLogger) + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + rows := sqlmock.NewRows([]string{"sequencer_key", "sequencer_value", "version"}).AddRow([]driver.Value{Key, Value, Version}...) + + mock.ExpectBegin() + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectExec("UPDATE").WillReturnError(errors.New("update error")) + mock.ExpectCommit() + + properties := make(map[string]string) + + properties["tableName"] = tableName + properties["dataBaseName"] = dataBaseName + properties["userName"] = userName + properties["password"] = password + properties["mysqlUrl"] = MySQLUrl + + req := &sequencer.GetSegmentRequest{Size: Size, Key: Key, Options: sequencer.SequencerOptions{AutoIncrement: sequencer.STRONG}, Metadata: properties} + + comp.db = db + _, _, err = comp.GetSegment(req) + + assert.Error(t, err) +} diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 949219f356..0c81dc3d4a 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -73,6 +73,7 @@ - [Redis](en/component_specs/sequencer/redis.md) - [Zookeeper](en/component_specs/sequencer/zookeeper.md) - [MongoDB](en/component_specs/sequencer/mongo.md) + - [Mysql](en/component_specs/sequencer/mysql.md) - [Secret Store](en/component_specs/secret/common.md) - [How to deploy and upgrade Layotto](en/operation/) - Design documents diff --git a/docs/en/component_specs/sequencer/mysql.md b/docs/en/component_specs/sequencer/mysql.md new file mode 100644 index 0000000000..fd1aa41cf7 --- /dev/null +++ b/docs/en/component_specs/sequencer/mysql.md @@ -0,0 +1,18 @@ +# mysql + +## metadata fields +| Field | Required | Description | +|-----------|-----|---------------------------------------------------------------------------| +| mysqlUrl | Y | mysql's service address | +| username | N | specify username | +| password | N | specify password | +| dataBaseName| N | mysql dataBaseName | +| connectionString| Y | Database connection string such as "root:123456@tcp(127.0.0.1:3306)/test" | + +## How to start mysql + +If you want to run the mysql demo, you need to start a mysql server with Docker first. + +```shell +docker run --name mysql-test -d -p 3306:3306 mysql +``` \ No newline at end of file diff --git a/docs/zh/_sidebar.md b/docs/zh/_sidebar.md index e61e5a6c6c..ccfdedea27 100644 --- a/docs/zh/_sidebar.md +++ b/docs/zh/_sidebar.md @@ -79,7 +79,8 @@ - [Redis](zh/component_specs/sequencer/redis.md) - [Zookeeper](zh/component_specs/sequencer/zookeeper.md) - [MongoDB](zh/component_specs/sequencer/mongo.md) - - [Secret Store](zh/component_specs/secret/common.md) + - [Mysql](zh/component_specs/sequencer/mysql.md) + - [Secret Store](zh/component_specs/secret/common.md) - [自定义组件](zh/component_specs/custom/common.md) - [如何部署、升级 Layotto](zh/operation/) - [如何本地开发、本地调试](zh/operation/local.md) diff --git a/docs/zh/component_specs/sequencer/mysql.md b/docs/zh/component_specs/sequencer/mysql.md new file mode 100644 index 0000000000..6e96769381 --- /dev/null +++ b/docs/zh/component_specs/sequencer/mysql.md @@ -0,0 +1,18 @@ +# mysql + +## 配置项说明 +| 字段 | 必填 | 说明 | +|-----------|-----|------------| +| mysqlUrl | Y | mysql的服务地址 | +| username | N | mysql用户名 | + | password | N | mysql密码 | + | dataBaseName| N | mysql数据库名称 | +| connectionString| Y | 数据库连接串 例如 "root:123456@tcp(127.0.0.1:3306)/test" | + +## 怎么启动 mysql + +如果想启动mysql的demo,需要先用Docker启动一个mysql命令: + +```shell +docker run --name mysql-test -d -p 3306:3306 mysql +``` \ No newline at end of file