pquery.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package pquery
  2. import (
  3. "database/sql"
  4. "strings"
  5. "sync"
  6. log "github.com/sirupsen/logrus"
  7. "github.com/xyproto/algernon/lua/convert"
  8. lua "github.com/xyproto/gopher-lua"
  9. // Using the PostgreSQL database engine
  10. _ "github.com/lib/pq"
  11. )
  12. const (
  13. defaultQuery = "SELECT version()"
  14. defaultConnectionString = "host=localhost port=5432 user=postgres dbname=test sslmode=disable"
  15. )
  16. var (
  17. // global map from connection string to database connection, to reuse connections, protected by a mutex
  18. reuseDB = make(map[string]*sql.DB)
  19. reuseMut = &sync.RWMutex{}
  20. )
  21. // Load makes functions related to building a library of Lua code available
  22. func Load(L *lua.LState) {
  23. // Register the PQ function
  24. L.SetGlobal("PQ", L.NewFunction(func(L *lua.LState) int {
  25. // Check if the optional argument is given
  26. query := defaultQuery
  27. if L.GetTop() >= 1 {
  28. query = L.ToString(1)
  29. if query == "" {
  30. query = defaultQuery
  31. }
  32. }
  33. connectionString := defaultConnectionString
  34. if L.GetTop() >= 2 {
  35. connectionString = L.ToString(2)
  36. }
  37. // Check if there is a connection that can be reused
  38. var db *sql.DB
  39. reuseMut.RLock()
  40. conn, ok := reuseDB[connectionString]
  41. reuseMut.RUnlock()
  42. if ok {
  43. // It exists, but is it still alive?
  44. err := conn.Ping()
  45. if err != nil {
  46. // no
  47. // log.Info("did not reuse the connection")
  48. reuseMut.Lock()
  49. delete(reuseDB, connectionString)
  50. reuseMut.Unlock()
  51. } else {
  52. // yes
  53. // log.Info("reused the connection")
  54. db = conn
  55. }
  56. }
  57. // Create a new connection, if needed
  58. var err error
  59. if db == nil {
  60. db, err = sql.Open("postgres", connectionString)
  61. if err != nil {
  62. log.Error("Could not connect to database using " + connectionString + ": " + err.Error())
  63. return 0 // No results
  64. }
  65. // Save the connection for later
  66. reuseMut.Lock()
  67. reuseDB[connectionString] = db
  68. reuseMut.Unlock()
  69. }
  70. // log.Info(fmt.Sprintf("PostgreSQL database: %v (%T)\n", db, db))
  71. reuseMut.Lock()
  72. rows, err := db.Query(query)
  73. reuseMut.Unlock()
  74. if err != nil {
  75. errMsg := err.Error()
  76. if strings.Contains(errMsg, ": connect: connection refused") {
  77. log.Info("PostgreSQL connection string: " + connectionString)
  78. log.Info("PostgreSQL query: " + query)
  79. log.Error("Could not connect to database: " + errMsg)
  80. } else if strings.Contains(errMsg, "missing") && strings.Contains(errMsg, "in connection info string") {
  81. log.Info("PostgreSQL connection string: " + connectionString)
  82. log.Info("PostgreSQL query: " + query)
  83. log.Error(errMsg)
  84. } else {
  85. log.Info("PostgreSQL query: " + query)
  86. log.Error("Query failed: " + errMsg)
  87. }
  88. return 0 // No results
  89. }
  90. if rows == nil {
  91. // Return an empty table
  92. L.Push(L.NewTable())
  93. return 1 // number of results
  94. }
  95. // Return the rows as a table
  96. var (
  97. values []string
  98. value string
  99. )
  100. for rows.Next() {
  101. err = rows.Scan(&value)
  102. if err != nil {
  103. break
  104. }
  105. values = append(values, value)
  106. }
  107. // Convert the strings to a Lua table
  108. table := convert.Strings2table(L, values)
  109. // Return the table
  110. L.Push(table)
  111. return 1 // number of results
  112. }))
  113. }