gitee.com/curryzheng/dm@v0.0.1/n.go (about) 1 /* 2 * Copyright (c) 2000-2018, 达梦数据库有限公司. 3 * All rights reserved. 4 */ 5 package dm 6 7 import ( 8 "bytes" 9 "context" 10 "database/sql" 11 "database/sql/driver" 12 "fmt" 13 "gitee.com/curryzheng/dm/parser" 14 "golang.org/x/text/encoding" 15 "sync/atomic" 16 ) 17 18 type DmConnection struct { 19 filterable 20 21 dmConnector *DmConnector 22 Access *dm_build_690 23 stmtMap map[int32]*DmStatement 24 stmtPool []stmtPoolInfo 25 lastExecInfo *execRetInfo 26 lexer *parser.Lexer 27 encode encoding.Encoding 28 encodeBuffer *bytes.Buffer 29 transformReaderDst []byte 30 transformReaderSrc []byte 31 32 serverEncoding string 33 GlobalServerSeries int 34 ServerVersion string 35 Malini2 bool 36 Execute2 bool 37 LobEmptyCompOrcl bool 38 IsoLevel int32 39 ReadOnly bool 40 NewLobFlag bool 41 sslEncrypt int 42 MaxRowSize int32 43 DDLAutoCommit bool 44 BackslashEscape bool 45 SvrStat int32 46 SvrMode int32 47 ConstParaOpt bool 48 DbTimezone int16 49 LifeTimeRemainder int16 50 InstanceName string 51 Schema string 52 LastLoginIP string 53 LastLoginTime string 54 FailedAttempts int32 55 LoginWarningID int32 56 GraceTimeRemainder int32 57 Guid string 58 DbName string 59 StandbyHost string 60 StandbyPort int32 61 StandbyCount int32 62 SessionID int64 63 OracleDateLanguage byte 64 FormatDate string 65 FormatTimestamp string 66 FormatTimestampTZ string 67 FormatTime string 68 FormatTimeTZ string 69 Local bool 70 MsgVersion int32 71 TrxStatus int32 72 dscControl bool 73 trxFinish bool 74 sessionID int64 75 autoCommit bool 76 isBatch bool 77 78 watching bool 79 watcher chan<- context.Context 80 closech chan struct{} 81 finished chan<- struct{} 82 canceled atomicError 83 closed atomicBool 84 } 85 86 func (conn *DmConnection) setTrxFinish(status int32) { 87 switch status & Dm_build_1080 { 88 case Dm_build_1077, Dm_build_1078, Dm_build_1079: 89 conn.trxFinish = true 90 default: 91 conn.trxFinish = false 92 } 93 } 94 95 func (dmConn *DmConnection) init() { 96 if dmConn.dmConnector.stmtPoolMaxSize > 0 { 97 dmConn.stmtPool = make([]stmtPoolInfo, 0, dmConn.dmConnector.stmtPoolMaxSize) 98 } 99 100 dmConn.stmtMap = make(map[int32]*DmStatement) 101 dmConn.DbTimezone = 0 102 dmConn.GlobalServerSeries = 0 103 dmConn.MaxRowSize = 0 104 dmConn.LobEmptyCompOrcl = false 105 dmConn.ReadOnly = false 106 dmConn.DDLAutoCommit = false 107 dmConn.ConstParaOpt = false 108 dmConn.IsoLevel = -1 109 dmConn.sessionID = -1 110 dmConn.Malini2 = true 111 dmConn.NewLobFlag = true 112 dmConn.Execute2 = true 113 dmConn.serverEncoding = ENCODING_GB18030 114 dmConn.TrxStatus = Dm_build_1028 115 dmConn.OracleDateLanguage = byte(Locale) 116 dmConn.lastExecInfo = NewExceInfo() 117 dmConn.MsgVersion = Dm_build_961 118 119 dmConn.idGenerator = dmConnIDGenerator 120 } 121 122 func (dmConn *DmConnection) reset() { 123 dmConn.DbTimezone = 0 124 dmConn.GlobalServerSeries = 0 125 dmConn.MaxRowSize = 0 126 dmConn.LobEmptyCompOrcl = false 127 dmConn.ReadOnly = false 128 dmConn.DDLAutoCommit = false 129 dmConn.ConstParaOpt = false 130 dmConn.IsoLevel = -1 131 dmConn.sessionID = -1 132 dmConn.Malini2 = true 133 dmConn.NewLobFlag = true 134 dmConn.Execute2 = true 135 dmConn.serverEncoding = ENCODING_GB18030 136 dmConn.TrxStatus = Dm_build_1028 137 } 138 139 func (dc *DmConnection) checkClosed() error { 140 if dc.closed.IsSet() { 141 return driver.ErrBadConn 142 } 143 144 return nil 145 } 146 147 func (dc *DmConnection) executeInner(query string, execType int16) (interface{}, error) { 148 149 stmt, err := NewDmStmt(dc, query) 150 151 if err != nil { 152 return nil, err 153 } 154 155 if execType == Dm_build_1045 { 156 defer stmt.close() 157 } 158 159 stmt.innerUsed = true 160 if stmt.dmConn.dmConnector.escapeProcess { 161 stmt.nativeSql, err = stmt.dmConn.escape(stmt.nativeSql, stmt.dmConn.dmConnector.keyWords) 162 if err != nil { 163 stmt.close() 164 return nil, err 165 } 166 } 167 168 var optParamList []OptParameter 169 170 if stmt.dmConn.ConstParaOpt { 171 optParamList = make([]OptParameter, 0) 172 stmt.nativeSql, optParamList, err = stmt.dmConn.execOpt(stmt.nativeSql, optParamList, stmt.dmConn.getServerEncoding()) 173 if err != nil { 174 stmt.close() 175 optParamList = nil 176 } 177 } 178 179 if execType == Dm_build_1044 && dc.dmConnector.enRsCache { 180 rpv, err := rp.get(stmt, query) 181 if err != nil { 182 return nil, err 183 } 184 185 if rpv != nil { 186 stmt.execInfo = rpv.execInfo 187 dc.lastExecInfo = rpv.execInfo 188 return newDmRows(rpv.getResultSet(stmt)), nil 189 } 190 } 191 192 var info *execRetInfo 193 194 if optParamList != nil && len(optParamList) > 0 { 195 info, err = dc.Access.Dm_build_769(stmt, optParamList) 196 if err != nil { 197 stmt.nativeSql = query 198 info, err = dc.Access.Dm_build_775(stmt, execType) 199 } 200 } else { 201 info, err = dc.Access.Dm_build_775(stmt, execType) 202 } 203 204 if err != nil { 205 stmt.close() 206 return nil, err 207 } 208 dc.lastExecInfo = info 209 210 if info.hasResultSet { 211 return newDmRows(newInnerRows(0, stmt, info)), nil 212 } else { 213 return newDmResult(stmt, info), nil 214 } 215 } 216 217 func g2dbIsoLevel(isoLevel int32) int32 { 218 switch isoLevel { 219 case 1: 220 return Dm_build_1032 221 case 2: 222 return Dm_build_1033 223 case 4: 224 return Dm_build_1034 225 case 6: 226 return Dm_build_1035 227 default: 228 return -1 229 } 230 } 231 232 func (dc *DmConnection) Begin() (driver.Tx, error) { 233 if len(dc.filterChain.filters) == 0 { 234 return dc.begin() 235 } else { 236 return dc.filterChain.reset().DmConnectionBegin(dc) 237 } 238 } 239 240 func (dc *DmConnection) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { 241 if len(dc.filterChain.filters) == 0 { 242 return dc.beginTx(ctx, opts) 243 } 244 return dc.filterChain.reset().DmConnectionBeginTx(dc, ctx, opts) 245 } 246 247 func (dc *DmConnection) Commit() error { 248 if len(dc.filterChain.filters) == 0 { 249 return dc.commit() 250 } else { 251 return dc.filterChain.reset().DmConnectionCommit(dc) 252 } 253 } 254 255 func (dc *DmConnection) Rollback() error { 256 if len(dc.filterChain.filters) == 0 { 257 return dc.rollback() 258 } else { 259 return dc.filterChain.reset().DmConnectionRollback(dc) 260 } 261 } 262 263 func (dc *DmConnection) Close() error { 264 if len(dc.filterChain.filters) == 0 { 265 return dc.close() 266 } else { 267 return dc.filterChain.reset().DmConnectionClose(dc) 268 } 269 } 270 271 func (dc *DmConnection) Ping(ctx context.Context) error { 272 if len(dc.filterChain.filters) == 0 { 273 return dc.ping(ctx) 274 } else { 275 return dc.filterChain.reset().DmConnectionPing(dc, ctx) 276 } 277 } 278 279 func (dc *DmConnection) Exec(query string, args []driver.Value) (driver.Result, error) { 280 if len(dc.filterChain.filters) == 0 { 281 return dc.exec(query, args) 282 } 283 return dc.filterChain.reset().DmConnectionExec(dc, query, args) 284 } 285 286 func (dc *DmConnection) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { 287 if len(dc.filterChain.filters) == 0 { 288 return dc.execContext(ctx, query, args) 289 } 290 return dc.filterChain.reset().DmConnectionExecContext(dc, ctx, query, args) 291 } 292 293 func (dc *DmConnection) Query(query string, args []driver.Value) (driver.Rows, error) { 294 if len(dc.filterChain.filters) == 0 { 295 return dc.query(query, args) 296 } 297 return dc.filterChain.reset().DmConnectionQuery(dc, query, args) 298 } 299 300 func (dc *DmConnection) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { 301 if len(dc.filterChain.filters) == 0 { 302 return dc.queryContext(ctx, query, args) 303 } 304 return dc.filterChain.reset().DmConnectionQueryContext(dc, ctx, query, args) 305 } 306 307 func (dc *DmConnection) Prepare(query string) (driver.Stmt, error) { 308 if len(dc.filterChain.filters) == 0 { 309 return dc.prepare(query) 310 } 311 return dc.filterChain.reset().DmConnectionPrepare(dc, query) 312 } 313 314 func (dc *DmConnection) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { 315 if len(dc.filterChain.filters) == 0 { 316 return dc.prepareContext(ctx, query) 317 } 318 return dc.filterChain.reset().DmConnectionPrepareContext(dc, ctx, query) 319 } 320 321 func (dc *DmConnection) ResetSession(ctx context.Context) error { 322 if len(dc.filterChain.filters) == 0 { 323 return dc.resetSession(ctx) 324 } 325 return dc.filterChain.reset().DmConnectionResetSession(dc, ctx) 326 } 327 328 func (dc *DmConnection) CheckNamedValue(nv *driver.NamedValue) error { 329 if len(dc.filterChain.filters) == 0 { 330 return dc.checkNamedValue(nv) 331 } 332 return dc.filterChain.reset().DmConnectionCheckNamedValue(dc, nv) 333 } 334 335 func (dc *DmConnection) begin() (*DmConnection, error) { 336 return dc.beginTx(context.Background(), driver.TxOptions{driver.IsolationLevel(sql.LevelDefault), false}) 337 } 338 339 func (dc *DmConnection) beginTx(ctx context.Context, opts driver.TxOptions) (*DmConnection, error) { 340 if err := dc.watchCancel(ctx); err != nil { 341 return nil, err 342 } 343 defer dc.finish() 344 345 err := dc.checkClosed() 346 if err != nil { 347 return nil, err 348 } 349 350 dc.autoCommit = false 351 352 if sql.IsolationLevel(opts.Isolation) == sql.LevelDefault { 353 opts.Isolation = driver.IsolationLevel(sql.LevelReadCommitted) 354 } 355 356 dc.ReadOnly = opts.ReadOnly 357 358 if dc.IsoLevel == int32(opts.Isolation) { 359 return dc, nil 360 } 361 362 switch sql.IsolationLevel(opts.Isolation) { 363 case sql.LevelDefault, sql.LevelReadUncommitted: 364 return dc, nil 365 case sql.LevelReadCommitted, sql.LevelSerializable: 366 dc.IsoLevel = int32(opts.Isolation) 367 case sql.LevelRepeatableRead: 368 if dc.CompatibleMysql() { 369 dc.IsoLevel = int32(sql.LevelReadCommitted) 370 } else { 371 return nil, ECGO_INVALID_TRAN_ISOLATION.throw() 372 } 373 default: 374 return nil, ECGO_INVALID_TRAN_ISOLATION.throw() 375 } 376 377 err = dc.Access.Dm_build_829(dc) 378 if err != nil { 379 return nil, err 380 } 381 return dc, nil 382 } 383 384 func (dc *DmConnection) commit() error { 385 err := dc.checkClosed() 386 if err != nil { 387 return err 388 } 389 390 defer func() { 391 dc.autoCommit = dc.dmConnector.autoCommit 392 }() 393 394 if !dc.autoCommit { 395 err = dc.Access.Commit() 396 if err != nil { 397 return err 398 } 399 dc.trxFinish = true 400 return nil 401 } else if !dc.dmConnector.alwayseAllowCommit { 402 return ECGO_COMMIT_IN_AUTOCOMMIT_MODE.throw() 403 } 404 405 return nil 406 } 407 408 func (dc *DmConnection) rollback() error { 409 err := dc.checkClosed() 410 if err != nil { 411 return err 412 } 413 414 defer func() { 415 dc.autoCommit = dc.dmConnector.autoCommit 416 }() 417 418 if !dc.autoCommit { 419 err = dc.Access.Rollback() 420 if err != nil { 421 return err 422 } 423 dc.trxFinish = true 424 return nil 425 } else if !dc.dmConnector.alwayseAllowCommit { 426 return ECGO_ROLLBACK_IN_AUTOCOMMIT_MODE.throw() 427 } 428 429 return nil 430 } 431 432 func (dc *DmConnection) reconnect() error { 433 err := dc.Access.Close() 434 if err != nil { 435 return err 436 } 437 438 for _, stmt := range dc.stmtMap { 439 stmt.closed = true 440 for id, _ := range stmt.rsMap { 441 delete(stmt.rsMap, id) 442 } 443 } 444 445 if dc.stmtPool != nil { 446 dc.stmtPool = dc.stmtPool[:0] 447 } 448 449 dc.dmConnector.reConnection = dc 450 451 if dc.dmConnector.group != nil { 452 _, err = dc.dmConnector.group.connect(dc.dmConnector) 453 if err != nil { 454 return err 455 } 456 } else { 457 _, err = dc.dmConnector.connect(context.Background()) 458 } 459 460 for _, stmt := range dc.stmtMap { 461 err = dc.Access.Dm_build_747(stmt) 462 if err != nil { 463 return err 464 } 465 466 if stmt.paramCount > 0 { 467 err = stmt.prepare() 468 if err != nil { 469 return err 470 } 471 } 472 } 473 474 return nil 475 } 476 477 func (dc *DmConnection) cleanup() { 478 dc.close() 479 } 480 481 func (dc *DmConnection) close() error { 482 if !dc.closed.TrySet(true) { 483 return nil 484 } 485 486 close(dc.closech) 487 if dc.Access == nil { 488 return nil 489 } 490 491 dc.rollback() 492 493 for _, stmt := range dc.stmtMap { 494 stmt.free() 495 } 496 497 if dc.stmtPool != nil { 498 for _, spi := range dc.stmtPool { 499 dc.Access.Dm_build_752(spi.id) 500 } 501 dc.stmtPool = nil 502 } 503 504 dc.Access.Close() 505 506 return nil 507 } 508 509 func (dc *DmConnection) ping(ctx context.Context) error { 510 if err := dc.watchCancel(ctx); err != nil { 511 return err 512 } 513 defer dc.finish() 514 515 rows, err := dc.query("select 1", nil) 516 if err != nil { 517 return err 518 } 519 return rows.close() 520 } 521 522 func (dc *DmConnection) exec(query string, args []driver.Value) (*DmResult, error) { 523 err := dc.checkClosed() 524 if err != nil { 525 return nil, err 526 } 527 528 if args != nil && len(args) > 0 { 529 stmt, err := dc.prepare(query) 530 defer stmt.close() 531 if err != nil { 532 return nil, err 533 } 534 dc.lastExecInfo = stmt.execInfo 535 536 return stmt.exec(args) 537 } else { 538 r1, err := dc.executeInner(query, Dm_build_1045) 539 if err != nil { 540 return nil, err 541 } 542 543 if r2, ok := r1.(*DmResult); ok { 544 return r2, nil 545 } else { 546 return nil, ECGO_NOT_EXEC_SQL.throw() 547 } 548 } 549 } 550 551 func (dc *DmConnection) execContext(ctx context.Context, query string, args []driver.NamedValue) (*DmResult, error) { 552 553 if err := dc.watchCancel(ctx); err != nil { 554 return nil, err 555 } 556 defer dc.finish() 557 558 err := dc.checkClosed() 559 if err != nil { 560 return nil, err 561 } 562 563 if args != nil && len(args) > 0 { 564 stmt, err := dc.prepare(query) 565 defer stmt.close() 566 if err != nil { 567 return nil, err 568 } 569 dc.lastExecInfo = stmt.execInfo 570 571 return stmt.execContext(ctx, args) 572 } else { 573 r1, err := dc.executeInner(query, Dm_build_1045) 574 if err != nil { 575 return nil, err 576 } 577 578 if r2, ok := r1.(*DmResult); ok { 579 return r2, nil 580 } else { 581 return nil, ECGO_NOT_EXEC_SQL.throw() 582 } 583 } 584 } 585 586 func (dc *DmConnection) query(query string, args []driver.Value) (*DmRows, error) { 587 588 err := dc.checkClosed() 589 if err != nil { 590 return nil, err 591 } 592 593 if args != nil && len(args) > 0 { 594 stmt, err := dc.prepare(query) 595 if err != nil { 596 stmt.close() 597 return nil, err 598 } 599 dc.lastExecInfo = stmt.execInfo 600 601 stmt.innerUsed = true 602 return stmt.query(args) 603 604 } else { 605 r1, err := dc.executeInner(query, Dm_build_1044) 606 if err != nil { 607 return nil, err 608 } 609 610 if r2, ok := r1.(*DmRows); ok { 611 return r2, nil 612 } else { 613 return nil, ECGO_NOT_QUERY_SQL.throw() 614 } 615 } 616 } 617 618 func (dc *DmConnection) queryContext(ctx context.Context, query string, args []driver.NamedValue) (*DmRows, error) { 619 if err := dc.watchCancel(ctx); err != nil { 620 return nil, err 621 } 622 defer dc.finish() 623 624 err := dc.checkClosed() 625 if err != nil { 626 return nil, err 627 } 628 629 if args != nil && len(args) > 0 { 630 stmt, err := dc.prepare(query) 631 if err != nil { 632 stmt.close() 633 return nil, err 634 } 635 dc.lastExecInfo = stmt.execInfo 636 637 stmt.innerUsed = true 638 return stmt.queryContext(ctx, args) 639 640 } else { 641 r1, err := dc.executeInner(query, Dm_build_1044) 642 if err != nil { 643 return nil, err 644 } 645 646 if r2, ok := r1.(*DmRows); ok { 647 return r2, nil 648 } else { 649 return nil, ECGO_NOT_QUERY_SQL.throw() 650 } 651 } 652 653 } 654 655 func (dc *DmConnection) prepare(query string) (*DmStatement, error) { 656 err := dc.checkClosed() 657 if err != nil { 658 return nil, err 659 } 660 661 stmt, err := NewDmStmt(dc, query) 662 if err != nil { 663 return nil, err 664 } 665 666 err = stmt.prepare() 667 return stmt, err 668 } 669 670 func (dc *DmConnection) prepareContext(ctx context.Context, query string) (*DmStatement, error) { 671 if err := dc.watchCancel(ctx); err != nil { 672 return nil, err 673 } 674 defer dc.finish() 675 676 err := dc.checkClosed() 677 if err != nil { 678 return nil, err 679 } 680 681 stmt, err := dc.prepare(query) 682 if err != nil { 683 return nil, err 684 } 685 686 return stmt, nil 687 } 688 689 func (dc *DmConnection) resetSession(ctx context.Context) error { 690 err := dc.checkClosed() 691 if err != nil { 692 return err 693 } 694 695 for _, stmt := range dc.stmtMap { 696 stmt.inUse = false 697 } 698 699 return nil 700 } 701 702 func (dc *DmConnection) checkNamedValue(nv *driver.NamedValue) error { 703 var err error 704 var cvt = converter{dc, false} 705 nv.Value, err = cvt.ConvertValue(nv.Value) 706 dc.isBatch = cvt.isBatch 707 return err 708 } 709 710 func (dc *DmConnection) driverQuery(query string) (*DmStatement, *DmRows, error) { 711 stmt, err := NewDmStmt(dc, query) 712 if err != nil { 713 return nil, nil, err 714 } 715 stmt.innerUsed = true 716 stmt.innerExec = true 717 info, err := dc.Access.Dm_build_775(stmt, Dm_build_1044) 718 if err != nil { 719 return nil, nil, err 720 } 721 dc.lastExecInfo = info 722 stmt.innerExec = false 723 return stmt, newDmRows(newInnerRows(0, stmt, info)), nil 724 } 725 726 func (dc *DmConnection) getIndexOnEPGroup() int32 { 727 if dc.dmConnector.group == nil || dc.dmConnector.group.epList == nil { 728 return -1 729 } 730 for i := 0; i < len(dc.dmConnector.group.epList); i++ { 731 ep := dc.dmConnector.group.epList[i] 732 if dc.dmConnector.host == ep.host && dc.dmConnector.port == ep.port { 733 return int32(i) 734 } 735 } 736 return -1 737 } 738 739 func (dc *DmConnection) getServerEncoding() string { 740 if dc.dmConnector.charCode != "" { 741 return dc.dmConnector.charCode 742 } 743 return dc.serverEncoding 744 } 745 746 func (dc *DmConnection) lobFetchAll() bool { 747 return dc.dmConnector.lobMode == 2 748 } 749 750 func (conn *DmConnection) CompatibleOracle() bool { 751 return conn.dmConnector.compatibleMode == COMPATIBLE_MODE_ORACLE 752 } 753 754 func (conn *DmConnection) CompatibleMysql() bool { 755 return conn.dmConnector.compatibleMode == COMPATIBLE_MODE_MYSQL 756 } 757 758 func (conn *DmConnection) cancel(err error) { 759 conn.canceled.Set(err) 760 fmt.Println(conn.close()) 761 } 762 763 func (conn *DmConnection) finish() { 764 if !conn.watching || conn.finished == nil { 765 return 766 } 767 select { 768 case conn.finished <- struct{}{}: 769 conn.watching = false 770 case <-conn.closech: 771 } 772 } 773 774 func (conn *DmConnection) startWatcher() { 775 watcher := make(chan context.Context, 1) 776 conn.watcher = watcher 777 finished := make(chan struct{}) 778 conn.finished = finished 779 go func() { 780 for { 781 var ctx context.Context 782 select { 783 case ctx = <-watcher: 784 case <-conn.closech: 785 return 786 } 787 788 select { 789 case <-ctx.Done(): 790 conn.cancel(ctx.Err()) 791 case <-finished: 792 case <-conn.closech: 793 return 794 } 795 } 796 }() 797 } 798 799 func (conn *DmConnection) watchCancel(ctx context.Context) error { 800 if conn.watching { 801 802 conn.cleanup() 803 return nil 804 } 805 806 if err := ctx.Err(); err != nil { 807 return err 808 } 809 810 if ctx.Done() == nil { 811 return nil 812 } 813 814 if conn.watcher == nil { 815 return nil 816 } 817 818 conn.watching = true 819 conn.watcher <- ctx 820 return nil 821 } 822 823 type noCopy struct{} 824 825 func (*noCopy) Lock() {} 826 827 type atomicBool struct { 828 _noCopy noCopy 829 value uint32 830 } 831 832 func (ab *atomicBool) IsSet() bool { 833 return atomic.LoadUint32(&ab.value) > 0 834 } 835 836 func (ab *atomicBool) Set(value bool) { 837 if value { 838 atomic.StoreUint32(&ab.value, 1) 839 } else { 840 atomic.StoreUint32(&ab.value, 0) 841 } 842 } 843 844 func (ab *atomicBool) TrySet(value bool) bool { 845 if value { 846 return atomic.SwapUint32(&ab.value, 1) == 0 847 } 848 return atomic.SwapUint32(&ab.value, 0) > 0 849 } 850 851 type atomicError struct { 852 _noCopy noCopy 853 value atomic.Value 854 } 855 856 func (ae *atomicError) Set(value error) { 857 ae.value.Store(value) 858 } 859 860 func (ae *atomicError) Value() error { 861 if v := ae.value.Load(); v != nil { 862 863 return v.(error) 864 } 865 return nil 866 }