fix(embed): update embedDirectives to return result directly fix(executor): clean up import order and formatting fix(syntax_test): change file permission syntax to octal fix(mysql): update file permission syntax to octal fix(pgsql): update file permission syntax to octal fix(sqlex): simplify Walk function parameter handling fix(transform): adjust identifier comparison logic fix(template): clean up function formatting and error handling
143 lines
4.7 KiB
Go
143 lines
4.7 KiB
Go
package mysql_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
driver "github.com/go-sql-driver/mysql"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/readers"
|
|
rmysql "git.warky.dev/wdevs/relspecgo/pkg/readers/mysql"
|
|
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
|
wmysql "git.warky.dev/wdevs/relspecgo/pkg/writers/mysql"
|
|
)
|
|
|
|
func TestLiveUnsupportedObjectsDiagnosed(t *testing.T) {
|
|
conn := os.Getenv("RELSPEC_TEST_MYSQL_CONN")
|
|
if conn == "" {
|
|
t.Skip("RELSPEC_TEST_MYSQL_CONN is not set")
|
|
}
|
|
db, err := sql.Open("mysql", conn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
prefix := fmt.Sprintf("unsupported_%d", time.Now().UnixNano())
|
|
table, view, trigger := prefix+"_table", prefix+"_view", prefix+"_trigger"
|
|
t.Cleanup(func() {
|
|
for _, stmt := range []string{"DROP VIEW IF EXISTS " + view, "DROP TRIGGER IF EXISTS " + trigger, "DROP TABLE IF EXISTS " + table} {
|
|
if _, err := db.Exec(stmt); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
})
|
|
for _, stmt := range []string{
|
|
"CREATE TABLE " + table + " (id INT PRIMARY KEY) PARTITION BY HASH(id) PARTITIONS 2",
|
|
"CREATE VIEW " + view + " AS SELECT id FROM " + table,
|
|
"CREATE TRIGGER " + trigger + " BEFORE INSERT ON " + table + " FOR EACH ROW SET NEW.id = NEW.id + 1",
|
|
} {
|
|
if _, err := db.Exec(stmt); err != nil {
|
|
t.Fatalf("%s: %v", stmt, err)
|
|
}
|
|
}
|
|
model, err := rmysql.NewReader(&readers.ReaderOptions{ConnectionString: conn}).ReadDatabase()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "schema.sql")
|
|
if err := os.WriteFile(path, []byte("original"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true, OutputPath: path}).WriteDatabase(model)
|
|
for _, want := range []string{"partitioned table", view, trigger} {
|
|
if err == nil || !strings.Contains(err.Error(), want) {
|
|
t.Errorf("missing diagnostic %s: %v", want, err)
|
|
}
|
|
}
|
|
bytes, readErr := os.ReadFile(path)
|
|
if readErr != nil || string(bytes) != "original" {
|
|
t.Fatalf("strict failure touched file: %s %v", bytes, readErr)
|
|
}
|
|
// Schema-filtered/library writes retain discovery diagnostics as well.
|
|
err = wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true}).WriteSchema(model.Schemas[0])
|
|
if err == nil || !strings.Contains(err.Error(), trigger) {
|
|
t.Fatalf("filtered schema lost diagnostics: %v", err)
|
|
}
|
|
var warnings []string
|
|
_, err = wmysql.NewWriter(&writers.WriterOptions{Warning: func(s string) { warnings = append(warnings, s) }}).Statements(model)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{view, trigger, table} {
|
|
found := false
|
|
for _, warning := range warnings {
|
|
if strings.Contains(warning, want) {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("no warning for %s: %v", want, warnings)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLiveUnsupportedSQLModeFailsBeforeDDL(t *testing.T) {
|
|
conn := os.Getenv("RELSPEC_TEST_MYSQL_CONN")
|
|
if conn == "" {
|
|
t.Skip("RELSPEC_TEST_MYSQL_CONN is not set")
|
|
}
|
|
for _, mode := range []string{"NO_BACKSLASH_ESCAPES", "ANSI_QUOTES"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
cfg, err := driver.ParseDSN(conn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Params == nil {
|
|
cfg.Params = map[string]string{}
|
|
}
|
|
cfg.Params["sql_mode"] = "'" + mode + "'"
|
|
modeConn := cfg.FormatDSN()
|
|
model, err := rmysql.NewReader(&readers.ReaderOptions{ConnectionString: modeConn}).ReadDatabase()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true}).Statements(model); err == nil || !strings.Contains(err.Error(), mode) {
|
|
t.Fatalf("source mode not diagnosed: %v", err)
|
|
}
|
|
db, err := sql.Open("mysql", conn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
name := fmt.Sprintf("mode_%d", time.Now().UnixNano())
|
|
defer db.Exec("DROP TABLE IF EXISTS " + name)
|
|
target := models.InitDatabase(cfg.DBName)
|
|
s := models.InitSchema(cfg.DBName)
|
|
target.Schemas = append(target.Schemas, s)
|
|
table := models.InitTable(name, s.Name)
|
|
s.Tables = append(s.Tables, table)
|
|
c := models.InitColumn("id", name, s.Name)
|
|
c.Type = "int"
|
|
table.Columns[c.Name] = c
|
|
err = wmysql.NewWriter(&writers.WriterOptions{StrictConversion: true, Metadata: map[string]any{"connection_string": modeConn}}).WriteDatabase(target)
|
|
if err == nil || !strings.Contains(err.Error(), mode) {
|
|
t.Fatalf("target mode not rejected: %v", err)
|
|
}
|
|
var count int
|
|
if err := db.QueryRow("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?", cfg.DBName, name).Scan(&count); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatal("mode rejection occurred after DDL")
|
|
}
|
|
})
|
|
}
|
|
}
|