Skip to content

Commit

Permalink
Merge pull request #1364 from dolthub/daylon/example
Browse files Browse the repository at this point in the history
Updated and expanded engine examples
  • Loading branch information
zachmu authored Nov 1, 2022
2 parents 938ee5a + 8fe82d5 commit 621dc30
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 75 deletions.
78 changes: 37 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,80 +274,76 @@ database implementation:
package main

import (
"fmt"
"time"

"github.com/dolthub/go-mysql-server/sql/information_schema"

sqle "github.com/dolthub/go-mysql-server"
"github.com/dolthub/go-mysql-server/memory"
"github.com/dolthub/go-mysql-server/server"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/information_schema"
)

// Example of how to implement a MySQL server based on a Engine:
//
// ```
// > mysql --host=127.0.0.1 --port=3306 -u root mydb -e "SELECT * FROM mytable"
// +----------+-------------------+-------------------------------+---------------------+
// | name | email | phone_numbers | created_at |
// +----------+-------------------+-------------------------------+---------------------+
// | John Doe | [email protected] | ["555-555-555"] | 2018-04-18 09:41:13 |
// | John Doe | [email protected] | [] | 2018-04-18 09:41:13 |
// | Jane Doe | [email protected] | [] | 2018-04-18 09:41:13 |
// | Evil Bob | [email protected] | ["555-666-555","666-666-666"] | 2018-04-18 09:41:13 |
// +----------+-------------------+-------------------------------+---------------------+
// ```
var (
dbName = "mydb"
tableName = "mytable"
address = "localhost"
port = 3306
)

func main() {
ctx := sql.NewEmptyContext()
engine := sqle.NewDefault(
sql.NewDatabaseProvider(
createTestDatabase(),
createTestDatabase(ctx),
information_schema.NewInformationSchemaDatabase(),
))
engine.Analyzer.Catalog.MySQLDb.AddRootAccount()

config := server.Config{
Protocol: "tcp",
Address: "localhost:3306",
Address: fmt.Sprintf("%s:%d", address, port),
}
s, err := server.NewDefaultServer(config, engine)
if err != nil {
panic(err)
}
s.Start()
if err = s.Start(); err != nil {
panic(err)
}
}

func createTestDatabase() *memory.Database {
const (
dbName = "mydb"
tableName = "mytable"
)
func createTestDatabase(ctx *sql.Context) *memory.Database {
db := memory.NewDatabase(dbName)
table := memory.NewTable(tableName, sql.NewPrimaryKeySchema(sql.Schema{
{Name: "name", Type: sql.Text, Nullable: false, Source: tableName},
{Name: "email", Type: sql.Text, Nullable: false, Source: tableName},
{Name: "name", Type: sql.Text, Nullable: false, Source: tableName, PrimaryKey: true},
{Name: "email", Type: sql.Text, Nullable: false, Source: tableName, PrimaryKey: true},
{Name: "phone_numbers", Type: sql.JSON, Nullable: false, Source: tableName},
{Name: "created_at", Type: sql.Datetime, Nullable: false, Source: tableName},
}), nil)

}), db.GetForeignKeyCollection())
db.AddTable(tableName, table)
ctx := sql.NewEmptyContext()
_ = table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.MustJSON(`["555-555-555"]`), time.Now()))
_ = table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.MustJSON(`[]`), time.Now()))
_ = table.Insert(ctx, sql.NewRow("Jane Doe", "[email protected]", sql.MustJSON(`[]`), time.Now()))
_ = table.Insert(ctx, sql.NewRow("Jane Deo", "[email protected]", sql.MustJSON(`["556-565-566", "777-777-777"]`), time.Now()))

