Remove support for Google Spanner database. (#105846)
* Remove support for Google Spanner database.
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
// Package spanner should only be used from tests, or from enterprise code (eg. protected by build tags).
|
||||
package spanner
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
spannerdriver "github.com/googleapis/go-sql-spanner"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func UsePlainText(connectorConfig spannerdriver.ConnectorConfig) bool {
|
||||
if strval, ok := connectorConfig.Params["useplaintext"]; ok {
|
||||
if val, err := strconv.ParseBool(strval); err == nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ConnectorConfigToClientOptions is adapted from https://github.com/googleapis/go-sql-spanner/blob/main/driver.go#L341-L477, from version 1.11.1.
|
||||
func ConnectorConfigToClientOptions(connectorConfig spannerdriver.ConnectorConfig) []option.ClientOption {
|
||||
var opts []option.ClientOption
|
||||
if connectorConfig.Host != "" {
|
||||
opts = append(opts, option.WithEndpoint(connectorConfig.Host))
|
||||
}
|
||||
if strval, ok := connectorConfig.Params["credentials"]; ok {
|
||||
opts = append(opts, option.WithCredentialsFile(strval))
|
||||
}
|
||||
if strval, ok := connectorConfig.Params["credentialsjson"]; ok {
|
||||
opts = append(opts, option.WithCredentialsJSON([]byte(strval)))
|
||||
}
|
||||
if UsePlainText(connectorConfig) {
|
||||
opts = append(opts,
|
||||
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
||||
option.WithoutAuthentication())
|
||||
}
|
||||
return opts
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
//go:build enterprise || pro
|
||||
|
||||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
spannerclient "cloud.google.com/go/spanner"
|
||||
_ "github.com/googleapis/go-sql-spanner"
|
||||
spannerdriver "github.com/googleapis/go-sql-spanner"
|
||||
"github.com/grafana/grafana/pkg/util/xorm/core"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
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 {
|
||||
// Spanner does not support auto-increment, but supports unique generated IDs (not sequential!).
|
||||
return "GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE)"
|
||||
}
|
||||
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.SmallInt, core.BigInt:
|
||||
return "INT64"
|
||||
case core.Varchar, core.Text, core.MediumText, core.LongText, core.Char, core.NVarchar, core.NChar, core.NText:
|
||||
l := col.Length
|
||||
if l == 0 {
|
||||
l = col.SQLType.DefaultLength
|
||||
}
|
||||
if l > 0 {
|
||||
return fmt.Sprintf("STRING(%d)", l)
|
||||
}
|
||||
return "STRING(MAX)"
|
||||
case core.Jsonb:
|
||||
return "STRING(MAX)"
|
||||
case core.Bool, core.TinyInt:
|
||||
return "BOOL"
|
||||
case core.Float, core.Double:
|
||||
return "FLOAT64"
|
||||
case core.Bytea, core.Blob, core.MediumBlob, core.LongBlob:
|
||||
l := col.Length
|
||||
if l == 0 {
|
||||
l = col.SQLType.DefaultLength
|
||||
}
|
||||
if l > 0 {
|
||||
return fmt.Sprintf("BYTES(%d)", l)
|
||||
}
|
||||
return "BYTES(MAX)"
|
||||
case core.DateTime, core.TimeStamp:
|
||||
return "TIMESTAMP"
|
||||
default:
|
||||
panic("unknown column type: " + col.SQLType.Name)
|
||||
//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, IS_IDENTITY, IDENTITY_GENERATION, IDENTITY_KIND, COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND TABLE_SCHEMA="" ORDER BY ORDINAL_POSITION`
|
||||
rows, err := s.DB().Query(query, 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
|
||||
var isIdentity, identityGeneration, identityKind, columnDefault sql.NullString
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&name, &sqlType, &isNullable, &isIdentity, &identityGeneration, &identityKind, &columnDefault); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var length int
|
||||
switch {
|
||||
case sqlType == "INT64":
|
||||
sqlType = core.Int
|
||||
case sqlType == "FLOAT32" || sqlType == "FLOAT64":
|
||||
sqlType = core.Float
|
||||
case sqlType == "BOOL":
|
||||
sqlType = core.Bool
|
||||
case sqlType == "BYTES(MAX)":
|
||||
sqlType = core.Blob
|
||||
case sqlType == "STRING(MAX)":
|
||||
sqlType = core.NVarchar
|
||||
case sqlType == "TIMESTAMP":
|
||||
sqlType = core.DateTime
|
||||
case strings.HasPrefix(sqlType, "BYTES("):
|
||||
// 6 == len(`BYTES(`), we also remove ")" from the end.
|
||||
if l, err := strconv.Atoi(sqlType[6 : len(sqlType)-1]); err == nil {
|
||||
length = l
|
||||
}
|
||||
sqlType = core.Blob
|
||||
case strings.HasPrefix(sqlType, "STRING("):
|
||||
// 7 == len(`STRING(`), we also remove ")" from the end.
|
||||
if l, err := strconv.Atoi(sqlType[7 : len(sqlType)-1]); err == nil {
|
||||
length = l
|
||||
}
|
||||
sqlType = core.Varchar
|
||||
default:
|
||||
panic("unknown column type: " + sqlType)
|
||||
}
|
||||
|
||||
autoincrement := isIdentity.Valid && isIdentity.String == "YES" &&
|
||||
identityGeneration.Valid && identityGeneration.String == "BY DEFAULT" &&
|
||||
identityKind.Valid && identityKind.String == "BIT_REVERSED_POSITIVE_SEQUENCE"
|
||||
|
||||
defValue := ""
|
||||
defEmpty := true
|
||||
if columnDefault.Valid {
|
||||
defValue = columnDefault.String
|
||||
defEmpty = false
|
||||
}
|
||||
|
||||
col := &core.Column{
|
||||
Name: name,
|
||||
SQLType: core.SQLType{Name: sqlType},
|
||||
Length: length,
|
||||
Nullable: isNullable == "YES",
|
||||
IsAutoIncrement: autoincrement,
|
||||
Indexes: map[string]int{},
|
||||
Default: defValue,
|
||||
DefaultIsEmpty: defEmpty,
|
||||
}
|
||||
columns[name] = col
|
||||
colNames = append(colNames, name)
|
||||
}
|
||||
|
||||
return colNames, columns, rows.Err()
|
||||
}
|
||||
|
||||
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.Nullable {
|
||||
sql += " NOT NULL"
|
||||
}
|
||||
if col.Default != "" {
|
||||
sql += " DEFAULT (" + col.Default + ")"
|
||||
}
|
||||
if col.IsAutoIncrement {
|
||||
sql += " GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE)"
|
||||
}
|
||||
}
|
||||
|
||||
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, res.Err()
|
||||
}
|
||||
|
||||
func (s *spanner) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
||||
res, err := s.DB().Query(`SELECT ix.INDEX_NAME, ix.INDEX_TYPE, ix.IS_UNIQUE, c.COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.INDEXES ix
|
||||
JOIN INFORMATION_SCHEMA.INDEX_COLUMNS c ON (ix.TABLE_NAME=c.TABLE_NAME AND ix.INDEX_NAME=c.INDEX_NAME)
|
||||
WHERE ix.TABLE_SCHEMA = "" AND ix.TABLE_NAME=?
|
||||
ORDER BY ix.INDEX_NAME, c.ORDINAL_POSITION`, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Close()
|
||||
|
||||
indexes := map[string]*core.Index{}
|
||||
var ixName, ixType, colName string
|
||||
var isUnique bool
|
||||
for res.Next() {
|
||||
err := res.Scan(&ixName, &ixType, &isUnique, &colName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isRegular := false
|
||||
if strings.HasPrefix(ixName, "IDX_"+tableName) || strings.HasPrefix(ixName, "UQE_"+tableName) {
|
||||
ixName = ixName[5+len(tableName):]
|
||||
isRegular = true
|
||||
}
|
||||
|
||||
var index *core.Index
|
||||
var ok bool
|
||||
if index, ok = indexes[ixName]; !ok {
|
||||
t := core.IndexType // ixType == "INDEX" && !isUnique
|
||||
if ixType == "PRIMARY KEY" || isUnique {
|
||||
t = core.UniqueType
|
||||
}
|
||||
|
||||
index = &core.Index{}
|
||||
index.IsRegular = isRegular
|
||||
index.Type = t
|
||||
index.Name = ixName
|
||||
indexes[ixName] = index
|
||||
}
|
||||
index.AddColumn(colName)
|
||||
}
|
||||
return indexes, res.Err()
|
||||
}
|
||||
|
||||
func (s *spanner) CreateSequenceGenerator(db *sql.DB) (SequenceGenerator, error) {
|
||||
dsn := s.DataSourceName()
|
||||
connectorConfig, err := spannerdriver.ExtractConnectorConfig(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if connectorConfig.Params[strings.ToLower("inMemSequenceGenerator")] == "true" {
|
||||
// Switch to using in-memory sequence number generator.
|
||||
// Using database-based sequence generator doesn't work with emulator, as emulator
|
||||
// only supports single transaction. If there is already another transaction started
|
||||
// generating new ID via database-based sequence generator would always fail.
|
||||
return newInMemSequenceGenerator(), nil
|
||||
}
|
||||
|
||||
return newSequenceGenerator(db), nil
|
||||
}
|
||||
|
||||
func (s *spanner) RetryOnError(err error) bool {
|
||||
return err != nil && spannerclient.ErrCode(spannerclient.ToSpannerError(err)) == codes.Aborted
|
||||
}
|
||||
@@ -15,8 +15,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/xorm/core"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/xorm/core"
|
||||
)
|
||||
|
||||
// ErrNoElementsOnSlice represents an error there is no element when insert
|
||||
@@ -422,10 +423,6 @@ func (session *Session) innerInsert(bean any) (int64, error) {
|
||||
buf.WriteString(" RETURNING " + session.engine.Quote(table.AutoIncrement))
|
||||
}
|
||||
|
||||
if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == "spanner" {
|
||||
buf.WriteString(" THEN RETURN " + session.engine.Quote(table.AutoIncrement))
|
||||
}
|
||||
|
||||
sqlStr := buf.String()
|
||||
args = buf.Args()
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ import (
|
||||
const (
|
||||
// Version show the xorm's version
|
||||
Version string = "0.8.0.1015"
|
||||
|
||||
Spanner = "spanner"
|
||||
)
|
||||
|
||||
func regDrvsNDialects() bool {
|
||||
@@ -102,12 +100,6 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
|
||||
switch uri.DbType {
|
||||
case core.SQLITE:
|
||||
engine.DatabaseTZ = time.UTC
|
||||
case Spanner:
|
||||
engine.DatabaseTZ = time.UTC
|
||||
// We need to specify "Z" to indicate that timestamp is in UTC.
|
||||
// Otherwise Spanner uses default America/Los_Angeles timezone.
|
||||
// https://cloud.google.com/spanner/docs/reference/standard-sql/data-types#time_zones
|
||||
engine.timestampFormat = "2006-01-02 15:04:05Z"
|
||||
default:
|
||||
engine.DatabaseTZ = time.Local
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//go:build enterprise || pro
|
||||
|
||||
package xorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"cloud.google.com/go/spanner/spannertest"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBasicOperationsWithSpanner(t *testing.T) {
|
||||
span, err := spannertest.NewServer("localhost:0")
|
||||
require.NoError(t, err)
|
||||
defer span.Close()
|
||||
|
||||
eng, err := NewEngine("spanner", fmt.Sprintf("%s/projects/test/instances/test/databases/test;usePlainText=true", span.Addr))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, eng)
|
||||
require.Equal(t, "spanner", eng.DriverName())
|
||||
|
||||
_, err = eng.Exec("CREATE TABLE test_struct (id int64, comment string(max), json string(max)) primary key (id)")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Currently broken because simple INSERT into spannertest doesn't work: https://github.com/googleapis/go-sql-spanner/issues/392
|
||||
// testBasicOperations(t, eng)
|
||||
}
|
||||
Reference in New Issue
Block a user