github.com/decred/politeia@v1.4.0/politeiawww/legacy/cmsdatabase/cockroachdb/cockroachdb.go (about) 1 // Copyright (c) 2017-2020 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package cockroachdb 6 7 import ( 8 "errors" 9 "fmt" 10 "net/url" 11 "path/filepath" 12 "sync" 13 "time" 14 15 v1 "github.com/decred/politeia/politeiawww/api/cms/v1" 16 database "github.com/decred/politeia/politeiawww/legacy/cmsdatabase" 17 "github.com/jinzhu/gorm" 18 _ "github.com/jinzhu/gorm/dialects/postgres" 19 ) 20 21 const ( 22 cacheID = "cms" 23 cmsVersion = "2" 24 25 // Database table names 26 tableNameVersions = "versions" 27 tableNameInvoice = "invoices" 28 tableNameLineItem = "line_items" 29 tableNameInvoiceChange = "invoice_changes" 30 tableNameExchangeRate = "exchange_rates" 31 tableNamePayments = "payments" 32 tableNameDCC = "dcc" 33 34 userPoliteiawww = "politeiawww" // cmsdb user (read/write access) 35 ) 36 37 // cockroachdb implements the cache interface. 38 type cockroachdb struct { 39 sync.RWMutex 40 shutdown bool // Backend is shutdown 41 recordsdb *gorm.DB // Database context 42 } 43 44 // Create new invoice. 45 // 46 // CreateInvoice satisfies the database interface. 47 func (c *cockroachdb) NewInvoice(dbInvoice *database.Invoice) error { 48 invoice := EncodeInvoice(dbInvoice) 49 50 log.Debugf("CreateInvoice: %v", invoice.Token) 51 return c.recordsdb.Create(invoice).Error 52 } 53 54 // Update existing invoice. 55 // 56 // UpdateInvoice satisfies the database interface. 57 func (c *cockroachdb) UpdateInvoice(dbInvoice *database.Invoice) error { 58 invoice := EncodeInvoice(dbInvoice) 59 60 log.Debugf("UpdateInvoice: %v", invoice.Token) 61 62 return c.recordsdb.Save(invoice).Error 63 } 64 65 // RemoveLineItem deletes an existing invoice line items from the database. 66 func (c *cockroachdb) RemoveInvoiceLineItems(invoiceToken string) error { 67 log.Debugf("RemoveInvoiceLineItems: %v", invoiceToken) 68 69 return c.recordsdb.Where("invoice_token = ?", invoiceToken).Delete(&LineItem{}).Error 70 } 71 72 // Return all invoices by userid 73 func (c *cockroachdb) InvoicesByUserID(userid string) ([]database.Invoice, error) { 74 log.Tracef("InvoicesByUserID") 75 76 // Lookup the latest version of each invoice 77 query := `SELECT * 78 FROM invoices a 79 LEFT OUTER JOIN invoices b 80 ON a.token = b.token 81 AND a.version < b.version 82 WHERE b.token IS NULL 83 AND a.user_id = ?` 84 rows, err := c.recordsdb.Raw(query, userid).Rows() 85 if err != nil { 86 return nil, err 87 } 88 defer rows.Close() 89 90 invoices := make([]Invoice, 0, 1024) // PNOOMA 91 for rows.Next() { 92 var i Invoice 93 err := c.recordsdb.ScanRows(rows, &i) 94 if err != nil { 95 return nil, err 96 } 97 invoices = append(invoices, i) 98 } 99 if err = rows.Err(); err != nil { 100 return nil, err 101 } 102 103 keys := make([]string, 0, len(invoices)) 104 for _, r := range invoices { 105 keys = append(keys, r.Key) 106 } 107 err = c.recordsdb. 108 Preload("LineItems"). 109 Preload("Changes"). 110 Preload("Payments"). 111 Where(keys). 112 Find(&invoices). 113 Error 114 if err != nil { 115 return nil, err 116 } 117 118 dbInvoices := make([]database.Invoice, 0, len(invoices)) 119 for _, v := range invoices { 120 dbInvoice, err := DecodeInvoice(&v) 121 if err != nil { 122 return nil, err 123 } 124 dbInvoices = append(dbInvoices, *dbInvoice) 125 } 126 return dbInvoices, nil 127 } 128 129 // InvoiceByToken Return invoice by its token. 130 func (c *cockroachdb) InvoiceByToken(token string) (*database.Invoice, error) { 131 log.Debugf("InvoiceByToken: %v", token) 132 133 invoice := Invoice{} 134 err := c.recordsdb. 135 Where("token = ?", token). 136 Order("version desc"). 137 Limit(1). 138 Preload("LineItems"). 139 Preload("Changes"). 140 Preload("Payments"). 141 Find(&invoice).Error 142 if err != nil { 143 if errors.Is(err, gorm.ErrRecordNotFound) { 144 err = database.ErrInvoiceNotFound 145 } 146 return nil, err 147 } 148 149 return DecodeInvoice(&invoice) 150 } 151 152 // InvoiceByKey Return invoice by its key. 153 func (c *cockroachdb) InvoiceByKey(key string) (*database.Invoice, error) { 154 log.Debugf("InvoiceByKey: %v", key) 155 156 invoice := Invoice{} 157 err := c.recordsdb. 158 Where("key = ?", key). 159 Order("version desc"). 160 Limit(1). 161 Preload("LineItems"). 162 Preload("Changes"). 163 Preload("Payments"). 164 Find(&invoice).Error 165 if err != nil { 166 if errors.Is(err, gorm.ErrRecordNotFound) { 167 err = database.ErrInvoiceNotFound 168 } 169 return nil, err 170 } 171 172 return DecodeInvoice(&invoice) 173 } 174 175 // InvoiceByTokenVersion Return invoice by its token and version 176 func (c *cockroachdb) InvoiceByTokenVersion(token string, version string) (*database.Invoice, error) { 177 log.Debugf("InvoiceByTokenVersion: %v", token) 178 179 invoice := Invoice{} 180 err := c.recordsdb. 181 Where("key = ?", token+version). 182 Preload("LineItems"). 183 Preload("Changes"). 184 Preload("Payments"). 185 Find(&invoice).Error 186 if err != nil { 187 if errors.Is(err, gorm.ErrRecordNotFound) { 188 err = database.ErrInvoiceNotFound 189 } 190 return nil, err 191 } 192 193 return DecodeInvoice(&invoice) 194 } 195 196 // InvoicesByMonthYearStatus returns all invoices by month year and status 197 func (c *cockroachdb) InvoicesByMonthYearStatus(month, year uint16, status int) ([]database.Invoice, error) { 198 log.Tracef("InvoicesByMonthYearStatus") 199 200 // Lookup the latest version of each invoice 201 query := `SELECT * 202 FROM invoices a 203 LEFT OUTER JOIN invoices b 204 ON a.token = b.token 205 AND a.version < b.version 206 WHERE b.token IS NULL 207 AND month = ? AND year = ? AND status = ?` 208 rows, err := c.recordsdb.Raw(query, month, year, status).Rows() 209 if err != nil { 210 return nil, err 211 } 212 defer rows.Close() 213 214 invoices := make([]Invoice, 0, 1024) // PNOOMA 215 for rows.Next() { 216 var i Invoice 217 err := c.recordsdb.ScanRows(rows, &i) 218 if err != nil { 219 return nil, err 220 } 221 invoices = append(invoices, i) 222 } 223 if err = rows.Err(); err != nil { 224 return nil, err 225 } 226 keys := make([]string, 0, len(invoices)) 227 for _, r := range invoices { 228 keys = append(keys, r.Key) 229 } 230 err = c.recordsdb. 231 Preload("LineItems"). 232 Preload("Changes"). 233 Preload("Payments"). 234 Where(keys). 235 Find(&invoices). 236 Error 237 if err != nil { 238 return nil, err 239 } 240 241 dbInvoices := make([]database.Invoice, 0, len(invoices)) 242 for _, v := range invoices { 243 dbInvoice, err := DecodeInvoice(&v) 244 if err != nil { 245 return nil, err 246 } 247 dbInvoices = append(dbInvoices, *dbInvoice) 248 } 249 return dbInvoices, nil 250 } 251 252 // InvoicesByMonthYear returns all invoices by month/year 253 func (c *cockroachdb) InvoicesByMonthYear(month, year uint16) ([]database.Invoice, error) { 254 log.Tracef("InvoicesByMonthYear") 255 256 // Lookup the latest version of each invoice 257 query := `SELECT * 258 FROM invoices a 259 LEFT OUTER JOIN invoices b 260 ON a.token = b.token 261 AND a.version < b.version 262 WHERE b.token IS NULL 263 AND a.month = ? AND a.year = ?` 264 rows, err := c.recordsdb.Raw(query, month, year).Rows() 265 if err != nil { 266 return nil, err 267 } 268 defer rows.Close() 269 270 invoices := make([]Invoice, 0, 1024) // PNOOMA 271 for rows.Next() { 272 var i Invoice 273 err := c.recordsdb.ScanRows(rows, &i) 274 if err != nil { 275 return nil, err 276 } 277 invoices = append(invoices, i) 278 } 279 if err = rows.Err(); err != nil { 280 return nil, err 281 } 282 283 keys := make([]string, 0, len(invoices)) 284 for _, r := range invoices { 285 keys = append(keys, r.Key) 286 } 287 err = c.recordsdb. 288 Preload("LineItems"). 289 Preload("Changes"). 290 Preload("Payments"). 291 Where(keys). 292 Find(&invoices). 293 Error 294 if err != nil { 295 return nil, err 296 } 297 298 dbInvoices := make([]database.Invoice, 0, len(invoices)) 299 for _, v := range invoices { 300 dbInvoice, err := DecodeInvoice(&v) 301 if err != nil { 302 return nil, err 303 } 304 dbInvoices = append(dbInvoices, *dbInvoice) 305 } 306 return dbInvoices, nil 307 } 308 309 // InvoicesByStatus returns all invoices by status 310 func (c *cockroachdb) InvoicesByStatus(status int) ([]database.Invoice, error) { 311 log.Tracef("InvoicesByStatus") 312 313 // Lookup the latest version of each invoice 314 query := `SELECT * 315 FROM invoices a 316 LEFT OUTER JOIN invoices b 317 ON a.token = b.token 318 AND a.version < b.version 319 WHERE a.status = ?` 320 rows, err := c.recordsdb.Raw(query, status).Rows() 321 if err != nil { 322 return nil, err 323 } 324 defer rows.Close() 325 326 invoices := make([]Invoice, 0, 1024) // PNOOMA 327 for rows.Next() { 328 var i Invoice 329 err := c.recordsdb.ScanRows(rows, &i) 330 if err != nil { 331 return nil, err 332 } 333 invoices = append(invoices, i) 334 } 335 if err = rows.Err(); err != nil { 336 return nil, err 337 } 338 339 // Compile a list of record primary keys 340 keys := make([]string, 0, len(invoices)) 341 for _, v := range invoices { 342 keys = append(keys, v.Key) 343 } 344 345 // Lookup files and metadata streams for each of the 346 // previously queried records. 347 err = c.recordsdb. 348 Preload("LineItems"). 349 Preload("Changes"). 350 Preload("Payments"). 351 Where(keys). 352 Find(&invoices). 353 Error 354 if err != nil { 355 return nil, err 356 } 357 dbInvoices := make([]database.Invoice, 0, len(invoices)) 358 for _, v := range invoices { 359 dbInvoice, err := DecodeInvoice(&v) 360 if err != nil { 361 return nil, err 362 } 363 dbInvoices = append(dbInvoices, *dbInvoice) 364 } 365 return dbInvoices, nil 366 } 367 368 // InvoicesAll returns all invoices 369 func (c *cockroachdb) InvoicesAll() ([]database.Invoice, error) { 370 // Lookup the latest version of each invoice 371 query := `SELECT * 372 FROM invoices a 373 LEFT OUTER JOIN invoices b 374 ON a.token = b.token 375 AND a.version < b.version` 376 rows, err := c.recordsdb.Raw(query).Rows() 377 if err != nil { 378 return nil, err 379 } 380 defer rows.Close() 381 382 invoices := make([]Invoice, 0, 1024) // PNOOMA 383 for rows.Next() { 384 var i Invoice 385 err := c.recordsdb.ScanRows(rows, &i) 386 if err != nil { 387 return nil, err 388 } 389 invoices = append(invoices, i) 390 } 391 if err = rows.Err(); err != nil { 392 return nil, err 393 } 394 395 // Compile a list of record primary keys 396 keys := make([]string, 0, len(invoices)) 397 for _, v := range invoices { 398 keys = append(keys, v.Key) 399 } 400 401 // Lookup files and metadata streams for each of the 402 // previously queried records. 403 err = c.recordsdb. 404 Preload("LineItems"). 405 Preload("Changes"). 406 Preload("Payments"). 407 Where(keys). 408 Find(&invoices). 409 Error 410 if err != nil { 411 return nil, err 412 } 413 414 dbInvoices := make([]database.Invoice, 0, len(invoices)) 415 for _, v := range invoices { 416 dbInvoice, err := DecodeInvoice(&v) 417 if err != nil { 418 return nil, err 419 } 420 dbInvoices = append(dbInvoices, *dbInvoice) 421 } 422 return dbInvoices, nil 423 } 424 425 // InvoicesByAddress return invoices by its payment address. 426 func (c *cockroachdb) InvoicesByAddress(address string) ([]database.Invoice, error) { 427 log.Debugf("InvoiceByAddress: %v", address) 428 429 // Lookup the latest version of each invoice 430 query := `SELECT * 431 FROM invoices a 432 LEFT OUTER JOIN invoices b 433 ON a.token = b.token 434 AND a.version < b.version 435 WHERE b.token IS NULL 436 AND a.payment_address = ?` 437 rows, err := c.recordsdb.Raw(query, address).Rows() 438 if err != nil { 439 return nil, err 440 } 441 defer rows.Close() 442 443 invoices := make([]Invoice, 0, 1024) // PNOOMA 444 for rows.Next() { 445 var i Invoice 446 err := c.recordsdb.ScanRows(rows, &i) 447 if err != nil { 448 return nil, err 449 } 450 invoices = append(invoices, i) 451 } 452 if err = rows.Err(); err != nil { 453 return nil, err 454 } 455 456 // Compile a list of record primary keys 457 keys := make([]string, 0, len(invoices)) 458 for _, v := range invoices { 459 keys = append(keys, v.Key) 460 } 461 462 // Lookup files and metadata streams for each of the 463 // previously queried records. 464 err = c.recordsdb. 465 Preload("LineItems"). 466 Preload("Changes"). 467 Preload("Payments"). 468 Where(keys). 469 Find(&invoices). 470 Error 471 if err != nil { 472 return nil, err 473 } 474 475 dbInvoices := make([]database.Invoice, 0, len(invoices)) 476 for _, v := range invoices { 477 dbInvoice, err := DecodeInvoice(&v) 478 if err != nil { 479 return nil, err 480 } 481 dbInvoices = append(dbInvoices, *dbInvoice) 482 } 483 484 return dbInvoices, nil 485 } 486 487 // Create new exchange rate. 488 // 489 // NewExchangeRate satisfies the database interface. 490 func (c *cockroachdb) NewExchangeRate(dbExchangeRate *database.ExchangeRate) error { 491 exchRate := encodeExchangeRate(dbExchangeRate) 492 493 log.Debugf("NewExchangeRate: %v %v", exchRate.Month, exchRate.Year) 494 return c.recordsdb.Create(exchRate).Error 495 } 496 497 // ExchangeRate returns exchange rate by month/year 498 func (c *cockroachdb) ExchangeRate(month, year int) (*database.ExchangeRate, error) { 499 log.Tracef("ExchangeRate") 500 501 exchangeRate := ExchangeRate{} 502 err := c.recordsdb. 503 Where("month = ? AND year = ?", month, year). 504 Find(&exchangeRate). 505 Error 506 if err != nil { 507 if errors.Is(err, gorm.ErrRecordNotFound) { 508 err = database.ErrExchangeRateNotFound 509 } 510 return nil, err 511 } 512 513 return decodeExchangeRate(exchangeRate), nil 514 } 515 516 // InvoicesByDateRangeStatus takes a start and end time (in Unix seconds) and returns 517 // all invoices with the included status. This uses the 518 // invoice_changes table to discover the token to look up the correct line items. 519 func (c *cockroachdb) InvoicesByDateRangeStatus(start, end int64, status int) ([]database.Invoice, error) { 520 log.Debugf("InvoicesByDateRangeStatus: %v %v", time.Unix(start, 0), 521 time.Unix(end, 0)) 522 // Get all invoice changes of given status within date range. 523 invoiceChanges := make([]InvoiceChange, 0, 1024) // PNOOMA 524 err := c.recordsdb. 525 Where("new_status = ? AND "+ 526 "timestamp BETWEEN ? AND ?", 527 status, 528 time.Unix(start, 0), 529 time.Unix(end, 0)). 530 Find(&invoiceChanges). 531 Error 532 if err != nil { 533 return nil, err 534 } 535 tokens := make([]string, 0, len(invoiceChanges)) 536 for _, r := range invoiceChanges { 537 tokens = append(tokens, r.InvoiceToken) 538 } 539 invoices := make([]Invoice, 0, len(tokens)) 540 // Using all invoice tokens from the results of the query above, ask for all 541 // invoices that match those tokens. 542 dbInvoices := make([]database.Invoice, 0, len(tokens)) 543 err = c.recordsdb. 544 Preload("LineItems"). 545 Preload("Payments"). 546 Preload("Changes"). 547 Where(tokens). 548 Find(&invoices). 549 Error 550 if err != nil { 551 return nil, err 552 } 553 for _, vv := range invoices { 554 inv, err := DecodeInvoice(&vv) 555 if err != nil { 556 return nil, err 557 } 558 dbInvoices = append(dbInvoices, *inv) 559 } 560 return dbInvoices, nil 561 } 562 563 // InvoicesByDateRange takes a start and end time (in Unix seconds) and returns 564 // all invoices. This uses the 565 // invoice_changes table to discover the token to look up the correct line items. 566 func (c *cockroachdb) InvoicesByDateRange(start, end int64) ([]database.Invoice, error) { 567 log.Debugf("InvoicesByDateRange: %v %v", time.Unix(start, 0), 568 time.Unix(end, 0)) 569 // Get all invoice changes of NEW status within date range. 570 invoiceChanges := make([]InvoiceChange, 0, 1024) // PNOOMA 571 err := c.recordsdb. 572 Where("new_status = ? AND"+ 573 "timestamp BETWEEN ? AND ?", 574 v1.InvoiceStatusNew, 575 time.Unix(start, 0), 576 time.Unix(end, 0)). 577 Find(&invoiceChanges). 578 Error 579 if err != nil { 580 return nil, err 581 } 582 tokens := make([]string, 0, len(invoiceChanges)) 583 for _, r := range invoiceChanges { 584 tokens = append(tokens, r.InvoiceToken) 585 } 586 invoices := make([]Invoice, 0, len(tokens)) 587 // Using all invoice tokens from the results of the query above, ask for all 588 // invoices that match those tokens. 589 dbInvoices := make([]database.Invoice, 0, len(tokens)) 590 err = c.recordsdb. 591 Preload("LineItems"). 592 Preload("Changes"). 593 Preload("Payments"). 594 Where(tokens). 595 Find(&invoices). 596 Error 597 if err != nil { 598 return nil, err 599 } 600 for _, vv := range invoices { 601 inv, err := DecodeInvoice(&vv) 602 if err != nil { 603 return nil, err 604 } 605 dbInvoices = append(dbInvoices, *inv) 606 } 607 return dbInvoices, nil 608 } 609 610 // InvoicesByLineItemsProposalToken takes a proposal token as an argument and 611 // returns all invoices that have line items corresponding with that token. 612 // All line items that are not considered relevant to the proposal token will 613 // be omitted. 614 func (c *cockroachdb) InvoicesByLineItemsProposalToken(token string) ([]database.Invoice, error) { 615 log.Debugf("InvoicesByLineItemsProposalToken: %v", token) 616 // Get all line items from approved, new, updated or paid invoices based 617 // on the provided proposal token 618 query := `SELECT 619 invoices.month, 620 invoices.year, 621 invoices.user_id, 622 invoices.public_key, 623 invoices.contractor_rate, 624 invoices.exchange_rate, 625 line_items.invoice_token, 626 line_items.type, 627 line_items.domain, 628 line_items.subdomain, 629 line_items.description, 630 line_items.proposal_url, 631 line_items.labor, 632 line_items.expenses, 633 line_items.contractor_rate AS sub_rate, 634 line_items.sub_user_id as sub_user 635 FROM invoices 636 LEFT OUTER JOIN invoices b 637 ON invoices.token = b.token 638 AND invoices.version < b.version 639 INNER JOIN line_items 640 ON invoices.key = line_items.invoice_key 641 WHERE line_items.proposal_url = ? AND ( 642 invoices.status = ? OR 643 invoices.status = ? OR 644 invoices.status = ? OR 645 invoices.status = ?)` 646 rows, err := c.recordsdb.Raw(query, token, 647 int(v1.InvoiceStatusNew), 648 int(v1.InvoiceStatusUpdated), 649 int(v1.InvoiceStatusApproved), 650 int(v1.InvoiceStatusPaid), 651 ).Rows() 652 if err != nil { 653 return nil, err 654 } 655 defer rows.Close() 656 657 matching := make([]MatchingLineItems, 0, 1024) 658 for rows.Next() { 659 var i MatchingLineItems 660 err := c.recordsdb.ScanRows(rows, &i) 661 if err != nil { 662 return nil, err 663 } 664 matching = append(matching, i) 665 } 666 if err = rows.Err(); err != nil { 667 return nil, err 668 } 669 return convertMatchingLineItemToInvoices(matching), nil 670 } 671 672 // MatchingLineItems is a type used for finding matched line items based on 673 // proposal ownership. 674 type MatchingLineItems struct { 675 InvoiceToken string 676 UserID string 677 Month uint 678 Year uint 679 Type uint 680 Domain string 681 Subdomain string 682 Description string 683 ProposalURL string 684 Labor uint 685 Expenses uint 686 ContractorRate uint 687 PublicKey string 688 ExchangeRate uint 689 SubRate uint 690 SubUser string 691 } 692 693 // Close satisfies the database interface. 694 func (c *cockroachdb) Close() error { 695 return c.recordsdb.Close() 696 } 697 698 // This function must be called within a transaction. 699 func createCmsTables(tx *gorm.DB) error { 700 log.Tracef("createCmsTables") 701 702 // Create cms tables 703 if !tx.HasTable(tableNameInvoice) { 704 err := tx.CreateTable(&Invoice{}).Error 705 if err != nil { 706 return err 707 } 708 } 709 if !tx.HasTable(tableNameLineItem) { 710 err := tx.CreateTable(&LineItem{}).Error 711 if err != nil { 712 return err 713 } 714 } 715 if !tx.HasTable(tableNameInvoiceChange) { 716 err := tx.CreateTable(&InvoiceChange{}).Error 717 if err != nil { 718 return err 719 } 720 } 721 if !tx.HasTable(tableNameExchangeRate) { 722 err := tx.CreateTable(&ExchangeRate{}).Error 723 if err != nil { 724 return err 725 } 726 } 727 if !tx.HasTable(tableNamePayments) { 728 err := tx.CreateTable(&Payments{}).Error 729 if err != nil { 730 return err 731 } 732 } 733 if !tx.HasTable(tableNameDCC) { 734 err := tx.CreateTable(&DCC{}).Error 735 if err != nil { 736 return err 737 } 738 } 739 if !tx.HasTable(tableNameVersions) { 740 err := tx.CreateTable(&Version{}).Error 741 if err != nil { 742 return err 743 } 744 } 745 746 var v Version 747 err := tx.Where("id = ?", cacheID). 748 Find(&v). 749 Error 750 if errors.Is(err, gorm.ErrRecordNotFound) { 751 err = tx.Create( 752 &Version{ 753 ID: cacheID, 754 Version: cmsVersion, 755 Timestamp: time.Now().Unix(), 756 }).Error 757 return err 758 } 759 return nil 760 } 761 762 // Setup calls the tables creation function to ensure the database is prepared for use. 763 func (c *cockroachdb) Setup() error { 764 tx := c.recordsdb.Begin() 765 err := createCmsTables(tx) 766 if err != nil { 767 tx.Rollback() 768 return err 769 } 770 771 return tx.Commit().Error 772 } 773 774 func (c *cockroachdb) dropTables(tx *gorm.DB) error { 775 // Drop record tables 776 err := tx.DropTableIfExists(tableNameInvoice, tableNameInvoiceChange, 777 tableNameLineItem, tableNamePayments, tableNameDCC).Error 778 if err != nil { 779 return err 780 } 781 782 // Remove cms version record 783 return tx.Delete(&Version{ 784 ID: cacheID, 785 }).Error 786 } 787 788 // build the records cache using the passed in records. 789 // 790 // This function cannot be called using a transaction because it could 791 // potentially exceed cockroachdb's transaction size limit. 792 func (c *cockroachdb) build(invoices []Invoice, dccs []DCC) error { 793 log.Tracef("build") 794 795 // Drop record tables 796 tx := c.recordsdb.Begin() 797 err := c.dropTables(tx) 798 if err != nil { 799 tx.Rollback() 800 return fmt.Errorf("drop tables: %v", err) 801 } 802 err = tx.Commit().Error 803 if err != nil { 804 return err 805 } 806 807 // Create record tables 808 tx = c.recordsdb.Begin() 809 err = createCmsTables(tx) 810 if err != nil { 811 tx.Rollback() 812 return fmt.Errorf("create tables: %v", err) 813 } 814 err = tx.Commit().Error 815 if err != nil { 816 return err 817 } 818 819 for _, i := range invoices { 820 err := c.recordsdb.Create(&i).Error 821 if err != nil { 822 return fmt.Errorf("create invoice: %v", err) 823 } 824 } 825 826 for _, d := range dccs { 827 err := c.recordsdb.Create(&d).Error 828 if err != nil { 829 return fmt.Errorf("create dcc: %v", err) 830 } 831 } 832 833 return nil 834 } 835 836 // Build drops all existing tables from the records cache, recreates them, then 837 // builds the records cache using the passed in records. 838 func (c *cockroachdb) Build(dbInvs []database.Invoice, dbDCCs []database.DCC) error { 839 log.Tracef("Build") 840 841 c.Lock() 842 defer c.Unlock() 843 844 if c.shutdown { 845 return database.ErrShutdown 846 } 847 848 log.Infof("Building records cache") 849 invoices := make([]Invoice, 0, len(dbInvs)) 850 for _, dbInv := range dbInvs { 851 inv := EncodeInvoice(&dbInv) 852 invoices = append(invoices, *inv) 853 } 854 dccs := make([]DCC, 0, len(dbDCCs)) 855 for _, dbDCC := range dbDCCs { 856 dcc := encodeDCC(&dbDCC) 857 dccs = append(dccs, *dcc) 858 } 859 // Build the records cache. This is not run using a 860 // transaction because it could potentially exceed 861 // cockroachdb's transaction size limit. 862 err := c.build(invoices, dccs) 863 if err != nil { 864 // Remove the version record. This will 865 // force a rebuild on the next start up. 866 err1 := c.recordsdb.Delete(&Version{ 867 ID: cacheID, 868 }).Error 869 if err1 != nil { 870 panic("the cache is out of sync and will not rebuild" + 871 "automatically; a rebuild must be forced") 872 } 873 } 874 875 return err 876 } 877 878 func buildQueryString(user, rootCert, cert, key string) string { 879 v := url.Values{} 880 v.Set("sslmode", "require") 881 v.Set("sslrootcert", filepath.Clean(rootCert)) 882 v.Set("sslcert", filepath.Join(cert)) 883 v.Set("sslkey", filepath.Join(key)) 884 return v.Encode() 885 } 886 887 // New returns a new cockroachdb context that contains a connection to the 888 // specified database that was made using the politeiawww user and the passed 889 // in certificates. 890 func New(host, net, rootCert, cert, key string) (*cockroachdb, error) { 891 log.Tracef("New: %v %v %v %v %v", host, net, rootCert, cert, key) 892 893 // Connect to database 894 dbName := cacheID + "_" + net 895 h := "postgresql://" + userPoliteiawww + "@" + host + "/" + dbName 896 u, err := url.Parse(h) 897 if err != nil { 898 return nil, fmt.Errorf("parse url '%v': %v", h, err) 899 } 900 901 qs := buildQueryString(u.User.String(), rootCert, cert, key) 902 addr := u.String() + "?" + qs 903 db, err := gorm.Open("postgres", addr) 904 if err != nil { 905 return nil, fmt.Errorf("connect to database '%v': %v", addr, err) 906 } 907 908 // Create context 909 c := &cockroachdb{ 910 recordsdb: db, 911 } 912 913 // Disable gorm logging. This prevents duplicate errors from 914 // being printed since we handle errors manually. 915 c.recordsdb.LogMode(false) 916 917 // Disable automatic table name pluralization. We set table 918 // names manually. 919 c.recordsdb.SingularTable(true) 920 921 // Return an error if the version record is not found or 922 // if there is a version mismatch, but also return the 923 // cache context so that the cache can be built/rebuilt. 924 if !c.recordsdb.HasTable(tableNameVersions) { 925 log.Debugf("table '%v' does not exist", tableNameVersions) 926 return c, database.ErrNoVersionRecord 927 } 928 929 var v Version 930 err = c.recordsdb. 931 Where("id = ?", cacheID). 932 Find(&v). 933 Error 934 if errors.Is(err, gorm.ErrRecordNotFound) { 935 log.Debugf("version record not found for ID '%v'", cacheID) 936 err = database.ErrNoVersionRecord 937 } else if v.Version != cmsVersion { 938 log.Debugf("version mismatch for ID '%v': got %v, want %v", 939 cacheID, v.Version, cmsVersion) 940 err = database.ErrWrongVersion 941 } 942 943 return c, err 944 } 945 946 // Update existing payment. 947 // 948 // UpdatePayments satisfies the database interface. 949 func (c *cockroachdb) UpdatePayments(dbPayments *database.Payments) error { 950 payments := encodePayments(dbPayments) 951 952 log.Debugf("UpdatePayments: %v", payments.InvoiceKey) 953 954 return c.recordsdb.Save(&payments).Error 955 } 956 957 // PaymentsByAddress returns payments row that has the matching Address. 958 func (c *cockroachdb) PaymentsByAddress(address string) (*database.Payments, error) { 959 log.Debugf("PaymentsByAddress: %v", address) 960 961 payments := Payments{} 962 err := c.recordsdb. 963 Where("address = ?", address). 964 Find(&payments).Error 965 if err != nil { 966 if errors.Is(err, gorm.ErrRecordNotFound) { 967 err = database.ErrInvoiceNotFound 968 } 969 return nil, err 970 } 971 dbPayments := decodePayment(&payments) 972 return &dbPayments, nil 973 } 974 975 // PaymentsByStatus returns all payments rows that match the given status. 976 func (c *cockroachdb) PaymentsByStatus(status uint) ([]database.Payments, error) { 977 log.Debugf("PaymentsByStatus: %v", status) 978 979 payments := make([]Payments, 0, 1048) 980 err := c.recordsdb. 981 Where("status = ?", status). 982 Find(&payments). 983 Error 984 if err != nil { 985 if errors.Is(err, gorm.ErrRecordNotFound) { 986 err = database.ErrInvoiceNotFound 987 } 988 return nil, err 989 } 990 dbPayments := make([]database.Payments, 0, 1048) 991 for _, v := range payments { 992 dbPayments = append(dbPayments, decodePayment(&v)) 993 } 994 return dbPayments, nil 995 } 996 997 // Create new dcc. 998 // 999 // NewDCC satisfies the database interface. 1000 func (c *cockroachdb) NewDCC(dbDCC *database.DCC) error { 1001 dcc := encodeDCC(dbDCC) 1002 1003 log.Debugf("NewDCC: %v", dcc.Token) 1004 return c.recordsdb.Create(dcc).Error 1005 } 1006 1007 // Update existing dcc. 1008 // 1009 // UpdateDCC satisfies the database interface. 1010 func (c *cockroachdb) UpdateDCC(dbDCC *database.DCC) error { 1011 dcc := encodeDCC(dbDCC) 1012 1013 log.Debugf("UpdateDCC: %v", dcc.Token) 1014 1015 return c.recordsdb.Save(dcc).Error 1016 } 1017 1018 // DCCByToken Return DCC by its token. 1019 func (c *cockroachdb) DCCByToken(token string) (*database.DCC, error) { 1020 log.Debugf("DCCByToken: %v", token) 1021 1022 dcc := DCC{ 1023 Token: token, 1024 } 1025 err := c.recordsdb. 1026 Find(&dcc).Error 1027 if err != nil { 1028 if errors.Is(err, gorm.ErrRecordNotFound) { 1029 err = database.ErrDCCNotFound 1030 } 1031 return nil, err 1032 } 1033 1034 return decodeDCC(&dcc), nil 1035 } 1036 1037 // DCCsByStatus Return DCCs by status. 1038 func (c *cockroachdb) DCCsByStatus(status int) ([]*database.DCC, error) { 1039 log.Debugf("DCCsByStatus: %v", status) 1040 1041 dccs := make([]DCC, 0, 1048) 1042 err := c.recordsdb. 1043 Where("status = ?", status). 1044 Find(&dccs). 1045 Error 1046 if err != nil { 1047 if errors.Is(err, gorm.ErrRecordNotFound) { 1048 err = database.ErrDCCNotFound 1049 } 1050 return nil, err 1051 } 1052 1053 dbDCCs := make([]*database.DCC, 0, 1048) 1054 for _, v := range dccs { 1055 dbDCCs = append(dbDCCs, decodeDCC(&v)) 1056 } 1057 return dbDCCs, nil 1058 } 1059 1060 // DCCsAll Returns all DCCs regardless of status. 1061 func (c *cockroachdb) DCCsAll() ([]*database.DCC, error) { 1062 log.Debugf("DCCsAll:") 1063 1064 dccs := make([]DCC, 0, 1048) 1065 err := c.recordsdb. 1066 Find(&dccs). 1067 Error 1068 if err != nil { 1069 if errors.Is(err, gorm.ErrRecordNotFound) { 1070 err = database.ErrDCCNotFound 1071 } 1072 return nil, err 1073 } 1074 1075 dbDCCs := make([]*database.DCC, 0, 1048) 1076 for _, v := range dccs { 1077 dbDCCs = append(dbDCCs, decodeDCC(&v)) 1078 } 1079 return dbDCCs, nil 1080 }