creationTime := time.Unix(0, 1667304000000001000).UTC()
_ = table.Insert(ctx, sql.NewRow("Jane Deo", "[email protected]", sql.MustJSON(`["556-565-566", "777-777-777"]`), creationTime))
_ = table.Insert(ctx, sql.NewRow("Jane Doe", "[email protected]", sql.MustJSON(`[]`), creationTime))
_ = table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.MustJSON(`["555-555-555"]`), creationTime))
_ = table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.MustJSON(`[]`), creationTime))
return db
}
```

Then, you can connect to the server with any MySQL client:

```bash
> mysql --host=127.0.0.1 --port=3306 -u root mydb -e "SELECT * FROM mytable"
+----------+-------------------+-------------------------------+---------------------+
| name | email | phone_numbers | created_at |
+----------+-------------------+-------------------------------+---------------------+
| John Doe | john@doe.com | ["555-555-555"] | 2018-04-18 10:42:58 |
| John Doe | johnalt@doe.com | [] | 2018-04-18 10:42:58 |
| Jane Doe | jane@doe.com | [] | 2018-04-18 10:42:58 |
| Jane Doe | janedeo@gmail.com | ["556-565-566","777-777-777"] | 2018-04-18 10:42:58 |
+----------+-------------------+-------------------------------+---------------------+
> mysql --host=localhost --port=3306 --user=root mydb --execute="SELECT * FROM mytable;"
+----------+-------------------+-------------------------------+----------------------------+
| name | email | phone_numbers | created_at |
+----------+-------------------+-------------------------------+----------------------------+
| Jane Deo | janedeo@gmail.com | ["556-565-566","777-777-777"] | 2022-11-01 12:00:00.000001 |
| Jane Doe | jane@doe.com | [] | 2022-11-01 12:00:00.000001 |
| John Doe | john@doe.com | ["555-555-555"] | 2022-11-01 12:00:00.000001 |
| John Doe | johnalt@doe.com | [] | 2022-11-01 12:00:00.000001 |
+----------+-------------------+-------------------------------+----------------------------+
```

See the complete example [here](_example/main.go).
Expand Down
83 changes: 49 additions & 34 deletions _example/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2021 Dolthub, Inc.
// Copyright 2020-2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -15,69 +15,84 @@
package main

import (
"fmt"
"time"

"github.com/dolthub/go-mysql-server/sql/information_schema"

sqle "github.com/dolthub/go-mysql-server"
"github.com/dolthub/go-mysql-server/memory"
"github.com/dolthub/go-mysql-server/server"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/information_schema"
)

// Example of how to implement a MySQL server based on a Engine:
// This is an example of how to implement a MySQL server.
// After running the example, you may connect to it using the following:
//
// ```
// > mysql --host=127.0.0.1 --port=3306 -u root mydb -e "SELECT * FROM mytable"
// +----------+-------------------+-------------------------------+---------------------+
// | name | email | phone_numbers | created_at |
// +----------+-------------------+-------------------------------+---------------------+
// | John Doe | [email protected] | ["555-555-555"] | 2018-04-18 09:41:13 |
// | John Doe | [email protected] | [] | 2018-04-18 09:41:13 |
// | Jane Doe | [email protected] | [] | 2018-04-18 09:41:13 |
// | Evil Bob | [email protected] | ["555-666-555","666-666-666"] | 2018-04-18 09:41:13 |
// +----------+-------------------+-------------------------------+---------------------+
// ```
// > mysql --host=localhost --port=3306 --user=root mydb --execute="SELECT * FROM mytable;"
// +----------+-------------------+-------------------------------+----------------------------+
// | name | email | phone_numbers | created_at |
// +----------+-------------------+-------------------------------+----------------------------+
// | Jane Deo | [email protected] | ["556-565-566","777-777-777"] | 2022-11-01 12:00:00.000001 |
// | Jane Doe | [email protected] | [] | 2022-11-01 12:00:00.000001 |
// | John Doe | [email protected] | ["555-555-555"] | 2022-11-01 12:00:00.000001 |
// | John Doe | [email protected] | [] | 2022-11-01 12:00:00.000001 |
// +----------+-------------------+-------------------------------+----------------------------+
//
// The included MySQL client is used in this example, however any MySQL-compatible client will work.

var (
dbName = "mydb"
tableName = "mytable"
address = "localhost"
port = 3306
)

// For go-mysql-server developers: Remember to update the snippet in the README when this file changes.

func main() {
ctx := sql.NewEmptyContext()
engine := sqle.NewDefault(
sql.NewDatabaseProvider(
createTestDatabase(),
createTestDatabase(ctx),
information_schema.NewInformationSchemaDatabase(),
))
engine.Analyzer.Catalog.MySQLDb.AddRootAccount()
// This variable may be found in the "users_example.go" file. Please refer to that file for a walkthrough on how to
// set up the "mysql" database to allow user creation and user checking when establishing connections. This is set
// to false for this example, but feel free to play around with it and see how it works.
if enableUsers {
if err := enableUserAccounts(ctx, engine); err != nil {
panic(err)
}
}

config := server.Config{
Protocol: "tcp",
Address: "localhost:3306",
Address: fmt.Sprintf("%s:%d", address, port),
}

s, err := server.NewDefaultServer(config, engine)
if err != nil {
panic(err)
}

s.Start()
if err = s.Start(); err != nil {
panic(err)
}
}

func createTestDatabase() *memory.Database {
const (
dbName = "mydb"
tableName = "mytable"
)

func createTestDatabase(ctx *sql.Context) *memory.Database {
db := memory.NewDatabase(dbName)
table := memory.NewTable(tableName, sql.NewPrimaryKeySchema(sql.Schema{
{Name: "name", Type: sql.Text, Nullable: false, Source: tableName, PrimaryKey: true},
{Name: "email", Type: sql.Text, Nullable: false, Source: tableName, PrimaryKey: true},
{Name: "phone_numbers", Type: sql.JSON, Nullable: false, Source: tableName},
{Name: "created_at", Type: sql.Timestamp, Nullable: false, Source: tableName},
{Name: "created_at", Type: sql.Datetime, Nullable: false, Source: tableName},
}), db.GetForeignKeyCollection())

creationTime := time.Unix(1524044473, 0).UTC()
db.AddTable(tableName, table)
ctx := sql.NewEmptyContext()
table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.JSONDocument{Val: []string{"555-555-555"}}, creationTime))
table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.JSONDocument{Val: []string{}}, creationTime))
table.Insert(ctx, sql.NewRow("Jane Doe", "[email protected]", sql.JSONDocument{Val: []string{}}, creationTime))
table.Insert(ctx, sql.NewRow("Evil Bob", "[email protected]", sql.JSONDocument{Val: []string{"555-666-555", "666-666-666"}}, creationTime))

