1 Star 0 Fork 0

DaMeng/Atlas

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
postgrescheck.go 5.68 KB
一键复制 编辑 原始数据 按行查看 历史
DaMeng 提交于 2024-10-24 15:32 +08:00 . :art:修改mod名称
// Copyright 2021-present The Atlas Authors. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package postgrescheck
import (
"context"
"errors"
"fmt"
"gitee.com/damengde/atlas/schemahcl"
"gitee.com/damengde/atlas/sql/internal/sqlx"
"gitee.com/damengde/atlas/sql/migrate"
"gitee.com/damengde/atlas/sql/postgres"
"gitee.com/damengde/atlas/sql/schema"
"gitee.com/damengde/atlas/sql/sqlcheck"
"gitee.com/damengde/atlas/sql/sqlcheck/condrop"
"gitee.com/damengde/atlas/sql/sqlcheck/datadepend"
"gitee.com/damengde/atlas/sql/sqlcheck/destructive"
"gitee.com/damengde/atlas/sql/sqlcheck/incompatible"
"gitee.com/damengde/atlas/sql/sqlcheck/naming"
)
func addNotNull(p *datadepend.ColumnPass) (diags []sqlcheck.Diagnostic, err error) {
tt, err := postgres.FormatType(p.Column.Type.Type)
if err != nil {
return nil, err
}
return []sqlcheck.Diagnostic{
{
Pos: p.Change.Stmt.Pos,
Text: fmt.Sprintf(
"Adding a non-nullable %q column %q will fail in case table %q is not empty",
tt, p.Column.Name, p.Table.Name,
),
},
}, nil
}
func analyzers(r *schemahcl.Resource) ([]sqlcheck.Analyzer, error) {
ds, err := destructive.New(r)
if err != nil {
return nil, err
}
cd, err := condrop.New(r)
if err != nil {
return nil, err
}
dd, err := datadepend.New(r, datadepend.Handler{
AddNotNull: addNotNull,
})
bc, err := incompatible.New(r)
if err != nil {
return nil, err
}
nm, err := naming.New(r)
if err != nil {
return nil, err
}
ci, err := NewConcurrentIndex(r)
if err != nil {
return nil, err
}
return []sqlcheck.Analyzer{ds, dd, cd, bc, nm, ci}, nil
}
type (
// ConcurrentIndex checks for concurrent index creation,
// dropping, and its transactional safety.
ConcurrentIndex struct {
ConcurrentOptions
}
// ConcurrentOptions for concurrent index creation.
ConcurrentOptions struct {
sqlcheck.Options
CheckCreate *bool `spec:"check_create"`
CheckDrop *bool `spec:"check_drop"`
CheckTxMode *bool `spec:"check_txmode"`
}
)
// NewConcurrentIndex creates a new concurrent-index Analyzer with the given options.
func NewConcurrentIndex(r *schemahcl.Resource) (*ConcurrentIndex, error) {
az := &ConcurrentIndex{}
az.CheckCreate = sqlx.P(true)
az.CheckDrop = sqlx.P(true)
az.CheckTxMode = sqlx.P(true)
if r, ok := r.Resource(az.Name()); ok {
if err := r.As(&az.Options); err != nil {
return nil, fmt.Errorf("sql/sqlcheck: parsing concurrent_index error option: %w", err)
}
if err := r.As(&az.ConcurrentOptions); err != nil {
return nil, fmt.Errorf("sql/sqlcheck: parsing concurrent_index check options: %w", err)
}
}
return az, nil
}
// Name of the analyzer. Implements the sqlcheck.NamedAnalyzer interface.
func (*ConcurrentIndex) Name() string {
return "concurrent_index"
}
var (
// codeCreateNoCon is a PostgreSQL specific code for reporting
// indexes creation without the CONCURRENTLY option.
codeCreateNoCon = sqlcheck.Code("PG101")
// codeDropNoCon is a PostgreSQL specific code for reporting
// indexes deletion without the CONCURRENTLY option.
codeDropNoCon = sqlcheck.Code("PG102")
// codeNoTxNone is a PostgreSQL specific code for reporting indexes
// creation or deletion concurrently without the txmode directive set
// to none.
codeNoTxNone = sqlcheck.Code("PG103")
)
// Analyze implements sqlcheck.Analyzer.
func (a *ConcurrentIndex) Analyze(_ context.Context, p *sqlcheck.Pass) error {
var (
notx bool
notxC int
diags []sqlcheck.Diagnostic
)
// The txmode directive, currently defined in cmd/atlas,
// might be moved to sql/migrate in the future.
if l, ok := p.File.File.(*migrate.LocalFile); ok {
mode := l.Directive("txmode")
notx = len(mode) == 1 && mode[0] == "none"
}
for _, sc := range p.File.Changes {
for _, c := range sc.Changes {
m, ok := c.(*schema.ModifyTable)
// Skip modifications for tables that have been created in this file.
if !ok || p.File.TableSpan(m.T)&sqlcheck.SpanAdded == 1 {
continue
}
for i := range m.Changes {
switch mc := m.Changes[i].(type) {
case *schema.AddIndex:
switch hasC := sqlx.Has(mc.Extra, &postgres.Concurrently{}); {
case !sqlx.V(a.CheckCreate):
case hasC && !notx && sqlx.V(a.CheckTxMode):
notxC++
case !hasC:
diags = append(diags, sqlcheck.Diagnostic{
Pos: sc.Stmt.Pos,
Code: codeCreateNoCon,
Text: fmt.Sprintf(
"Creating index %q non-concurrently causes write locks on the %q table",
mc.I.Name, m.T.Name,
),
})
}
case *schema.DropIndex:
switch hasC := sqlx.Has(mc.Extra, &postgres.Concurrently{}); {
case !sqlx.V(a.CheckDrop):
case hasC && !notx && sqlx.V(a.CheckTxMode):
notxC++
case !hasC:
diags = append(diags, sqlcheck.Diagnostic{
Pos: sc.Stmt.Pos,
Code: codeDropNoCon,
Text: fmt.Sprintf(
"Dropping index %q non-concurrently causes write locks on the %q table",
mc.I.Name, m.T.Name,
),
})
}
}
}
}
}
if notxC > 0 {
diags = append([]sqlcheck.Diagnostic{{
Pos: 0,
Code: codeNoTxNone,
Text: "Indexes cannot be created or deleted concurrently within a transaction. Add the `atlas:txmode none` " +
"directive to the header to prevent this file from running in a transaction",
}}, diags...)
}
if len(diags) > 0 {
const reportText = "concurrent index violations detected"
p.Reporter.WriteReport(sqlcheck.Report{Text: reportText, Diagnostics: diags})
// Report an error only if it is configured this way and the
// diagnostics include non-concurrent creation or deletion.
if sqlx.V(a.Error) && (notxC == 0 || len(diags) > 1) {
return errors.New(reportText)
}
}
return nil
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/damengde/atlas.git
git@gitee.com:damengde/atlas.git
damengde
atlas
Atlas
v0.0.3

搜索帮助