The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/util/xorm/dialect_spanner.go

285 lines
8.1 KiB

//go:build enterprise || pro
package xorm
import (
"strings"
_ "github.com/googleapis/go-sql-spanner"
"xorm.io/core"
)
func init() {
core.RegisterDriver("spanner", &spannerDriver{})
core.RegisterDialect("spanner", func() core.Dialect { return &spanner{} })
}
// https://cloud.google.com/spanner/docs/reference/standard-sql/lexical#reserved_keywords
var spannerReservedKeywords = map[string]struct{}{
"ALL": {},
"AND": {},
"ANY": {},
"ARRAY": {},
"AS": {},
"ASC": {},
"ASSERT_ROWS_MODIFIED": {},
"AT": {},
"BETWEEN": {},
"BY": {},
"CASE": {},
"CAST": {},
"COLLATE": {},
"CONTAINS": {},
"CREATE": {},
"CROSS": {},
"CUBE": {},
"CURRENT": {},
"DEFAULT": {},
"DEFINE": {},
"DESC": {},
"DISTINCT": {},
"ELSE": {},
"END": {},
"ENUM": {},
"ESCAPE": {},
"EXCEPT": {},
"EXCLUDE": {},
"EXISTS": {},
"EXTRACT": {},
"FALSE": {},
"FETCH": {},
"FOLLOWING": {},
"FOR": {},
"FROM": {},
"FULL": {},
"GROUP": {},
"GROUPING": {},
"GROUPS": {},
"HASH": {},
"HAVING": {},
"IF": {},
"IGNORE": {},
"IN": {},
"INNER": {},
"INTERSECT": {},
"INTERVAL": {},
"INTO": {},
"IS": {},
"JOIN": {},
"LATERAL": {},
"LEFT": {},
"LIKE": {},
"LIMIT": {},
"LOOKUP": {},
"MERGE": {},
"NATURAL": {},
"NEW": {},
"NO": {},
"NOT": {},
"NULL": {},
"NULLS": {},
"OF": {},
"ON": {},
"OR": {},
"ORDER": {},
"OUTER": {},
"OVER": {},
"PARTITION": {},
"PRECEDING": {},
"PROTO": {},
"RANGE": {},
"RECURSIVE": {},
"RESPECT": {},
"RIGHT": {},
"ROLLUP": {},
"ROWS": {},
"SELECT": {},
"SET": {},
"SOME": {},
"STRUCT": {},
"TABLESAMPLE": {},
"THEN": {},
"TO": {},
"TREAT": {},
"TRUE": {},
"UNBOUNDED": {},
"UNION": {},
"UNNEST": {},
"USING": {},
"WHEN": {},
"WHERE": {},
"WINDOW": {},
"WITH": {},
"WITHIN": {},
}
type spannerDriver struct{}
func (d *spannerDriver) Parse(_driverName, datasourceName string) (*core.Uri, error) {
return &core.Uri{DbType: "spanner", DbName: datasourceName}, nil
}
type spanner struct {
core.Base
}
func (s *spanner) Init(db *core.DB, uri *core.Uri, driverName string, datasourceName string) error {
return s.Base.Init(db, s, uri, driverName, datasourceName)
}
func (s *spanner) Filters() []core.Filter { return []core.Filter{&core.IdFilter{}} }
func (s *spanner) IsReserved(name string) bool {
_, exists := spannerReservedKeywords[name]
return exists
}
func (s *spanner) AndStr() string { return "AND" }
func (s *spanner) OrStr() string { return "OR" }
func (s *spanner) EqStr() string { return "=" }
func (s *spanner) RollBackStr() string { return "ROLL BACK" }
func (s *spanner) AutoIncrStr() string { return "" } // Spanner does not support auto-increment
func (s *spanner) SupportInsertMany() bool { return false } // Needs manual transaction batching
func (s *spanner) SupportEngine() bool { return false } // No support for engine selection
func (s *spanner) SupportCharset() bool { return false } // ...or charsets
func (s *spanner) SupportDropIfExists() bool { return false } // Drop should be handled differently
func (s *spanner) IndexOnTable() bool { return false }
func (s *spanner) ShowCreateNull() bool { return false }
func (s *spanner) Quote(name string) string { return "`" + name + "`" }
func (s *spanner) SqlType(col *core.Column) string {
switch col.SQLType.Name {
case core.Int, core.BigInt:
return "INT64"
case core.Varchar, core.Text:
return "STRING(MAX)"
case core.Bool:
return "BOOL"
case core.Float, core.Double:
return "FLOAT64"
case core.Bytea:
return "BYTES(MAX)"
case core.DateTime, core.TimeStamp:
return "TIMESTAMP"
default:
return "STRING(MAX)" // XXX: more types to add
}
}
func (s *spanner) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
query := `SELECT COLUMN_NAME, SPANNER_TYPE, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @tableName`
rows, err := s.DB().Query(query, map[string]any{"tableName": tableName})
if err != nil {
return nil, nil, err
}
defer rows.Close()
columns := make(map[string]*core.Column)
var colNames []string
var name, sqlType, isNullable string
for rows.Next() {
if err := rows.Scan(&name, &sqlType, &isNullable); err != nil {
return nil, nil, err
}
col := &core.Column{
Name: name,
SQLType: core.SQLType{Name: sqlType},
Nullable: isNullable == "YES",
}
columns[name] = col
colNames = append(colNames, name)
}
return colNames, columns, nil
}
func (s *spanner) CreateTableSql(table *core.Table, tableName, _, charset string) string {
sql := "CREATE TABLE " + s.Quote(tableName) + " ("
for i, col := range table.Columns() {
if i > 0 {
sql += ", "
}
sql += s.Quote(col.Name) + " " + s.SqlType(col)
if col.IsPrimaryKey {
sql += " PRIMARY KEY"
}
}
sql += ") PRIMARY KEY (" + strings.Join(table.PrimaryKeys, ",") + ")"
return sql
}
func (s *spanner) CreateIndexSql(tableName string, index *core.Index) string {
sql := "CREATE "
if index.Type == core.UniqueType {
sql += "UNIQUE NULL_FILTERED "
}
sql += "INDEX " + s.Quote(index.XName(tableName)) + " ON " + s.Quote(tableName) + " (" + strings.Join(index.Cols, ", ") + ")"
return sql
}
func (s *spanner) IndexCheckSql(tableName, indexName string) (string, []any) {
return `SELECT index_name FROM information_schema.indexes
WHERE table_name = ? AND table_schema = "" AND index_name = ?`,
[]any{tableName, indexName}
}
func (s *spanner) TableCheckSql(tableName string) (string, []any) {
return `SELECT table_name FROM information_schema.tables
WHERE table_name = ? AND table_schema = ""`,
[]any{tableName}
}
func (s *spanner) GetTables() ([]*core.Table, error) {
res, err := s.DB().Query(`
SELECT table_name FROM information_schema.tables
WHERE table_schema = ""
`)
if err != nil {
return nil, err
}
defer res.Close()
tables := []*core.Table{}
for res.Next() {
var name string
if err := res.Scan(&name); err != nil {
return nil, err
}
t := core.NewEmptyTable()
t.Name = name
tables = append(tables, t)
}
return tables, nil
}
func (s *spanner) GetIndexes(tableName string) (map[string]*core.Index, error) {
res, err := s.DB().Query(`
SELECT index_name, index_type, is_unique FROM information_schema.tables
WHERE table_name = ? AND table_schema = ""
`, []any{tableName})
if err != nil {
return nil, err
}
defer res.Close()
indices := map[string]*core.Index{}
for res.Next() {
index := struct {
Name string `xorm:"index_name"`
Type string `xorm:"index_type"`
IsUnqiue bool `xorm:"is_unique"`
}{}
err := res.Scan(&index)
if err != nil {
return nil, err
}
switch {
case index.Type == "INDEX":
indices[index.Name] = core.NewIndex(index.Name, core.IndexType)
case index.Type == "PRIMARY_KEY", index.IsUnqiue:
indices[index.Name] = core.NewIndex(index.Name, core.UniqueType)
}
}
return indices, nil
}