diff --git a/docker/Dockerfile b/docker/Dockerfile index 93ae4fb..f72ba56 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16.3 as builder +FROM golang:1.18.3 as builder WORKDIR /app ADD . /app RUN make build-local diff --git a/docker/conf/config_shd.yaml b/docker/conf/config_shd.yaml index 0241916..758cd7f 100644 --- a/docker/conf/config_shd.yaml +++ b/docker/conf/config_shd.yaml @@ -13,6 +13,7 @@ executors: - name: redirect mode: shd config: + transaction_timeout: 60000 db_groups: - name: drug_0 load_balance_algorithm: RandomWeight @@ -43,6 +44,8 @@ data_source_cluster: dsn: root:123456@tcp(dbpack-mysql1:3306)/drug?timeout=10s&readTimeout=10s&writeTimeout=10s&parseTime=true&loc=Local&charset=utf8mb4,utf8 ping_interval: 20s ping_times_for_change_status: 3 + filters: + - mysqlDTFilter - name: drug_1 capacity: 10 @@ -51,5 +54,22 @@ data_source_cluster: dsn: root:123456@tcp(dbpack-mysql2:3306)/drug?timeout=60s&readTimeout=60s&writeTimeout=60s&parseTime=true&loc=Local&charset=utf8mb4,utf8 ping_interval: 20s ping_times_for_change_status: 3 + filters: + - mysqlDTFilter -http_listen_port: 9999 \ No newline at end of file +filters: + - name: mysqlDTFilter + kind: MysqlDistributedTransaction + conf: + appid: svc + lock_retry_interval: 50ms + lock_retry_times: 30 + +distributed_transaction: + appid: svc + retry_dead_threshold: 130000 + rollback_retry_timeout_unlock_enable: true + etcd_config: + endpoints: + - etcd:2379 +http_listen_port: 9999 diff --git a/docker/docker-compose-shd.yaml b/docker/docker-compose-shd.yaml index a4d39e1..b6f6b56 100644 --- a/docker/docker-compose-shd.yaml +++ b/docker/docker-compose-shd.yaml @@ -1,5 +1,22 @@ version: "2.3" services: + etcd: + image: docker.io/bitnami/etcd:3 + container_name: etcd + environment: + - ALLOW_NONE_AUTHENTICATION=yes + - ETCD_NAME=etcd + - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380 + - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 + - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 + - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 + - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster + - ETCD_INITIAL_CLUSTER=etcd=http://etcd:2380 + - ETCD_INITIAL_CLUSTER_STATE=new + networks: + - local + ports: + - "2379:2379" mysql1: image: mysql:8.0 container_name: dbpack-mysql1 @@ -37,6 +54,7 @@ services: - ./conf/config_shd.yaml:/config.yaml - ./scripts/wait-for-mysql.sh:/wait-for-mysql.sh depends_on: + - etcd - mysql1 - mysql2 command: ["./wait-for-mysql.sh","--","/dbpack", "start", "-c", "config.yaml"] diff --git a/docker/scripts/drug_0.sql b/docker/scripts/drug_0.sql index 561c71e..918dd88 100644 --- a/docker/scripts/drug_0.sql +++ b/docker/scripts/drug_0.sql @@ -3581,3 +3581,16 @@ COMMIT; SET FOREIGN_KEY_CHECKS = 1; + +CREATE TABLE `undo_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `branch_id` bigint NOT NULL, + `xid` varchar(100) NOT NULL, + `context` varchar(128) NOT NULL, + `rollback_info` longblob NOT NULL, + `log_status` int NOT NULL, + `log_created` datetime NOT NULL, + `log_modified` datetime NOT NULL, + `ext` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`) +); diff --git a/docker/scripts/drug_1.sql b/docker/scripts/drug_1.sql index b8b5d0a..6dc09a4 100644 --- a/docker/scripts/drug_1.sql +++ b/docker/scripts/drug_1.sql @@ -3579,3 +3579,16 @@ COMMIT; SET FOREIGN_KEY_CHECKS = 1; + +CREATE TABLE `undo_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `branch_id` bigint NOT NULL, + `xid` varchar(100) NOT NULL, + `context` varchar(128) NOT NULL, + `rollback_info` longblob NOT NULL, + `log_status` int NOT NULL, + `log_created` datetime NOT NULL, + `log_modified` datetime NOT NULL, + `ext` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`) +); diff --git a/go.mod b/go.mod index b95b8b8..2973456 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cectc/dbpack -go 1.16 +go 1.18 require ( github.com/agiledragon/gomonkey/v2 v2.7.0 @@ -44,19 +44,68 @@ require ( require ( github.com/BurntSushi/toml v1.1.0 // indirect + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/andybalholm/brotli v1.0.4 // indirect github.com/benbjohnson/clock v1.3.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect + github.com/coreos/go-systemd/v22 v22.1.0 // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/cznic/golex v0.0.0-20181122101858-9c343928389c // indirect + github.com/danjacques/gofslock v0.0.0-20191023191349-0a45f885bc37 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/gobuffalo/envy v1.7.0 // indirect + github.com/gobuffalo/packd v0.3.0 // indirect + github.com/gobuffalo/packr v1.30.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/joho/godotenv v1.3.0 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/klauspost/compress v1.15.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pingcap/failpoint v0.0.0-20210316064728-7acb0f0a3dfd // indirect + github.com/pingcap/kvproto v0.0.0-20210806074406-317f69fb54b4 // indirect + github.com/pingcap/parser v0.0.0-20210831085004-b5390aa83f65 // indirect + github.com/pingcap/tipb v0.0.0-20210708040514-0f154bb0dc0f // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect + github.com/shirou/gopsutil v3.21.2+incompatible // indirect github.com/soheilhy/cmux v0.1.5-0.20210205191134-5ec6847320e5 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tikv/client-go/v2 v2.0.0-alpha.0.20210831090540-391fcd842dc8 // indirect + github.com/tikv/pd v1.1.0-beta.0.20210818112400-0c5667766690 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect + github.com/uber/jaeger-client-go v2.22.1+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + go.etcd.io/etcd v0.5.0-alpha.5.0.20200824191128-ae9734ed278b // indirect + go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 // indirect go.opentelemetry.io/otel v1.7.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.7.0 go.opentelemetry.io/otel/sdk v1.7.0 go.opentelemetry.io/otel/trace v1.7.0 + go.uber.org/multierr v1.7.0 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/tools v0.1.10 // indirect + google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1 // indirect google.golang.org/protobuf v1.27.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 k8s.io/apimachinery v0.23.5 + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect ) diff --git a/go.sum b/go.sum index 42e863f..c68491b 100644 --- a/go.sum +++ b/go.sum @@ -201,8 +201,6 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9 h1:BXEAWJOT2C6ex9iOzVnrYWMFjTRccNs7p8fpLCLLcm0= -github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik= github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41 h1:yRPjAkkuR/E/tsVG7QmhzEeEtD3P2yllxsT1/ftURb0= github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= @@ -897,7 +895,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= @@ -979,7 +976,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1025,7 +1021,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1078,7 +1073,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1185,7 +1179,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1472,7 +1465,6 @@ k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hr k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8= k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= diff --git a/pkg/config/db.go b/pkg/config/db.go index 6481432..b501c7d 100644 --- a/pkg/config/db.go +++ b/pkg/config/db.go @@ -78,8 +78,9 @@ type ( } ShardingConfig struct { - DBGroups []*DataSourceRefGroup `yaml:"db_groups" json:"db_groups"` - LogicTables []*LogicTable `yaml:"logic_tables" json:"logic_tables"` + DBGroups []*DataSourceRefGroup `yaml:"db_groups" json:"db_groups"` + LogicTables []*LogicTable `yaml:"logic_tables" json:"logic_tables"` + TransactionTimeout int32 `yaml:"transaction_timeout" json:"transaction_timeout"` } ) diff --git a/pkg/constant/config.go b/pkg/constant/config.go index 6486c57..9a33e40 100644 --- a/pkg/constant/config.go +++ b/pkg/constant/config.go @@ -17,5 +17,6 @@ package constant const ( - ConfigPathKey = "config" + ConfigPathKey = "config" + TransactionTimeout = "transaction-timeout" ) diff --git a/pkg/executor/sharding.go b/pkg/executor/sharding.go index 59fd974..d89d2c8 100644 --- a/pkg/executor/sharding.go +++ b/pkg/executor/sharding.go @@ -25,6 +25,7 @@ import ( "github.com/cectc/dbpack/pkg/cond" "github.com/cectc/dbpack/pkg/config" + "github.com/cectc/dbpack/pkg/constant" "github.com/cectc/dbpack/pkg/filter" "github.com/cectc/dbpack/pkg/lb" "github.com/cectc/dbpack/pkg/log" @@ -40,6 +41,7 @@ type ShardingExecutor struct { PreFilters []proto.DBPreFilter PostFilters []proto.DBPostFilter + config *config.ShardingConfig all []*DataSourceBrief optimizer proto.Optimizer localTransactionMap map[uint32]proto.Tx @@ -76,6 +78,7 @@ func NewShardingExecutor(conf *config.Executor) (proto.Executor, error) { executor := &ShardingExecutor{ PreFilters: make([]proto.DBPreFilter, 0), PostFilters: make([]proto.DBPostFilter, 0), + config: shardingConfig, all: all, optimizer: optimize.NewOptimizer(executors, algorithms, topologies), localTransactionMap: make(map[uint32]proto.Tx, 0), @@ -199,7 +202,7 @@ func (executor *ShardingExecutor) ExecutorComQuery(ctx context.Context, sql stri plan proto.Plan err error ) - newCtx, span := tracing.GetTraceSpan(ctx, "sharding_execute_com_query") + newCtx, span := tracing.GetTraceSpan(ctx, "sharding_com_query") defer span.End() log.Debugf("query: %s", sql) @@ -228,12 +231,11 @@ func (executor *ShardingExecutor) ExecutorComQuery(ctx context.Context, sql stri } } } - plan, err = executor.optimizer.Optimize(newCtx, queryStmt) if err != nil { return nil, 0, err } - + proto.WithVariable(newCtx, constant.TransactionTimeout, executor.config.TransactionTimeout) return plan.Execute(newCtx) } @@ -243,17 +245,19 @@ func (executor *ShardingExecutor) ExecutorComStmtExecute(ctx context.Context, st plan proto.Plan err error ) + newCtx, span := tracing.GetTraceSpan(ctx, "sharding_com_stmt_execute") + defer span.End() for i := 0; i < len(stmt.BindVars); i++ { parameterID := fmt.Sprintf("v%d", i+1) args = append(args, stmt.BindVars[parameterID]) } - plan, err = executor.optimizer.Optimize(ctx, stmt.StmtNode, args...) + plan, err = executor.optimizer.Optimize(newCtx, stmt.StmtNode, args...) if err != nil { return nil, 0, err } - - return plan.Execute(ctx) + proto.WithVariable(newCtx, constant.TransactionTimeout, executor.config.TransactionTimeout) + return plan.Execute(newCtx) } func (executor *ShardingExecutor) ConnectionClose(ctx context.Context) { diff --git a/pkg/filter/dt/filter_mysql.go b/pkg/filter/dt/filter_mysql.go index 82a1ffe..75a8576 100644 --- a/pkg/filter/dt/filter_mysql.go +++ b/pkg/filter/dt/filter_mysql.go @@ -19,7 +19,6 @@ package dt import ( "context" "encoding/json" - "strings" "time" "github.com/cectc/dbpack/pkg/tracing" @@ -35,16 +34,13 @@ import ( "github.com/cectc/dbpack/pkg/log" "github.com/cectc/dbpack/pkg/proto" "github.com/cectc/dbpack/third_party/parser/ast" - "github.com/cectc/dbpack/third_party/parser/model" ) const ( - mysqlFilter = "MysqlDistributedTransaction" - beforeImage = "BeforeImage" - XID = "x-dbpack-xid" - BranchID = "x-dbpack-branch-id" - hintXID = "XID" - hintGlobalLock = "GlobalLock" + mysqlFilter = "MysqlDistributedTransaction" + beforeImage = "BeforeImage" + XID = "x-dbpack-xid" + BranchID = "x-dbpack-branch-id" ) type _mysqlFactory struct { @@ -211,26 +207,6 @@ func (f *_mysqlFilter) registerBranchTransaction(ctx context.Context, xid, resou return branchID, err } -func hasXIDHint(hints []*ast.TableOptimizerHint) (bool, string) { - for _, hint := range hints { - if strings.EqualFold(hint.HintName.String(), hintXID) { - hintData := hint.HintData.(model.CIStr) - xid := hintData.String() - return true, xid - } - } - return false, "" -} - -func hasGlobalLockHint(hints []*ast.TableOptimizerHint) bool { - for _, hint := range hints { - if strings.EqualFold(hint.HintName.String(), hintGlobalLock) { - return true - } - } - return false -} - func init() { filter.RegistryFilterFactory(mysqlFilter, &_mysqlFactory{}) } diff --git a/pkg/filter/dt/filter_mysql_prepare.go b/pkg/filter/dt/filter_mysql_prepare.go index 898eaa8..e3d5442 100644 --- a/pkg/filter/dt/filter_mysql_prepare.go +++ b/pkg/filter/dt/filter_mysql_prepare.go @@ -27,12 +27,13 @@ import ( "github.com/cectc/dbpack/pkg/dt/schema" "github.com/cectc/dbpack/pkg/filter/dt/exec" "github.com/cectc/dbpack/pkg/log" + "github.com/cectc/dbpack/pkg/misc" "github.com/cectc/dbpack/pkg/proto" "github.com/cectc/dbpack/third_party/parser/ast" ) func (f *_mysqlFilter) processBeforePrepareDelete(ctx context.Context, conn *driver.BackendConnection, stmt *proto.Stmt, deleteStmt *ast.DeleteStmt) error { - if hasGlobalLockHint(deleteStmt.TableHints) { + if misc.HasGlobalLockHint(deleteStmt.TableHints) { executor := exec.NewPrepareGlobalLockExecutor(conn, false, deleteStmt, nil, stmt.BindVars) result, err := executor.Executable(ctx, f.lockRetryInterval, f.lockRetryTimes) if err != nil { @@ -43,7 +44,7 @@ func (f *_mysqlFilter) processBeforePrepareDelete(ctx context.Context, conn *dri } return nil } - if has, _ := hasXIDHint(deleteStmt.TableHints); !has { + if has, _ := misc.HasXIDHint(deleteStmt.TableHints); !has { return nil } executor := exec.NewPrepareDeleteExecutor(conn, deleteStmt, stmt.BindVars) @@ -58,7 +59,7 @@ func (f *_mysqlFilter) processBeforePrepareDelete(ctx context.Context, conn *dri } func (f *_mysqlFilter) processBeforePrepareUpdate(ctx context.Context, conn *driver.BackendConnection, stmt *proto.Stmt, updateStmt *ast.UpdateStmt) error { - if hasGlobalLockHint(updateStmt.TableHints) { + if misc.HasGlobalLockHint(updateStmt.TableHints) { executor := exec.NewPrepareGlobalLockExecutor(conn, true, nil, updateStmt, stmt.BindVars) result, err := executor.Executable(ctx, f.lockRetryInterval, f.lockRetryTimes) if err != nil { @@ -69,7 +70,7 @@ func (f *_mysqlFilter) processBeforePrepareUpdate(ctx context.Context, conn *dri } return nil } - if has, _ := hasXIDHint(updateStmt.TableHints); !has { + if has, _ := misc.HasXIDHint(updateStmt.TableHints); !has { return nil } executor := exec.NewPrepareUpdateExecutor(conn, updateStmt, stmt.BindVars, nil) @@ -85,7 +86,7 @@ func (f *_mysqlFilter) processBeforePrepareUpdate(ctx context.Context, conn *dri func (f *_mysqlFilter) processAfterPrepareDelete(ctx context.Context, conn *driver.BackendConnection, stmt *proto.Stmt, deleteStmt *ast.DeleteStmt) error { - has, xid := hasXIDHint(deleteStmt.TableHints) + has, xid := misc.HasXIDHint(deleteStmt.TableHints) if !has { return nil } @@ -115,7 +116,7 @@ func (f *_mysqlFilter) processAfterPrepareDelete(ctx context.Context, conn *driv func (f *_mysqlFilter) processAfterPrepareInsert(ctx context.Context, conn *driver.BackendConnection, result proto.Result, stmt *proto.Stmt, insertStmt *ast.InsertStmt) error { - has, xid := hasXIDHint(insertStmt.TableHints) + has, xid := misc.HasXIDHint(insertStmt.TableHints) if !has { return nil } @@ -144,7 +145,7 @@ func (f *_mysqlFilter) processAfterPrepareInsert(ctx context.Context, conn *driv func (f *_mysqlFilter) processAfterPrepareUpdate(ctx context.Context, conn *driver.BackendConnection, stmt *proto.Stmt, updateStmt *ast.UpdateStmt) error { - has, xid := hasXIDHint(updateStmt.TableHints) + has, xid := misc.HasXIDHint(updateStmt.TableHints) if !has { return nil } @@ -177,7 +178,7 @@ func (f *_mysqlFilter) processAfterPrepareUpdate(ctx context.Context, conn *driv func (f *_mysqlFilter) processSelectForPrepareUpdate(ctx context.Context, conn *driver.BackendConnection, result proto.Result, stmt *proto.Stmt, selectStmt *ast.SelectStmt) error { - has, _ := hasXIDHint(selectStmt.TableHints) + has, _ := misc.HasXIDHint(selectStmt.TableHints) if !has { return nil } diff --git a/pkg/filter/dt/filter_mysql_query.go b/pkg/filter/dt/filter_mysql_query.go index e15f72c..bb20329 100644 --- a/pkg/filter/dt/filter_mysql_query.go +++ b/pkg/filter/dt/filter_mysql_query.go @@ -27,12 +27,13 @@ import ( "github.com/cectc/dbpack/pkg/dt/schema" "github.com/cectc/dbpack/pkg/filter/dt/exec" "github.com/cectc/dbpack/pkg/log" + "github.com/cectc/dbpack/pkg/misc" "github.com/cectc/dbpack/pkg/proto" "github.com/cectc/dbpack/third_party/parser/ast" ) func (f *_mysqlFilter) processBeforeQueryDelete(ctx context.Context, conn *driver.BackendConnection, deleteStmt *ast.DeleteStmt) error { - if hasGlobalLockHint(deleteStmt.TableHints) { + if misc.HasGlobalLockHint(deleteStmt.TableHints) { executor := exec.NewQueryGlobalLockExecutor(conn, false, deleteStmt, nil) result, err := executor.Executable(ctx, f.lockRetryInterval, f.lockRetryTimes) if err != nil { @@ -43,7 +44,7 @@ func (f *_mysqlFilter) processBeforeQueryDelete(ctx context.Context, conn *drive } return nil } - if has, _ := hasXIDHint(deleteStmt.TableHints); !has { + if has, _ := misc.HasXIDHint(deleteStmt.TableHints); !has { return nil } executor := exec.NewQueryDeleteExecutor(conn, deleteStmt) @@ -58,7 +59,7 @@ func (f *_mysqlFilter) processBeforeQueryDelete(ctx context.Context, conn *drive } func (f *_mysqlFilter) processBeforeQueryUpdate(ctx context.Context, conn *driver.BackendConnection, updateStmt *ast.UpdateStmt) error { - if hasGlobalLockHint(updateStmt.TableHints) { + if misc.HasGlobalLockHint(updateStmt.TableHints) { executor := exec.NewQueryGlobalLockExecutor(conn, true, nil, updateStmt) result, err := executor.Executable(ctx, f.lockRetryInterval, f.lockRetryTimes) if err != nil { @@ -69,7 +70,7 @@ func (f *_mysqlFilter) processBeforeQueryUpdate(ctx context.Context, conn *drive } return nil } - if has, _ := hasXIDHint(updateStmt.TableHints); !has { + if has, _ := misc.HasXIDHint(updateStmt.TableHints); !has { return nil } executor := exec.NewQueryUpdateExecutor(conn, updateStmt, nil) @@ -84,7 +85,7 @@ func (f *_mysqlFilter) processBeforeQueryUpdate(ctx context.Context, conn *drive } func (f *_mysqlFilter) processAfterQueryDelete(ctx context.Context, conn *driver.BackendConnection, deleteStmt *ast.DeleteStmt) error { - has, xid := hasXIDHint(deleteStmt.TableHints) + has, xid := misc.HasXIDHint(deleteStmt.TableHints) if !has { return nil } @@ -114,7 +115,7 @@ func (f *_mysqlFilter) processAfterQueryDelete(ctx context.Context, conn *driver func (f *_mysqlFilter) processAfterQueryInsert(ctx context.Context, conn *driver.BackendConnection, result proto.Result, insertStmt *ast.InsertStmt) error { - has, xid := hasXIDHint(insertStmt.TableHints) + has, xid := misc.HasXIDHint(insertStmt.TableHints) if !has { return nil } @@ -142,7 +143,7 @@ func (f *_mysqlFilter) processAfterQueryInsert(ctx context.Context, conn *driver } func (f *_mysqlFilter) processAfterQueryUpdate(ctx context.Context, conn *driver.BackendConnection, updateStmt *ast.UpdateStmt) error { - has, xid := hasXIDHint(updateStmt.TableHints) + has, xid := misc.HasXIDHint(updateStmt.TableHints) if !has { return nil } @@ -175,7 +176,7 @@ func (f *_mysqlFilter) processAfterQueryUpdate(ctx context.Context, conn *driver func (f *_mysqlFilter) processSelectForQueryUpdate(ctx context.Context, conn *driver.BackendConnection, result proto.Result, selectStmt *ast.SelectStmt) error { - has, _ := hasXIDHint(selectStmt.TableHints) + has, _ := misc.HasXIDHint(selectStmt.TableHints) if !has { return nil } diff --git a/pkg/misc/hint.go b/pkg/misc/hint.go new file mode 100644 index 0000000..dd96a60 --- /dev/null +++ b/pkg/misc/hint.go @@ -0,0 +1,74 @@ +/* + * Copyright 2022 CECTC, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package misc + +import ( + "strings" + + "github.com/cectc/dbpack/third_party/parser/ast" + "github.com/cectc/dbpack/third_party/parser/model" +) + +const ( + XIDHint = "XID" + GlobalLockHint = "GlobalLock" + TraceParentHint = "TraceParent" +) + +func HasXIDHint(hints []*ast.TableOptimizerHint) (bool, string) { + for _, hint := range hints { + if strings.EqualFold(hint.HintName.String(), XIDHint) { + hintData := hint.HintData.(model.CIStr) + xid := hintData.String() + return true, xid + } + } + return false, "" +} + +func HasGlobalLockHint(hints []*ast.TableOptimizerHint) bool { + for _, hint := range hints { + if strings.EqualFold(hint.HintName.String(), GlobalLockHint) { + return true + } + } + return false +} + +func HasTraceParentHint(hints []*ast.TableOptimizerHint) (bool, string) { + for _, hint := range hints { + if strings.EqualFold(hint.HintName.String(), TraceParentHint) { + hintData := hint.HintData.(model.CIStr) + traceParent := hintData.String() + return true, traceParent + } + } + return false, "" +} + +func NewXIDHint(xid string) *ast.TableOptimizerHint { + return &ast.TableOptimizerHint{ + HintName: model.CIStr{ + O: XIDHint, + L: strings.ToLower(XIDHint), + }, + HintData: model.CIStr{ + O: xid, + L: strings.ToLower(xid), + }, + } +} diff --git a/pkg/optimize/optimize_insert.go b/pkg/optimize/optimize_insert.go index b777004..a01f7a2 100644 --- a/pkg/optimize/optimize_insert.go +++ b/pkg/optimize/optimize_insert.go @@ -109,11 +109,9 @@ func (o Optimizer) optimizeInsert(ctx context.Context, stmt *ast.InsertStmt, arg } func findPkIndex(stmt *ast.InsertStmt, pk string) int { - if stmt.Columns != nil { - for i, column := range stmt.Columns { - if column.Name.String() == pk { - return i - } + for i, column := range stmt.Columns { + if column.Name.String() == pk { + return i } } return -1 diff --git a/pkg/plan/delete.go b/pkg/plan/delete.go index 6f7eb5c..15f79e5 100644 --- a/pkg/plan/delete.go +++ b/pkg/plan/delete.go @@ -18,14 +18,19 @@ package plan import ( "context" + "fmt" "strings" "github.com/pkg/errors" "github.com/cectc/dbpack/pkg/constant" + "github.com/cectc/dbpack/pkg/dt" "github.com/cectc/dbpack/pkg/log" + "github.com/cectc/dbpack/pkg/misc" "github.com/cectc/dbpack/pkg/mysql" "github.com/cectc/dbpack/pkg/proto" + "github.com/cectc/dbpack/pkg/visitor" + "github.com/cectc/dbpack/third_party/parser" "github.com/cectc/dbpack/third_party/parser/ast" "github.com/cectc/dbpack/third_party/parser/format" ) @@ -38,7 +43,7 @@ type DeletePlan struct { Executor proto.DBGroupExecutor } -func (p *DeletePlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *DeletePlan) Execute(ctx context.Context, hints ...*ast.TableOptimizerHint) (proto.Result, uint16, error) { var ( sb strings.Builder tx proto.Tx @@ -53,17 +58,27 @@ func (p *DeletePlan) Execute(ctx context.Context) (proto.Result, uint16, error) } for _, table := range p.Tables { sb.Reset() - if err = p.generate(&sb, table); err != nil { + if err = p.generate(&sb, table, hints...); err != nil { return nil, 0, errors.Wrap(err, "failed to generate sql for delete") } sql := sb.String() log.Debugf("delete, db name: %s, sql: %s", p.Database, sql) + pp := parser.New() + stmtNode, err := pp.ParseOneStmt(sql, "", "") + if err != nil { + return nil, 0, errors.WithStack(err) + } + stmtNode.Accept(&visitor.ParamVisitor{}) + commandType := proto.CommandType(ctx) switch commandType { case constant.ComQuery: + ctx := proto.WithQueryStmt(ctx, stmtNode) result, warns, err = tx.Query(ctx, sql) case constant.ComStmtExecute: + stmt := generateStatement(sql, stmtNode, p.Args) + ctx := proto.WithPrepareStmt(ctx, stmt) result, warns, err = tx.ExecuteSql(ctx, sql, p.Args...) default: continue @@ -87,11 +102,25 @@ func (p *DeletePlan) Execute(ctx context.Context) (proto.Result, uint16, error) return mysqlResult, warnings, nil } -func (p *DeletePlan) generate(sb *strings.Builder, table string) error { +func (p *DeletePlan) generate(sb *strings.Builder, table string, hints ...*ast.TableOptimizerHint) error { ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, sb) ctx.WriteKeyWord("DELETE ") + + if len(hints) != 0 { + ctx.WritePlain("/*+ ") + for i, tableHint := range hints { + if i != 0 { + ctx.WritePlain(" ") + } + if err := tableHint.Restore(ctx); err != nil { + return errors.Wrapf(err, "An error occurred while restore DeleteStmt.TableHints[%d], HintName: %s", + i, tableHint.HintName.String()) + } + } + ctx.WritePlain("*/ ") + } + ctx.WriteKeyWord("FROM ") - // todo add xid hint for distributed transaction ctx.WritePlain(table) if p.Stmt.Where != nil { ctx.WriteKeyWord(" WHERE ") @@ -127,18 +156,46 @@ type MultiDeletePlan struct { Plans []*DeletePlan } -func (p *MultiDeletePlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *MultiDeletePlan) Execute(ctx context.Context, _ ...*ast.TableOptimizerHint) (result proto.Result, warns uint16, err error) { var ( affectedRows uint64 warnings uint16 + affected uint64 + hints []*ast.TableOptimizerHint ) - // todo distributed transaction + if has, _ := misc.HasXIDHint(p.Stmt.TableHints); !has { + tableName := p.Stmt.TableRefs.TableRefs.Left.(*ast.TableSource).Source.(*ast.TableName).Name.String() + transactionManager := dt.GetDistributedTransactionManager() + timeoutVariable := proto.Variable(ctx, constant.TransactionTimeout) + timeout, ok := timeoutVariable.(int32) + if !ok { + return nil, 0, errors.New("transaction timeout must be of type int32") + } + var xid string + xid, err = transactionManager.Begin(ctx, fmt.Sprintf("DELETE_%s", tableName), timeout) + if err != nil { + return nil, 0, err + } + hints = append(hints, misc.NewXIDHint(xid)) + defer func() { + if err != nil { + if _, rollbackErr := transactionManager.Rollback(ctx, xid); rollbackErr != nil { + log.Error(err) + } + } else { + if _, commitErr := transactionManager.Commit(ctx, xid); commitErr != nil { + log.Error(err) + } + } + }() + } + for _, pl := range p.Plans { - result, warns, err := pl.Execute(ctx) + result, warns, err = pl.Execute(ctx, hints...) if err != nil { return nil, 0, err } - affected, err := result.RowsAffected() + affected, err = result.RowsAffected() if err != nil { return nil, 0, errors.WithStack(err) } diff --git a/pkg/plan/insert.go b/pkg/plan/insert.go index 7ada5fc..5bb6130 100644 --- a/pkg/plan/insert.go +++ b/pkg/plan/insert.go @@ -38,7 +38,7 @@ type InsertPlan struct { Executor proto.DBGroupExecutor } -func (p *InsertPlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *InsertPlan) Execute(ctx context.Context, hints ...*ast.TableOptimizerHint) (proto.Result, uint16, error) { var ( sb strings.Builder err error diff --git a/pkg/plan/query.go b/pkg/plan/query.go index 5205f4f..e4e7506 100644 --- a/pkg/plan/query.go +++ b/pkg/plan/query.go @@ -51,7 +51,7 @@ type Limit struct { Count int64 } -func (p *QueryOnSingleDBPlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *QueryOnSingleDBPlan) Execute(ctx context.Context, hints ...*ast.TableOptimizerHint) (proto.Result, uint16, error) { var ( sb strings.Builder args []interface{} @@ -166,7 +166,7 @@ type QueryOnMultiDBPlan struct { Plans []*QueryOnSingleDBPlan } -func (p *QueryOnMultiDBPlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *QueryOnMultiDBPlan) Execute(ctx context.Context, _ ...*ast.TableOptimizerHint) (proto.Result, uint16, error) { resultChan := make(chan *ResultWithErr, len(p.Plans)) var wg sync.WaitGroup wg.Add(len(p.Plans)) diff --git a/pkg/plan/result.go b/pkg/plan/result.go index 65101cb..d7b27a4 100644 --- a/pkg/plan/result.go +++ b/pkg/plan/result.go @@ -18,6 +18,7 @@ package plan import ( "context" + "fmt" "sort" "strings" "time" @@ -531,3 +532,18 @@ func getOrderByFieldIndex(orderByField string, fields []*mysql.Field) int { } return 0 } + +func generateStatement(sql string, stmtNode ast.StmtNode, args []interface{}) *proto.Stmt { + stmt := &proto.Stmt{ + HasLongDataParam: true, + SqlText: sql, + ParamsCount: uint16(len(args)), + StmtNode: stmtNode, + } + stmt.BindVars = make(map[string]interface{}) + for i, arg := range args { + parameterID := fmt.Sprintf("v%d", i+1) + stmt.BindVars[parameterID] = arg + } + return stmt +} diff --git a/pkg/plan/update.go b/pkg/plan/update.go index ae1c329..6ecab34 100644 --- a/pkg/plan/update.go +++ b/pkg/plan/update.go @@ -38,7 +38,7 @@ type UpdatePlan struct { Executor proto.DBGroupExecutor } -func (p *UpdatePlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *UpdatePlan) Execute(ctx context.Context, hints ...*ast.TableOptimizerHint) (proto.Result, uint16, error) { var ( sb strings.Builder tx proto.Tx @@ -137,7 +137,7 @@ type MultiUpdatePlan struct { Plans []*UpdatePlan } -func (p *MultiUpdatePlan) Execute(ctx context.Context) (proto.Result, uint16, error) { +func (p *MultiUpdatePlan) Execute(ctx context.Context, _ ...*ast.TableOptimizerHint) (proto.Result, uint16, error) { var ( affectedRows uint64 warnings uint16 diff --git a/pkg/proto/interface.go b/pkg/proto/interface.go index 9c5d571..2972495 100644 --- a/pkg/proto/interface.go +++ b/pkg/proto/interface.go @@ -166,7 +166,7 @@ type ( // Plan represents a plan for query/execute command. Plan interface { // Execute executes the current Plan. - Execute(ctx context.Context) (Result, uint16, error) + Execute(ctx context.Context, hints ...*ast.TableOptimizerHint) (Result, uint16, error) } // Optimizer represents a sql statement optimizer which can be used to create QueryPlan or ExecPlan. diff --git a/test/shd/sharding_test.go b/test/shd/sharding_test.go index 7bc59d1..4c7fe5f 100644 --- a/test/shd/sharding_test.go +++ b/test/shd/sharding_test.go @@ -19,6 +19,7 @@ package rws import ( "database/sql" "testing" + "time" _ "github.com/go-sql-driver/mysql" // register mysql "github.com/stretchr/testify/suite" @@ -34,7 +35,7 @@ const ( selectDrugResourceOrderByIDDescLimit = "select id, drug_res_type_id, base_type, sale_price from drug_resource where id between ? and ? order by id desc limit ?, ?" selectDrugResourceOrderByIDDescLimit2 = "select id, drug_res_type_id, base_type, sale_price from drug_resource where id between ? and ? order by id desc limit ?" - deleteDrugResource = "delete from drug_resource where id = ?" + deleteDrugResource = "delete from drug_resource where id between ? and ?" insertDrugResource = "INSERT INTO `drug_resource`(`id`, `drug_res_type_id`, `base_type`, `status`, `type_id`, " + "`dict_dosage_id`, `code`, `pym`, `name`, `manufacturer_id`, `approval_no`, `med_type`, `admin_code`, " + "`pack_unit_id`, `min_unit_id`, `pack_quantity`, `dosage_unit_id`, `dosage_quantity`, `pack_spec`, `take_method_id`, " + @@ -166,11 +167,12 @@ func (suite *_ShardingSuite) TestSelectOrderByAndLimit2() { } func (suite *_ShardingSuite) TestDeleteDrugResource() { - result, err := suite.db.Exec(deleteDrugResource, 20) + result, err := suite.db.Exec(deleteDrugResource, 10, 20) suite.Assert().Nil(err) affectedRows, err := result.RowsAffected() suite.Assert().Nil(err) suite.Assert().Equal(int64(1), affectedRows) + time.Sleep(10 * time.Second) } func (suite *_ShardingSuite) TestInsertDrugResource() { diff --git a/third_party/parser/ast/misc.go b/third_party/parser/ast/misc.go index 7b7ff15..46e3806 100644 --- a/third_party/parser/ast/misc.go +++ b/third_party/parser/ast/misc.go @@ -3385,6 +3385,8 @@ func (n *TableOptimizerHint) Restore(ctx *format.RestoreCtx) error { ctx.WriteString(hintData.VarName) ctx.WritePlain(", ") ctx.WriteString(hintData.Value) + case "xid": + ctx.WriteString(n.HintData.(model.CIStr).String()) } ctx.WritePlain(")") return nil