/model.go

https://github.com/mmcgrana/pgpin · Go · 359 lines · 333 code · 22 blank · 4 comment · 87 complexity · 122ccb9fd3acf87f74ec4fb77c9866fb MD5 · raw file

  1. package main
  2. import (
  3. "code.google.com/p/go-uuid/uuid"
  4. "database/sql"
  5. "github.com/jrallison/go-workers"
  6. _ "github.com/lib/pq"
  7. "regexp"
  8. "time"
  9. )
  10. // Constants.
  11. var DataUuidRegexp = regexp.MustCompilePOSIX("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
  12. // Structs.
  13. type Pin struct {
  14. Id string `json:"id"`
  15. Name string `json:"name"`
  16. DbId string `json:"db_id"`
  17. Query string `json:"query"`
  18. CreatedAt time.Time `json:"created_at"`
  19. UpdatedAt time.Time `json:"updated_at"`
  20. QueryStartedAt *time.Time `json:"query_started_at"`
  21. QueryFinishedAt *time.Time `json:"query_finished_at"`
  22. ResultsFields PgJson `json:"results_fields"`
  23. ResultsRows PgJson `json:"results_rows"`
  24. ResultsError *string `json:"results_error"`
  25. ScheduledAt time.Time `json:"-"`
  26. DeletedAt *time.Time `json:"-"`
  27. Version int `json:"-"`
  28. }
  29. type Db struct {
  30. Id string `json:"id"`
  31. Name string `json:"name"`
  32. Url string `json:"url"`
  33. AddedAt time.Time `json:"created_at"`
  34. UpdatedAt time.Time `json:"updated_at"`
  35. RemovedAt *time.Time `json:"-"`
  36. Version int `json:"-"`
  37. }
  38. // Db operations.
  39. func DbValidate(db *Db) error {
  40. err := ValidateSlug("name", db.Name)
  41. if err != nil {
  42. return err
  43. }
  44. err = ValidatePgUrl("url", db.Url)
  45. if err != nil {
  46. return err
  47. }
  48. sameNamed, err := PgCount("SELECT count(*) FROM dbs WHERE name=$1 and id!=$2 and deleted_at IS NULL", db.Name, db.Id)
  49. if err != nil {
  50. return err
  51. }
  52. if sameNamed > 0 {
  53. return &PgpinError{
  54. Id: "duplicate-db-name",
  55. Message: "name is already used by another db",
  56. HttpStatus: 400,
  57. }
  58. }
  59. return nil
  60. }
  61. func DbList(queryFrag string) ([]*Db, error) {
  62. if queryFrag == "" {
  63. queryFrag = "true"
  64. }
  65. res, err := PgConn.Query("SELECT id, name, url_encrypted, created_at, updated_at, version, deleted_at FROM dbs WHERE deleted_at IS NULL AND " + queryFrag)
  66. if err != nil {
  67. return nil, err
  68. }
  69. defer func() { Must(res.Close()) }()
  70. dbs := []*Db{}
  71. for res.Next() {
  72. db := Db{}
  73. urlEncrypted := make([]byte, 0)
  74. err := res.Scan(&db.Id, &db.Name, &urlEncrypted, &db.AddedAt, &db.UpdatedAt, &db.Version, &db.RemovedAt)
  75. if err != nil {
  76. return nil, err
  77. }
  78. db.Url = FernetDecrypt(urlEncrypted)
  79. dbs = append(dbs, &db)
  80. }
  81. err = res.Err()
  82. if err != nil {
  83. return nil, err
  84. }
  85. return dbs, nil
  86. }
  87. func DbCreate(name string, url string) (*Db, error) {
  88. db := &Db{
  89. Id: uuid.New(),
  90. Name: name,
  91. Url: url,
  92. AddedAt: time.Now(),
  93. UpdatedAt: time.Now(),
  94. RemovedAt: nil,
  95. Version: 1,
  96. }
  97. err := DbValidate(db)
  98. if err == nil {
  99. _, err = PgConn.Exec("INSERT INTO dbs (id, name, url_encrypted, created_at, updated_at, deleted_at, version) VALUES ($1, $2, $3, $4, $5, $6, $7)",
  100. db.Id, db.Name, FernetEncrypt(db.Url), db.AddedAt, db.UpdatedAt, db.RemovedAt, db.Version)
  101. }
  102. return db, err
  103. }
  104. func DbGet(idOrName string) (*Db, error) {
  105. var row *sql.Row
  106. if DataUuidRegexp.MatchString(idOrName) {
  107. query := "SELECT id, name, url_encrypted, created_at, updated_at, version FROM dbs WHERE deleted_at is NULL AND (id=$1 OR name=$2) LIMIT 1"
  108. row = PgConn.QueryRow(query, idOrName, idOrName)
  109. } else {
  110. query := "SELECT id, name, url_encrypted, created_at, updated_at, version FROM dbs WHERE deleted_at is NULL AND name=$1 LIMIT 1"
  111. row = PgConn.QueryRow(query, idOrName)
  112. }
  113. db := Db{}
  114. urlEncrypted := make([]byte, 0)
  115. err := row.Scan(&db.Id, &db.Name, &urlEncrypted, &db.AddedAt, &db.UpdatedAt, &db.Version)
  116. switch {
  117. case err == nil:
  118. db.Url = FernetDecrypt(urlEncrypted)
  119. return &db, nil
  120. case err == sql.ErrNoRows:
  121. return nil, &PgpinError{
  122. Id: "db-not-found",
  123. Message: "db not found",
  124. HttpStatus: 404,
  125. }
  126. default:
  127. return nil, err
  128. }
  129. }
  130. func DbUpdate(db *Db) error {
  131. err := DbValidate(db)
  132. if err != nil {
  133. return err
  134. }
  135. db.UpdatedAt = time.Now()
  136. result, err := PgConn.Exec("UPDATE dbs SET name=$1, url_encrypted=$2, created_at=$3, updated_at=$4, deleted_at=$5, version=$6 WHERE id=$7 AND version=$8",
  137. db.Name, FernetEncrypt(db.Url), db.AddedAt, db.UpdatedAt, db.RemovedAt, db.Version+1, db.Id, db.Version)
  138. if err != nil {
  139. return err
  140. }
  141. rowsAffected, err := result.RowsAffected()
  142. if err != nil {
  143. return err
  144. }
  145. if rowsAffected != 1 {
  146. return &PgpinError{
  147. Id: "db-concurrent-update",
  148. Message: "concurrent db update attempted",
  149. HttpStatus: 400,
  150. }
  151. }
  152. db.Version = db.Version + 1
  153. return nil
  154. }
  155. func DbDelete(id string) (*Db, error) {
  156. db, err := DbGet(id)
  157. if err != nil {
  158. return nil, err
  159. }
  160. numPins, err := PgCount("SELECT count(*) FROM pins WHERE db_id=$1 AND deleted_at IS NULL", db.Id)
  161. if err != nil {
  162. return nil, err
  163. }
  164. if numPins != 0 {
  165. return nil, &PgpinError{
  166. Id: "removing-db-with-pins",
  167. Message: "cannot remove db with pins",
  168. HttpStatus: 400,
  169. }
  170. }
  171. removedAt := time.Now()
  172. db.RemovedAt = &removedAt
  173. err = DbUpdate(db)
  174. return db, err
  175. }
  176. // Pin operations.
  177. func PinValidate(pin *Pin) error {
  178. err := ValidateSlug("name", pin.Name)
  179. if err != nil {
  180. return err
  181. }
  182. err = ValidateNonempty("query", pin.Query)
  183. if err != nil {
  184. return err
  185. }
  186. _, err = DbGet(pin.DbId)
  187. if err != nil {
  188. return err
  189. }
  190. sameNamed, err := PgCount("SELECT count(*) FROM pins WHERE name=$1 AND id!=$2 AND deleted_at IS NULL", pin.Name, pin.Id)
  191. if err != nil {
  192. return err
  193. } else if sameNamed > 0 {
  194. return &PgpinError{
  195. Id: "duplicate-pin-name",
  196. Message: "name is already used by another pin",
  197. HttpStatus: 400,
  198. }
  199. }
  200. return nil
  201. }
  202. func PinList(queryFrag string, queryVals ...interface{}) ([]*Pin, error) {
  203. if queryFrag == "" {
  204. queryFrag = "true"
  205. }
  206. query := "SELECT id, name, db_id, query, created_at, updated_at, query_started_at, query_finished_at, results_fields, results_rows, results_error, scheduled_at, deleted_at, version FROM pins WHERE deleted_at IS NULL AND " + queryFrag
  207. res, err := PgConn.Query(query, queryVals...)
  208. if err != nil {
  209. return nil, err
  210. }
  211. defer func() { Must(res.Close()) }()
  212. pins := []*Pin{}
  213. for res.Next() {
  214. pin := Pin{}
  215. err := res.Scan(&pin.Id, &pin.Name, &pin.DbId, &pin.Query, &pin.CreatedAt, &pin.UpdatedAt, &pin.QueryStartedAt, &pin.QueryFinishedAt, &pin.ResultsFields, &pin.ResultsRows, &pin.ResultsError, &pin.ScheduledAt, &pin.DeletedAt, &pin.Version)
  216. if err != nil {
  217. return nil, err
  218. }
  219. pins = append(pins, &pin)
  220. }
  221. err = res.Err()
  222. if err != nil {
  223. return nil, err
  224. }
  225. return pins, nil
  226. }
  227. func PinCreate(dbId string, name string, query string) (*Pin, error) {
  228. now := time.Now()
  229. pin := &Pin{
  230. Id: uuid.New(),
  231. Name: name,
  232. DbId: dbId,
  233. Query: query,
  234. CreatedAt: now,
  235. UpdatedAt: now,
  236. QueryStartedAt: nil,
  237. QueryFinishedAt: nil,
  238. ResultsFields: MustNewPgJson(nil),
  239. ResultsRows: MustNewPgJson(nil),
  240. ResultsError: nil,
  241. ScheduledAt: now,
  242. DeletedAt: nil,
  243. Version: 1,
  244. }
  245. err := PinValidate(pin)
  246. if err != nil {
  247. return nil, err
  248. }
  249. _, err = PgConn.Exec("INSERT INTO pins (id, name, db_id, query, created_at, updated_at, query_started_at, query_finished_at, results_fields, results_rows, results_error, scheduled_at, deleted_at, version) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
  250. pin.Id, pin.Name, pin.DbId, pin.Query, pin.CreatedAt, pin.UpdatedAt, pin.QueryStartedAt, pin.QueryFinishedAt, pin.ResultsFields, pin.ResultsRows, pin.ResultsError, pin.ScheduledAt, pin.DeletedAt, pin.Version)
  251. if err != nil {
  252. return nil, err
  253. }
  254. err = workers.Enqueue("pins", "", pin.Id)
  255. if err != nil {
  256. return nil, err
  257. }
  258. return pin, nil
  259. }
  260. func PinGetInternal(queryFrag string, queryVals ...interface{}) (*Pin, error) {
  261. row := PgConn.QueryRow("SELECT id, name, db_id, query, created_at, updated_at, query_started_at, query_finished_at, results_fields, results_rows, results_error, scheduled_at, deleted_at, version FROM pins WHERE deleted_at IS NULL AND "+queryFrag+" LIMIT 1", queryVals...)
  262. pin := Pin{}
  263. err := row.Scan(&pin.Id, &pin.Name, &pin.DbId, &pin.Query, &pin.CreatedAt, &pin.UpdatedAt, &pin.QueryStartedAt, &pin.QueryFinishedAt, &pin.ResultsFields, &pin.ResultsRows, &pin.ResultsError, &pin.ScheduledAt, &pin.DeletedAt, &pin.Version)
  264. switch {
  265. case err == sql.ErrNoRows:
  266. return nil, nil
  267. case err != nil:
  268. return nil, err
  269. default:
  270. return &pin, nil
  271. }
  272. }
  273. func PinGet(idOrName string) (*Pin, error) {
  274. var pin *Pin
  275. var err error
  276. if DataUuidRegexp.MatchString(idOrName) {
  277. pin, err = PinGetInternal("(id=$1 OR name=$2)", idOrName, idOrName)
  278. } else {
  279. pin, err = PinGetInternal("name=$1", idOrName)
  280. }
  281. if err != nil {
  282. return nil, err
  283. }
  284. if pin == nil {
  285. return nil, &PgpinError{
  286. Id: "pin-not-found",
  287. Message: "pin not found",
  288. HttpStatus: 404,
  289. }
  290. }
  291. return pin, nil
  292. }
  293. func PinUpdate(pin *Pin) error {
  294. err := PinValidate(pin)
  295. if err != nil {
  296. return err
  297. }
  298. pin.UpdatedAt = time.Now()
  299. result, err := PgConn.Exec("UPDATE pins SET db_id=$1, name=$2, query=$3, created_at=$4, updated_at=$5, query_started_at=$6, query_finished_at=$7, results_fields=$8, results_rows=$9, results_error=$10, scheduled_at=$11, deleted_at=$12, version=$13 WHERE id=$14 AND version=$15",
  300. pin.DbId, pin.Name, pin.Query, pin.CreatedAt, pin.UpdatedAt, pin.QueryStartedAt, pin.QueryFinishedAt, pin.ResultsFields, pin.ResultsRows, pin.ResultsError, pin.ScheduledAt, pin.DeletedAt, pin.Version+1, pin.Id, pin.Version)
  301. if err != nil {
  302. return err
  303. }
  304. rowsAffected, err := result.RowsAffected()
  305. if err != nil {
  306. return err
  307. }
  308. if rowsAffected != 1 {
  309. return &PgpinError{
  310. Id: "pin-concurrent-update",
  311. Message: "concurrent pin updated attempted",
  312. HttpStatus: 400,
  313. }
  314. }
  315. pin.Version = pin.Version + 1
  316. return nil
  317. }
  318. func PinDelete(id string) (*Pin, error) {
  319. pin, err := PinGet(id)
  320. if err != nil {
  321. return nil, err
  322. }
  323. deletedAt := time.Now()
  324. pin.DeletedAt = &deletedAt
  325. err = PinUpdate(pin)
  326. if err != nil {
  327. return nil, err
  328. }
  329. return pin, nil
  330. }
  331. func PinDbUrl(pin *Pin) (string, error) {
  332. db, err := DbGet(pin.DbId)
  333. if err != nil {
  334. return "", err
  335. }
  336. return db.Url, nil
  337. }