适配mysql
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package dbcompat
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CalendarSeed is a MySQL-version-neutral replacement for LAG/LEAD over the
|
||||
// trade calendar. Keeping the adjacent dates in Go avoids depending on MySQL 8.
|
||||
type CalendarSeed struct {
|
||||
TradeDate string
|
||||
PreviousTradeDate string
|
||||
PreviousTwoTradeDate string
|
||||
NextTradeDate string
|
||||
}
|
||||
|
||||
type calendarDateRow struct {
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
}
|
||||
|
||||
type columnCollationRow struct {
|
||||
CollationName string `gorm:"column:collation_name"`
|
||||
}
|
||||
|
||||
// LoadSSEOpenCalendarSeeds loads all SSE open dates and builds the date window
|
||||
// needed by range queries. The returned seeds are limited to [startDate, endDate].
|
||||
func LoadSSEOpenCalendarSeeds(db *gorm.DB, startDate, endDate string) ([]CalendarSeed, error) {
|
||||
var rows []calendarDateRow
|
||||
if err := db.Table("trade_calendar").
|
||||
Select("DATE_FORMAT(cal_date, '%Y-%m-%d') AS trade_date").
|
||||
Where("exchange = ? AND is_open = ?", "SSE", 1).
|
||||
Order("cal_date ASC").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seeds := make([]CalendarSeed, 0)
|
||||
for i, row := range rows {
|
||||
if row.TradeDate < startDate || row.TradeDate > endDate {
|
||||
continue
|
||||
}
|
||||
|
||||
seed := CalendarSeed{TradeDate: row.TradeDate}
|
||||
if i > 0 {
|
||||
seed.PreviousTradeDate = rows[i-1].TradeDate
|
||||
}
|
||||
if i > 1 {
|
||||
seed.PreviousTwoTradeDate = rows[i-2].TradeDate
|
||||
}
|
||||
if i+1 < len(rows) {
|
||||
seed.NextTradeDate = rows[i+1].TradeDate
|
||||
}
|
||||
seeds = append(seeds, seed)
|
||||
}
|
||||
|
||||
return seeds, nil
|
||||
}
|
||||
|
||||
// CalendarSeedTable converts calendar seeds into a derived table that works on
|
||||
// MySQL 5.7, MySQL 8, and MariaDB without CTE/window-function support.
|
||||
func CalendarSeedTable(seeds []CalendarSeed) (string, []any) {
|
||||
if len(seeds) == 0 {
|
||||
return `SELECT
|
||||
CAST(NULL AS DATE) AS trade_date,
|
||||
CAST(NULL AS DATE) AS previous_trade_date,
|
||||
CAST(NULL AS DATE) AS previous_two_trade_date,
|
||||
CAST(NULL AS DATE) AS next_trade_date
|
||||
WHERE 1 = 0`, nil
|
||||
}
|
||||
|
||||
parts := make([]string, 0, len(seeds))
|
||||
args := make([]any, 0, len(seeds)*4)
|
||||
for _, seed := range seeds {
|
||||
parts = append(parts, `SELECT
|
||||
CAST(? AS DATE) AS trade_date,
|
||||
CAST(? AS DATE) AS previous_trade_date,
|
||||
CAST(? AS DATE) AS previous_two_trade_date,
|
||||
CAST(? AS DATE) AS next_trade_date`)
|
||||
args = append(args,
|
||||
nullableDate(seed.TradeDate),
|
||||
nullableDate(seed.PreviousTradeDate),
|
||||
nullableDate(seed.PreviousTwoTradeDate),
|
||||
nullableDate(seed.NextTradeDate),
|
||||
)
|
||||
}
|
||||
|
||||
return strings.Join(parts, "\nUNION ALL\n"), args
|
||||
}
|
||||
|
||||
// TsCodeEqual keeps joins free of hard-coded COLLATE clauses. This avoids
|
||||
// binding SQL to MySQL-8-only collations while preserving index-friendly joins.
|
||||
func TsCodeEqual(left, right string) string {
|
||||
return left + " = " + right
|
||||
}
|
||||
|
||||
// ColumnCollation returns the current database collation for one column.
|
||||
func ColumnCollation(db *gorm.DB, tableName, columnName string) (string, error) {
|
||||
var row columnCollationRow
|
||||
err := db.Raw(`
|
||||
SELECT COLLATION_NAME AS collation_name
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME = ?
|
||||
LIMIT 1`, tableName, columnName).Scan(&row).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return row.CollationName, nil
|
||||
}
|
||||
|
||||
// TsCodeEqualUnicode compares a utf8mb4_unicode_ci column with a column that
|
||||
// may be utf8mb4_0900_ai_ci. utf8mb4_unicode_ci exists on both MySQL 5.7 and 8.
|
||||
func TsCodeEqualUnicode(left, right string) string {
|
||||
return left + " = " + right + " COLLATE utf8mb4_unicode_ci"
|
||||
}
|
||||
|
||||
// TsCodeEqualRightCollation compares two stock-code columns using a collation
|
||||
// known to exist on the connected database. This avoids MySQL-version-specific
|
||||
// literals while preserving the fast side of joins such as stock_daily.ts_code.
|
||||
func TsCodeEqualRightCollation(left, right, collation string) string {
|
||||
if !safeCollationName(collation) {
|
||||
return TsCodeEqual(left, right)
|
||||
}
|
||||
return left + " = " + right + " COLLATE " + collation
|
||||
}
|
||||
|
||||
func safeCollationName(collation string) bool {
|
||||
if collation == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range collation {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func nullableDate(date string) any {
|
||||
if strings.TrimSpace(date) == "" {
|
||||
return nil
|
||||
}
|
||||
return date
|
||||
}
|
||||
Reference in New Issue
Block a user