creationTime := time.Unix(0, 1667304000000001000).UTC()
_ = table.Insert(ctx, sql.NewRow("Jane Deo", "[email protected]", sql.MustJSON(`["556-565-566", "777-777-777"]`), creationTime))
_ = table.Insert(ctx, sql.NewRow("Jane Doe", "[email protected]", sql.MustJSON(`[]`), creationTime))
_ = table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.MustJSON(`["555-555-555"]`), creationTime))
_ = table.Insert(ctx, sql.NewRow("John Doe", "[email protected]", sql.MustJSON(`[]`), creationTime))
return db
}
129 changes: 129 additions & 0 deletions _example/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2022 Dolthub, 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 main

import (
"database/sql"
"fmt"
"net"
"testing"

_ "github.com/go-sql-driver/mysql"
"github.com/gocraft/dbr/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var expectedResults = [][]string{
{"Jane Deo", "[email protected]", `["556-565-566","777-777-777"]`, "2022-11-01 12:00:00.000001"},
{"Jane Doe", "[email protected]", `[]`, "2022-11-01 12:00:00.000001"},
{"John Doe", "[email protected]", `["555-555-555"]`, "2022-11-01 12:00:00.000001"},
{"John Doe", "[email protected]", `[]`, "2022-11-01 12:00:00.000001"},
}

func TestExampleUsersDisabled(t *testing.T) {
enableUsers = false
useUnusedPort(t)
go func() {
main()
}()

conn, err := dbr.Open("mysql", fmt.Sprintf("no_user:@tcp(%s:%d)/%s", address, port, dbName), nil)
require.NoError(t, err)
require.NoError(t, conn.Ping())

rows, err := conn.Query(fmt.Sprintf("SELECT * FROM %s;", tableName))
require.NoError(t, err)
checkRows(t, expectedResults, rows)
require.NoError(t, conn.Close())
}

func TestExampleRootUserEnabled(t *testing.T) {
enableUsers = true
pretendThatFileExists = false
useUnusedPort(t)
go func() {
main()
}()

conn, err := dbr.Open("mysql", fmt.Sprintf("no_user:@tcp(%s:%d)/%s", address, port, dbName), nil)
require.NoError(t, err)
require.ErrorContains(t, conn.Ping(), "User not found")
conn, err = dbr.Open("mysql", fmt.Sprintf("root:@tcp(%s:%d)/%s", address, port, dbName), nil)
require.NoError(t, err)
require.NoError(t, conn.Ping())

rows, err := conn.Query(fmt.Sprintf("SELECT * FROM %s;", tableName))
require.NoError(t, err)
checkRows(t, expectedResults, rows)
require.NoError(t, conn.Close())
}

func TestExampleLoadedUser(t *testing.T) {
enableUsers = true
pretendThatFileExists = true
useUnusedPort(t)
go func() {
main()
}()

conn, err := dbr.Open("mysql", fmt.Sprintf("no_user:@tcp(%s:%d)/%s", address, port, dbName), nil)
require.NoError(t, err)
require.ErrorContains(t, conn.Ping(), "User not found")
conn, err = dbr.Open("mysql", fmt.Sprintf("root:@tcp(%s:%d)/%s", address, port, dbName), nil)
require.NoError(t, err)
require.ErrorContains(t, conn.Ping(), "User not found")
conn, err = dbr.Open("mysql",
fmt.Sprintf("gms_user:123456@tcp(%s:%d)/%s?allowCleartextPasswords=true", address, port, dbName), nil)
require.NoError(t, err)
require.NoError(t, conn.Ping())

rows, err := conn.Query(fmt.Sprintf("SELECT * FROM %s;", tableName))
require.NoError(t, err)
checkRows(t, expectedResults, rows)
require.NoError(t, conn.Close())
}

func checkRows(t *testing.T, expectedRows [][]string, actualRows *sql.Rows) {
rowIdx := -1
for actualRows.Next() {
rowIdx++

if assert.Less(t, rowIdx, len(expectedRows)) {
compareRow := make([]string, len(expectedRows[rowIdx]))
connRow := make([]*string, len(compareRow))
interfaceRow := make([]any, len(compareRow))
for i := range connRow {
interfaceRow[i] = &connRow[i]
}
assert.NoError(t, actualRows.Scan(interfaceRow...))
for i := range connRow {
if assert.NotNil(t, connRow[i]) {
compareRow[i] = *connRow[i]
}
}
assert.Equal(t, expectedRows[rowIdx], compareRow)
}
}
assert.NoError(t, actualRows.Close())
}

func useUnusedPort(t *testing.T) {
// Tests should grab an open port, otherwise they'll fail if some hardcoded port is already in use
listener, err := net.Listen("tcp", ":0")
require.NoError(t, err)
port = listener.Addr().(*net.TCPAddr).Port
require.NoError(t, listener.Close())
}
Loading

0 comments on commit 621dc30

Please sign in to comment.