Your SlideShare is downloading. ×
0
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Test Driven Database Development

2,883

Published on

reating a database schema means creating an interface for applications to use to manage their data. But how do you know how well that interface works until you’ve tried it? …

reating a database schema means creating an interface for applications to use to manage their data. But how do you know how well that interface works until you’ve tried it?

Well, by trying it before you create it.

This talk introduces the concept of test-driven development to database administrators. We’ll use pgTAP to work through a real-world example creating a database design with an intuitive, useful interface for managing application data. Derive more intuitive table structures! Keep an eye to the beauty of your views! Let your procedures make your application more productive! Feel younger and more clever, all through the power of TDDD!

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,883
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
111
Comments
0
Likes
5
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide
  • TODO:
    • Add more privilege stuff?
    • Add example of renaming `flips.timestamp`?
  • TODO:
    • Add more privilege stuff?
    • Add example of renaming `flips.timestamp`?
  • TODO:
    • Add more privilege stuff?
    • Add example of renaming `flips.timestamp`?
  • TODO:
    • Add more privilege stuff?
    • Add example of renaming `flips.timestamp`?

































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































  • Transcript

    • 1. Test Driven Database Development David E. Wheeler PostgreSQL Experts, Inc. OSCON 2010 Portland OR USA Text: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Images licensed independently and © Their respective owners.
    • 2. Test Driven Database Development ✘ David E. Wheeler PostgreSQL Experts, Inc. OSCON 2010 Portland OR USA Text: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Images licensed independently and © Their respective owners.
    • 3. David E. Wheeler OSCON 2010 Portland OR USA Text: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Images licensed independently and © Their respective owners.
    • 4. David E. Wheeler OSCON 2010 Portland OR USA License: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/
    • 5. Build My VC-Funded App ✔ for Me for Free David E. Wheeler CEO, Data Manager FASN Enterprises, Inc. OSCON 2010 Portland OR USA License: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/
    • 6. This is Genius
    • 7. This is Genius I had this idea
    • 8. This is Genius I had this idea Social networking is hot
    • 9. This is Genius I had this idea Social networking is hot Has been for too long
    • 10. This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue
    • 11. This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue Getting ahead of the curve
    • 12. This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue Getting ahead of the curve Introducing…
    • 13. http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
    • 14. antisocial network http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
    • 15. How it Works antisocial network
    • 16. How it Works Microblogging platform antisocial network
    • 17. How it Works Microblogging platform Everyone follows you antisocial network
    • 18. How it Works Microblogging platform Everyone follows you New users follow everyone antisocial network
    • 19. How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers antisocial network
    • 20. How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers Get them to unfollow you antisocial network
    • 21. How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers Get them to unfollow you Leaderboard: Those with fewest followers antisocial network
    • 22. Your Task antisocial network
    • 23. Your Task Create the database antisocial network
    • 24. Your Task Create the database Use TDDD to make it right antisocial network
    • 25. Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp antisocial network
    • 26. Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp Profit! antisocial network
    • 27. Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp Profit! For VC-funded Corp antisocial network
    • 28. http://flic.kr/p/2honiQ © 2007 James Duncan Davidson. All rights reserved. Used with permission.
    • 29. antisocial network
    • 30. TDDD antisocial network WTF?
    • 31. We’ll get there antisocial network
    • 32. But first… antisocial network
    • 33. NDA antisocial network
    • 34. Okay, now that that’s out of the way… antisocial network
    • 35. Please organize into pairs antisocial network
    • 36. Yes, that’s right antisocial network
    • 37. You’re gonna do pair database programming antisocial network
    • 38. App developers partner with DBAs antisocial network
    • 39. I’m waiting… antisocial network
    • 40. Okay, Ready? antisocial network
    • 41. Time to Install antisocial network
    • 42. Install PostgreSQL antisocial network
    • 43. Install PostgreSQL http:/ /www.postgresql.org/download/ http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
    • 44. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
    • 45. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
    • 46. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! http:/ 10.16.2/oscon/ /10. antisocial network
    • 47. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. antisocial network
    • 48. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. ./configure && make && make install antisocial network
    • 49. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. ./configure && make && make install cd contrib && make && make install antisocial network
    • 50. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. ./configure && make && make install cd contrib && make && make install initdb -D /usr/local/pgsql/data antisocial network
    • 51. Install Test::Harness
    • 52. Install Test::Harness % cpan Test::Harness
    • 53. Install Test::Harness % cpan Test::Harness Or…
    • 54. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321
    • 55. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz
    • 56. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21
    • 57. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL
    • 58. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL % make
    • 59. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL % make % make test
    • 60. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL % make % make test % sudo make install
    • 61. Install pgTAP
    • 62. Install pgTAP % tar jxf pgtap-0.24.tar.bz2
    • 63. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24
    • 64. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap Separate schema
    • 65. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install
    • 66. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck
    • 67. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck % createdb -U postgres flipr
    • 68. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck % createdb -U postgres flipr % createlang -U postgres -d flipr plpgsql
    • 69. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck % createdb -U postgres flipr % createlang -U postgres -d flipr plpgsql % psql -U postgres -d flipr -f pgtap.sql
    • 70. We good? antisocial network
    • 71. First Po^^Test! antisocial network
    • 72. pgTAP Basics 001-schema.pg
    • 73. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 74. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 75. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 76. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 77. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 78. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 79. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
    • 80. Run the Test
    • 81. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
    • 82. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
    • 83. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
    • 84. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
    • 85. Create Users Table 001-users.sql
    • 86. Create Users Table CREATE TABLE users ( id INT ); 001-users.sql
    • 87. Create Users Table CREATE TABLE users ( id INT ); Bare minimum 001-users.sql
    • 88. First Pass
    • 89. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS
    • 90. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS
    • 91. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS
    • 92. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS W00t!
    • 93. Now…Iterate! antisocial network
    • 94. What Columns? antisocial network
    • 95. What Columns? Nickname antisocial network
    • 96. What Columns? Nickname Password antisocial network
    • 97. What Columns? Nickname Password Timestamp antisocial network
    • 98. Column Testing 002-schema.pg
    • 99. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
    • 100. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
    • 101. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
    • 102. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
    • 103. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
    • 104. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
    • 105. Run ’Em
    • 106. Run ’Em % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist not ok 3 - Column users.nickname should exist # Failed test 3: "Column users.nickname should exist" not ok 4 - Column users.password should exist # Failed test 4: "Column users.password should exist" not ok 5 - Column users."timestamp" should exist # Failed test 5: "Column users."timestamp" should exist" # Looks like you failed 3 tests of 5 tests/002-schema.pg .. Failed 3/5 subtests Test Summary Report ------------------- tests/002-schema.pg (Tests: 5 Failed: 2) Failed tests: 3-5 Files=1, Tests=5, 0 wallclock secs Result: FAIL
    • 107. Run ’Em % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist not ok 3 - Column users.nickname should exist # Failed test 3: "Column users.nickname should exist" not ok 4 - Column users.password should exist # Failed test 4: "Column users.password should exist" not ok 5 - Column users."timestamp" should exist # Failed test 5: "Column users."timestamp" should exist" # Looks like you failed 3 tests of 5 As tests/002-schema.pg .. Failed 3/5 subtests Test Summary Report ------------------- tests/002-schema.pg (Tests: 5 Failed: 2) Expected Failed tests: 3-5 Files=1, Tests=5, 0 wallclock secs Result: FAIL
    • 108. Add the Columns 002-users.sql
    • 109. Add the Columns DROP TABLE IF EXISTS users; CREATE TABLE users ( nickname TEXT PRIMARY KEY, password TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() ); 002-users.sql
    • 110. Add the Columns DROP TABLE IF EXISTS users; CREATE TABLE users ( nickname TEXT PRIMARY KEY, password TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() ); 002-users.sql Simple.
    • 111. Run Again
    • 112. Run Again % psql -d flipr -f sql/002-users.sql % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist ok 3 - Column users.nickname should exist ok 4 - Column users.password should exist ok 5 - Column users."timestamp" should exist ok All tests successful. Files=1, Tests=5, 0 wallclock secs Result: PASS
    • 113. Run Again % psql -d flipr -f sql/002-users.sql % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist ok 3 - Column users.nickname should exist ok 4 - Column users.password should exist ok 5 - Column users."timestamp" should exist ok All tests successful. Files=1, Tests=5, 0 wallclock secs Result: PASS Rockin.
    • 114. 003-schmea.pg
    • 115. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 116. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 117. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 118. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 119. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 120. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 121. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 122. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 123. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_pk( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT col_hasnt_default( 'users', 'nickname' ); SELECT col_is_pk( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT col_not_null( 'users', 'password' ); SELECT col_hasnt_default( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
    • 124. Files
    • 125. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git
    • 126. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/
    • 127. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10.
    • 128. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. http:/ /github.com/theory/tddd/
    • 129. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. http:/ /github.com/theory/tddd/ pg_prove -vd flipr -U postgres tests/002-schema.pg
    • 130. Go!
    • 131. Go! % pg_prove -d flipr tests/003-schema.pg tests/003-schema.pg .. ok All tests successful. Files=1, Tests=16, 0 wallclock secs Result: PASS
    • 132. Go! % pg_prove -d flipr tests/003-schema.pg tests/003-schema.pg .. ok All tests successful. Files=1, Tests=16, 0 wallclock secs Result: PASS
    • 133. Go! % pg_prove -d flipr tests/003-schema.pg tests/003-schema.pg .. ok All tests successful. Files=1, Tests=16, 0 wallclock secs Result: PASS Kick ass.
    • 134. antisocial network Your Turn
    • 135. antisocial network Your Turn Create users table
    • 136. antisocial network Your Turn Create users table Project Repo: http://github.com/theory/tddd/
    • 137. So Far So Good antisocial network
    • 138. So Far So Good Appdev begins antisocial network
    • 139. So Far So Good Appdev begins Web site antisocial network
    • 140. So Far So Good Appdev begins Web site API antisocial network
    • 141. So Far So Good Appdev begins Web site API Ruh-roh antisocial network
    • 142. So Far So Good Appdev begins Web site API Ruh-roh Two code paths antisocial network
    • 143. So Far So Good Appdev begins Web site API Ruh-roh Two code paths Just look at this data! antisocial network
    • 144. Data Inconsistencies
    • 145. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
    • 146. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
    • 147. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
    • 148. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
    • 149. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
    • 150. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
    • 151. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna 〷〷〷〷〷〷〷〷〷 | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 We can do better! (4 rows)
    • 152. What Encryption? antisocial network
    • 153. What Encryption? Multiple interfaces antisocial network
    • 154. What Encryption? Multiple interfaces Data must be consistent antisocial network
    • 155. What Encryption? Multiple interfaces Data must be consistent Encrypt passwords antisocial network
    • 156. What Encryption? Multiple interfaces Data must be consistent Encrypt passwords Obscure password duplication antisocial network
    • 157. What Encryption? Multiple interfaces Data must be consistent Encrypt passwords Obscure password duplication Solution: Database functions antisocial network
    • 158. Test For New Function 004-userfunc.pg
    • 159. Test For New Function SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT finish(); ROLLBACK; 004-userfunc.pg
    • 160. Test For New Function SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT finish(); ROLLBACK; 004-userfunc.pg
    • 161. Test For New Function SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT finish(); ROLLBACK; 004-userfunc.pg
    • 162. Test For New Function SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT finish(); ROLLBACK; 004-userfunc.pg
    • 163. Run ’Em
    • 164. Run ’Em % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. 1/? not ok 1 - Function ins_user() should exist # Failed test 1: "Function ins_user() should exist" not ok 2 - Function ins_user(text, text) should exist # Failed test 2: "Function ins_user(text, text) should exist" not ok 3 - Function ins_user() should return void # Failed test 3: "Function ins_user() should return void" # Function ins_user() does not exist # Looks like you failed 3 tests of 3 tests/004-userfunc.pg .. Failed 3/3 subtests Test Summary Report ------------------- tests/004-userfunc.pg (Wstat: 0 Tests: 3 Failed: 3) Failed tests: 1-3 Files=1, Tests=3, 0 wallclock secs Result: FAIL
    • 165. Run ’Em % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. 1/? not ok 1 - Function ins_user() should exist # Failed test 1: "Function ins_user() should exist" not ok 2 - Function ins_user(text, text) should exist # Failed test 2: "Function ins_user(text, text) should exist" not ok 3 - Function ins_user() should return void # Failed test 3: "Function ins_user() should return void" # Function ins_user() does not exist # Looks like you failed 3 tests of 3 tests/004-userfunc.pg .. Failed 3/3 subtests Test Summary Report ------------------- tests/004-userfunc.pg (Wstat: 0 Tests: 3 Failed: 3) Failed tests: 1-3 Files=1, Tests=3, 0 wallclock secs Result: FAIL
    • 166. Create It! 004-userfunc.sql
    • 167. Create It! CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS ''; 004-userfunc.sql
    • 168. Create It! CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS ''; Just enough… 004-userfunc.sql
    • 169. …To make ’em pass
    • 170. …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION %
    • 171. …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. ok All tests successful. Files=1, Tests=3, 0 wallclock secs Result: PASS
    • 172. …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. ok All tests successful. Files=1, Tests=3, 0 wallclock secs Result: PASS Yay!
    • 173. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 005-userfunc.pg
    • 174. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT is(COUNT(*)::int, 0, 'Should have no users') FROM users; SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; 005-userfunc.pg
    • 175. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT is(COUNT(*)::int, 0, 'Should have no users') FROM users; SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; 005-userfunc.pg
    • 176. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT is(COUNT(*)::int, 0, 'Should have no users') FROM users; SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; 005-userfunc.pg
    • 177. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT is(COUNT(*)::int, 0, 'Should have no users') FROM users; SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; 005-userfunc.pg
    • 178. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); SELECT is(COUNT(*)::int, 0, 'Should have no users') FROM users; SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; 005-userfunc.pg
    • 179. What does that get us?
    • 180. What does that get us? % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. 1/? not ok 6 - Should now have one user # Failed test 6: "Should now have one user" # have: 0 # want: 1 # Looks like you failed 1 test of 6 tests/005-userfunc.pg .. Failed 1/6 subtests Test Summary Report ------------------- tests/005-userfunc.pg (Wstat: 0 Tests: 6 Failed: 1) Failed test: 6 Files=1, Tests=6, 1 wallclock secs Result: FAIL
    • 181. What does that get us? % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. 1/? not ok 6 - Should now have one user # Failed test 6: "Should now have one user" # have: 0 # want: 1 # Looks like you failed 1 test of 6 tests/005-userfunc.pg .. Failed 1/6 subtests Test Summary Report ------------------- tests/005-userfunc.pg (Wstat: 0 Tests: 6 Failed: 1) Failed test: 6 Files=1, Tests=6, 1 wallclock secs Result: FAIL
    • 182. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS''; 005-userfunc.sql
    • 183. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT $$ ) RETURNS VOID LANGUAGE SQL AS INSERT INTO users values($1, $2); $$; 005-userfunc.sql
    • 184. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT $$ ) RETURNS VOID LANGUAGE SQL AS INSERT INTO users values($1, $2); $$; Bare minimum 005-userfunc.sql
    • 185. …To make ’em pass
    • 186. …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION %
    • 187. …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. ok All tests successful. Files=1, Tests=6, 0 wallclock secs Result: PASS
    • 188. …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. ok All tests successful. Files=1, Tests=6, 0 wallclock secs Result: PASS Great!
    • 189. Add More Assertions SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; 006-userfunc.pg
    • 190. Add More Assertions SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; 006-userfunc.pg
    • 191. Add More Assertions SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; 006-userfunc.pg
    • 192. Add More Assertions SELECT lives_ok( $$ SELECT ins_user('theory', 'wet blanket') $$, 'Execution of ins_user() should not die' ); SELECT is(COUNT(*)::int, 1, 'Should now have one user') FROM users; SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; 006-userfunc.pg
    • 193. Run ’Em
    • 194. Run ’Em % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. 1/? not ok 7 - Password should not be clear text # Failed test 7: "Password should not be clear text" # have: wet blanket # want: anything else # Looks like you failed 1 test of 7 tests/006-userfunc.pg .. Failed 1/7 subtests Test Summary Report ------------------- tests/006-userfunc.pg (Wstat: 0 Tests: 7 Failed: 1) Failed test: 7 Files=1, Tests=7, 0 wallclock secs Result: FAIL
    • 195. Run ’Em % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. 1/? not ok 7 - Password should not be clear text # Failed test 7: "Password should not be clear text" # have: wet blanket # want: anything else # Looks like you failed 1 test of 7 tests/006-userfunc.pg .. Failed 1/7 subtests Test Summary Report ------------------- tests/006-userfunc.pg (Wstat: 0 Tests: 7 Failed: 1) Failed test: 7 Files=1, Tests=7, 0 wallclock secs Result: FAIL
    • 196. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $ ); 2 $$; 006-userfunc.sql
    • 197. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $ ); 1 $$; 006-userfunc.sql
    • 198. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $ ); 1 $$; I’m not kidding 006-userfunc.sql
    • 199. …To make ’em pass
    • 200. …To make ’em pass % psql -d flipr -f sql/006-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. ok All tests successful. Files=1, Tests=7, 1 wallclock secs Result: PASS
    • 201. …To make ’em pass % psql -d flipr -f sql/006-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. ok All tests successful. Files=1, Tests=7, 1 wallclock secs Result: PASS So what?
    • 202. Sanity Check SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; 007-userfunc.pg
    • 203. Sanity Check SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; 007-userfunc.pg
    • 204. Sanity Check SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; 007-userfunc.pg
    • 205. Sanity Check SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; 007-userfunc.pg
    • 206. Are We Insane?
    • 207. Are We Insane? pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. 1/? not ok 8 - Password should not be nickname # Failed test 8: "Password should not be nickname" # have: theory # want: anything else # Looks like you failed 1 test of 8 tests/007-userfunc.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/007-userfunc.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 0 wallclock secs Result: FAIL
    • 208. Are We Insane? pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. 1/? not ok 8 - Password should not be nickname # Failed test 8: "Password should not be nickname" # have: theory # want: anything else # Looks like you failed 1 test of 8 tests/007-userfunc.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/007-userfunc.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 0 wallclock secs Result: FAIL
    • 209. Change for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1); $$; 007-userfunc.sql
    • 210. Change for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, 'whatever'); $$; 007-userfunc.sql
    • 211. Change for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, 'whatever'); $$; That’ll do it. 007-userfunc.sql
    • 212. See ’em Pass
    • 213. See ’em Pass % psql -d flipr -f sql/007-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. ok All tests successful. Files=1, Tests=8, 0 wallclock secs Result: PASS
    • 214. See ’em Pass % psql -d flipr -f sql/007-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. ok All tests successful. Files=1, Tests=8, 0 wallclock secs Result: PASS Exciting, no?
    • 215. Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; 008-userfunc.pg
    • 216. Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; SELECT lives_ok( $$ SELECT ins_user('strongrrl', 'wet blanket') $$, 'Insert user "strongrrl"' ); SELECT is(COUNT(*)::int, 2, 'Should now have two users') FROM users; SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); 008-userfunc.pg
    • 217. Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; SELECT lives_ok( $$ SELECT ins_user('strongrrl', 'wet blanket') $$, 'Insert user "strongrrl"' ); SELECT is(COUNT(*)::int, 2, 'Should now have two users') FROM users; SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); 008-userfunc.pg
    • 218. Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; SELECT lives_ok( $$ SELECT ins_user('strongrrl', 'wet blanket') $$, 'Insert user "strongrrl"' ); SELECT is(COUNT(*)::int, 2, 'Should now have two users') FROM users; SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); 008-userfunc.pg
    • 219. Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; SELECT lives_ok( $$ SELECT ins_user('strongrrl', 'wet blanket') $$, 'Insert user "strongrrl"' ); SELECT is(COUNT(*)::int, 2, 'Should now have two users') FROM users; SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); 008-userfunc.pg
    • 220. Let Us Be Inconsistent
    • 221. Let Us Be Inconsistent pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: whatever # want: anything else # Looks like you failed 1 test of 11 tests/008-userfunc.pg .. Failed 1/11 subtests Test Summary Report ------------------- tests/008-userfunc.pg (Wstat: 0 Tests: 11 Failed: 1) Failed test: 11 Files=1, Tests=11, 0 wallclock secs Result: FAIL
    • 222. Let Us Be Inconsistent pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: whatever # want: anything else # Looks like you failed 1 test of 11 tests/008-userfunc.pg .. Failed 1/11 subtests Test Summary Report ------------------- tests/008-userfunc.pg (Wstat: 0 Tests: 11 Failed: 1) Failed test: 11 Files=1, Tests=11, 0 wallclock secs Result: FAIL
    • 223. Cheat Death CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, 'whatever'); $$; 008-userfunc.sql
    • 224. Cheat Death CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1 || $2); $$; 008-userfunc.sql
    • 225. Cheat Death CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1 || $2); $$; No, seriously. 008-userfunc.sql
    • 226. Take a Pass
    • 227. Take a Pass % psql -d flipr -f sql/008-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. ok All tests successful. Files=1, Tests=11, 0 wallclock secs Result: PASS
    • 228. Take a Pass % psql -d flipr -f sql/008-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. ok All tests successful. Files=1, Tests=11, 0 wallclock secs Result: PASS Not unexpected.
    • 229. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); 009-userfunc.pg
    • 230. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 231. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 232. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 233. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 234. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 235. % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 12 - Password should not contain nickname # Failed test 12: "Password should not contain nickname" # 'theorywet blanket' # matches: '%theory%' not ok 13 - Password should not contain nickname # Failed test 13: "Password should not contain nickname" # 'strongrrlwet blanket' # matches: '%strongrrl%' not ok 14 - Password should not contain clear text # Failed test 14: "Password should not contain clear text" # 'theorywet blanket' # matches: '%wet blanket%' not ok 15 - Password should not contain clear text # Failed test 15: "Password should not contain clear text" # 'strongrrlwet blanket' # matches: '%wet blanket%' # Looks like you failed 4 tests of 15 tests/009-userfunc.pg .. Failed 4/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4) Failed tests: 12-15 Files=1, Tests=15, 0 wallclock secs Result: FAIL
    • 236. % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 12 - Password should not contain nickname # Failed test 12: "Password should not contain nickname" # 'theorywet blanket' # matches: '%theory%' not ok 13 - Password should not contain nickname # Failed test 13: "Password should not contain nickname" # 'strongrrlwet blanket' # matches: '%strongrrl%' not ok 14 - Password should not contain clear text # Failed test 14: "Password should not contain clear text" # 'theorywet blanket' # matches: '%wet blanket%' not ok 15 - Password should not contain clear text # Failed test 15: "Password should not contain clear text" # 'strongrrlwet blanket' # matches: '%wet blanket%' # Looks like you failed 4 tests of 15 tests/009-userfunc.pg .. Failed 4/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4) Failed tests: 12-15 Files=1, Tests=15, 0 wallclock secs Result: FAIL
    • 237. % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 12 - Password should not contain nickname # Failed test 12: "Password should not contain nickname" # 'theorywet blanket' # matches: '%theory%' not ok 13 - Password should not contain nickname # Failed test 13: "Password should not contain nickname" # 'strongrrlwet blanket' # matches: '%strongrrl%' not ok 14 - Password should not contain clear text # Failed test 14: "Password should not contain clear text" # 'theorywet blanket' # matches: '%wet blanket%' not ok 15 - Password should not contain clear text # Failed test 15: "Password should not contain clear text" # 'strongrrlwet blanket' # matches: '%wet blanket%' # Looks like you failed 4 tests of 15 tests/009-userfunc.pg .. Failed 4/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4) Failed tests: 12-15 Files=1, Tests=15, 0 wallclock secs Result: FAIL
    • 238. Try MD5 CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1 || $2); $$; 009-userfunc.sql
    • 239. Try MD5 CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, md5($2)); $$; 009-userfunc.sql
    • 240. Try MD5 CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, md5($2)); $$; It’s hashish. 009-userfunc.sql
    • 241. Here We Go!
    • 242. Here We Go! % psql -d flipr sql/009-userfunc.sql % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: 3c908e08d874ad9ec39bffc93b5fb983 # want: anything else # Looks like you failed 1 test of 15 tests/009-userfunc.pg .. Failed 1/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 11 Files=1, Tests=15, 0 wallclock secs Result: FAIL
    • 243. Here We Go! % psql -d flipr sql/009-userfunc.sql % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: 3c908e08d874ad9ec39bffc93b5fb983 # want: anything else # Looks like you failed 1 test of 15 tests/009-userfunc.pg .. Failed 1/15 subtests Test Summary Report ------------------- Wait, what? tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 11 Files=1, Tests=15, 0 wallclock secs Result: FAIL
    • 244. Here We Go! % psql -d flipr sql/009-userfunc.sql % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: 3c908e08d874ad9ec39bffc93b5fb983 # want: anything else # Looks like you failed 1 test of 15 tests/009-userfunc.pg .. Failed 1/15 subtests Test Summary Report ------------------- Wait, what? tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 11 Files=1, Tests=15, 0 wallclock secs Result: FAIL
    • 245. Whaaaaa? SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 246. Whaaaaa? SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 247. Whaaaaa? SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); SELECT unalike( password, '%' || nickname || '%', 'Password should not contain nickname' Regression! ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 248. Whaaaaa? SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); Must SELECT unalike( password, obscure '%' || nickname || '%', 'Password should not contain nickname' dupes. Regression! ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
    • 249. Where Are We? antisocial network
    • 250. Where Are We? Function adds user antisocial network
    • 251. Where Are We? Function adds user Nickname not in password antisocial network
    • 252. Where Are We? Function adds user Nickname not in password No clear text password antisocial network
    • 253. Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates antisocial network
    • 254. Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates How? antisocial network
    • 255. Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates How? Solution: pg_crypto antisocial network
    • 256. Test for pg_crypto SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); 010-schema.pg
    • 257. Test for pg_crypto SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT has_function('crypt'); SELECT has_function('gen_salt'); 010-schema.pg
    • 258. See Test Fail
    • 259. See Test Fail % pg_prove -d flipr tests/010-schema.pg tests/010-schema.pg .. 1/? not ok 17 - Function crypt() should exist # Failed test 17: "Function crypt() should exist" not ok 18 - Function gen_salt() should exist # Failed test 18: "Function gen_salt() should exist" # Looks like you failed 2 tests of 18 tests/010-schema.pg .. Failed 2/18 subtests Test Summary Report ------------------- tests/010-schema.pg (Wstat: 0 Tests: 18 Failed: 2) Failed tests: 17-18 Files=1, Tests=18, 0 wallclock secs Result: FAIL
    • 260. Install pg_crypto
    • 261. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql %
    • 262. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql % pg_prove -d flipr tests/004-schema.pg tests/004-schema.pg .. ok All tests successful. Files=1, Tests=18, 1 wallclock secs Result: PASS
    • 263. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql % pg_prove -d flipr tests/004-schema.pg tests/004-schema.pg .. ok All tests successful. Files=1, Tests=18, 1 wallclock secs Result: PASS Great.
    • 264. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql % pg_prove -d flipr tests/004-schema.pg tests/004-schema.pg .. ok All tests successful. Files=1, Tests=18, 1 wallclock secs Result: PASS Great. Let’s use them!
    • 265. Use pg_crypt CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, md5($2)); $$; 010-userfunc.sql
    • 266. Use pg_crypt CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, crypt($1, gen_salt('md5'))); $$; 010-userfunc.sql
    • 267. Use pg_crypt CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, crypt($1, gen_salt('md5'))); $$; Cryptonite! 010-userfunc.sql
    • 268. Cryptonomicon!
    • 269. Cryptonomicon! % psql -d flipr -f sql/010-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. ok All tests successful. Files=1, Tests=15, 0 wallclock secs Result: PASS
    • 270. Cryptonomicon! % psql -d flipr -f sql/010-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. ok All tests successful. Files=1, Tests=15, 0 wallclock secs Result: PASS Finally!
    • 271. Sanity Check SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 011-userfunc.pg
    • 272. Sanity Check SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; SELECT is( password, crypt('wet blanket', password), 'Password should match crypted password' ) FROM users; 011-userfunc.pg
    • 273. Sanity Check SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; SELECT is( password, crypt('wet blanket', password), 'Password should match crypted password' ) FROM users; 011-userfunc.pg
    • 274. Sanity Check SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; SELECT is( password, crypt('wet blanket', password), 'Password should match crypted password' ) FROM users; 011-userfunc.pg
    • 275. Are We Sane?
    • 276. Are We Sane? % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. 1/? not ok 16 - Password should match crypted password # Failed test 16: "Password should match crypted password" # have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK. # want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0 not ok 17 - Password should match crypted password # Failed test 17: "Password should match crypted password" # have: $1$KCxoeJAK$APSOlalADk1akGrItOASx. # want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0 # Looks like you failed 2 tests of 17 tests/011-userfunc.pg .. Failed 2/17 subtests Test Summary Report ------------------- tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2) Failed tests: 16-17 Files=1, Tests=17, 0 wallclock secs Result: FAIL
    • 277. Are We Sane? % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. 1/? not ok 16 - Password should match crypted password # Failed test 16: "Password should match crypted password" # have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK. # want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0 not ok 17 - Password should match crypted password # Failed test 17: "Password should match crypted password" # have: $1$KCxoeJAK$APSOlalADk1akGrItOASx. # want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0 # Looks like you failed 2 tests of 17 tests/011-userfunc.pg .. Failed 2/17 subtests Test Summary Report ------------------- Say what? tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2) Failed tests: 16-17 Files=1, Tests=17, 0 wallclock secs Result: FAIL
    • 278. Are We Sane? % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. 1/? not ok 16 - Password should match crypted password # Failed test 16: "Password should match crypted password" # have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK. # want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0 not ok 17 - Password should match crypted password # Failed test 17: "Password should match crypted password" # have: $1$KCxoeJAK$APSOlalADk1akGrItOASx. # want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0 # Looks like you failed 2 tests of 17 tests/011-userfunc.pg .. Failed 2/17 subtests Test Summary Report ------------------- Say what? tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2) Failed tests: 16-17 Files=1, Tests=17, 0 wallclock secs Result: FAIL
    • 279. What’d We Do Wrong? CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ 1 INSERT INTO users values($1, crypt($ , gen_salt('md5'))); $$; 011-userfunc.sql
    • 280. What’d We Do Wrong? CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ 2 INSERT INTO users values($1, crypt($ , gen_salt('md5'))); $$; 011-userfunc.sql
    • 281. What’d We Do Wrong? CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ 2 INSERT INTO users values($1, crypt($ , gen_salt('md5'))); $$; Cryptonite! 011-userfunc.sql
    • 282. And…
    • 283. And… % psql -d flipr -f sql/011-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. ok All tests successful. Files=1, Tests=17, 1 wallclock secs Result: PASS
    • 284. And… % psql -d flipr -f sql/011-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. ok All tests successful. Files=1, Tests=17, 1 wallclock secs Result: PASS We’re there!
    • 285. Next: upd_pass() antisocial network
    • 286. Next: upd_pass() Need password updating antisocial network
    • 287. Next: upd_pass() Need password updating Things to consider: antisocial network
    • 288. Next: upd_pass() Need password updating Things to consider: Update nonexistent user? antisocial network
    • 289. Next: upd_pass() Need password updating Things to consider: Update nonexistent user? Update with invalid password? antisocial network
    • 290. Next: upd_pass() Need password updating Things to consider: Update nonexistent user? Update with invalid password? Was password updated? antisocial network
    • 291. antisocial network Your Turn
    • 292. antisocial network Your Turn Create upd_pass()
    • 293. antisocial network Your Turn Create upd_pass() Project Repo: http://github.com/theory/tddd/
    • 294. How’d We Do? 012-userfunc.sql
    • 295. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); 012-userfunc.sql
    • 296. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); 012-userfunc.sql
    • 297. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); 012-userfunc.sql
    • 298. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); 012-userfunc.sql
    • 299. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); 012-userfunc.sql
    • 300. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); 012-userfunc.sql
    • 301. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); SELECT ok( NOT upd_pass('nobody', 'foo', 'bar'), 'upd_pass() should return false for nonexistent user' ); SELECT ok( NOT upd_pass('theory', 'foo', 'bar'), 'upd_pass() should return false for invalid old pass' ); SELECT is( password, crypt('wet blanket', password), 'Password should be unchanged' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 302. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); SELECT ok( NOT upd_pass('nobody', 'foo', 'bar'), 'upd_pass() should return false for nonexistent user' ); SELECT ok( NOT upd_pass('theory', 'foo', 'bar'), 'upd_pass() should return false for invalid old pass' ); SELECT is( password, crypt('wet blanket', password), 'Password should be unchanged' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 303. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); SELECT ok( NOT upd_pass('nobody', 'foo', 'bar'), 'upd_pass() should return false for nonexistent user' ); SELECT ok( NOT upd_pass('theory', 'foo', 'bar'), 'upd_pass() should return false for invalid old pass' ); SELECT is( password, crypt('wet blanket', password), 'Password should be unchanged' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 304. How’d We Do? SELECT has_language('plpgsql'); SELECT has_function('upd_pass'); SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']); SELECT function_returns('upd_pass', 'boolean'); SELECT function_lang_is('upd_pass', 'plpgsql'); SELECT ok( NOT upd_pass('nobody', 'foo', 'bar'), 'upd_pass() should return false for nonexistent user' ); SELECT ok( NOT upd_pass('theory', 'foo', 'bar'), 'upd_pass() should return false for invalid old pass' ); SELECT is( password, crypt('wet blanket', password), 'Password should be unchanged' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 305. How’d We Do? 012-userfunc.sql
    • 306. How’d We Do? SELECT ok( upd_pass('theory', 'wet blanket', 'pgtap rulez'), 'upd_pass() should return true for proper args' ); SELECT is( password, crypt('pgtap rulez', password), 'Password should now be changed' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 307. How’d We Do? SELECT ok( upd_pass('theory', 'wet blanket', 'pgtap rulez'), 'upd_pass() should return true for proper args' ); SELECT is( password, crypt('pgtap rulez', password), 'Password should now be changed' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 308. How’d We Do? SELECT ok( upd_pass('theory', 'wet blanket', 'pgtap rulez'), 'upd_pass() should return true for proper args' ); SELECT is( password, crypt('pgtap rulez', password), 'Password should now be changed' ) FROM users WHERE nickname = 'theory'; 012-userfunc.sql
    • 309. What Does it Look Like? 013-privs.sql
    • 310. What Does it Look Like? CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql AS $$ BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 013-privs.sql
    • 311. What Does it Look Like? CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql AS $$ BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 013-privs.sql Good, eh?
    • 312. What About Privileges? antisocial network
    • 313. What About Privileges? Got solid functions antisocial network
    • 314. What About Privileges? Got solid functions Apps still using tables antisocial network
    • 315. What About Privileges? Got solid functions Apps still using tables How to prevent that? antisocial network
    • 316. What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! antisocial network
    • 317. What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! Deny access to table antisocial network
    • 318. What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! Deny access to table Allow access to functions antisocial network
    • 319. Test Privileges 013-privs.pg
    • 320. Test Privileges SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT finish(); ROLLBACK; 013-privs.pg
    • 321. Test Privileges SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT finish(); ROLLBACK; 013-privs.pg
    • 322. Test Privileges SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT finish(); ROLLBACK; 013-privs.pg
    • 323. Check Privileges
    • 324. Check Privileges % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. 1/? not ok 1 - Role fliprapp should exist # Failed test 1: "Role fliprapp should exist" not ok 2 - User fliprapp should not be a super user # Failed test 2: "User fliprapp should not be a super user" # User fliprapp does not exist # Looks like you failed 2 tests of 2 tests/013-privs.pg .. Failed 2/2 subtests Test Summary Report ------------------- tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2) Failed tests: 1-2 Files=1, Tests=2, 0 wallclock secs Result: FAIL
    • 325. Check Privileges % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. 1/? not ok 1 - Role fliprapp should exist # Failed test 1: "Role fliprapp should exist" not ok 2 - User fliprapp should not be a super user # Failed test 2: "User fliprapp should not be a super user" # User fliprapp does not exist # Looks like you failed 2 tests of 2 tests/013-privs.pg .. Failed 2/2 subtests Test Summary Report ------------------- tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2) Failed tests: 1-2 Files=1, Tests=2, 0 wallclock secs Result: FAIL
    • 326. Check Privileges % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. 1/? not ok 1 - Role fliprapp should exist # Failed test 1: "Role fliprapp should exist" not ok 2 - User fliprapp should not be a super user # Failed test 2: "User fliprapp should not be a super user" # User fliprapp does not exist # Looks like you failed 2 tests of 2 tests/013-privs.pg .. Failed 2/2 subtests Test Summary Report ------------------- tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2) Failed tests: 1-2 Files=1, Tests=2, 0 wallclock secs Result: FAIL
    • 327. Add a User 013-privs.sql
    • 328. Add a User CREATE USER fliprapp WITH LOGIN; 013-privs.sql
    • 329. Add a User CREATE USER fliprapp WITH LOGIN; Easy, right? 013-privs.sql
    • 330. User Test
    • 331. User Test % psql -d flipr -f sql/013-privs.sql CREATE ROLE % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs Result: PASS
    • 332. User Test % psql -d flipr -f sql/013-privs.sql CREATE ROLE % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs Result: PASS Riiiiight.
    • 333. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); 014-userfunc.pg
    • 334. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg
    • 335. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg
    • 336. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg
    • 337. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg
    • 338. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg
    • 339. Check Privileges
    • 340. Check Privileges % pg_prove -d flipr tests/014-privs.pg tests/014-privs.pg .. 1/? not ok 5 - Function ins_user() should be security definer # Failed test 5: "Function ins_user() should be security definer" not ok 6 - Function upd_pass() should be security definer # Failed test 6: "Function upd_pass() should be security definer" # Looks like you failed 2 tests of 6 tests/014-privs.pg .. Failed 2/6 subtests Test Summary Report ------------------- tests/014-privs.pg (Wstat: 0 Tests: 6 Failed: 2) Failed tests: 5-6 Files=1, Tests=6, 0 wallclock secs Result: FAIL
    • 341. Check Privileges % pg_prove -d flipr tests/014-privs.pg tests/014-privs.pg .. 1/? not ok 5 - Function ins_user() should be security definer # Failed test 5: "Function ins_user() should be security definer" not ok 6 - Function upd_pass() should be security definer # Failed test 6: "Function upd_pass() should be security definer" # Looks like you failed 2 tests of 6 tests/014-privs.pg .. Failed 2/6 subtests Test Summary Report ------------------- tests/014-privs.pg (Wstat: 0 Tests: 6 Failed: 2) Failed tests: 5-6 Files=1, Tests=6, 0 wallclock secs Result: FAIL
    • 342. Check Privileges % pg_prove -d flipr tests/014-privs.pg tests/014-privs.pg .. 1/? not ok 5 - Function ins_user() should be security definer # Failed test 5: "Function ins_user() should be security definer" not ok 6 - Function upd_pass() should be security definer # Failed test 6: "Function upd_pass() should be security definer" # Looks like you failed 2 tests of 6 tests/014-privs.pg .. Failed 2/6 subtests Test Summary Report ------------------- tests/014-privs.pg (Wstat: 0 Tests: 6 Failed: 2) Failed tests: 5-6 Files=1, Tests=6, 0 wallclock secs Result: FAIL Gotta fix that…
    • 343. Define the Security CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL $$AS INSERT INTO users values($1, crypt($2, gen_salt('md5'))); $$; CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql $$ AS BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 014-userfunc.sql
    • 344. Define the Security CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL SECURITY DEFINER AS $$ INSERT INTO users values($1, crypt($2, gen_salt('md5'))); $$; CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql $$ AS BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 014-userfunc.sql
    • 345. Define the Security CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL SECURITY DEFINER AS $$ INSERT INTO users values($1, crypt($2, gen_salt('md5'))); $$; CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 014-userfunc.sql
    • 346. We’re In!
    • 347. We’re In! % psql -d flipr -f sql/014-userfunc.sql CREATE FUNCTION CREATE FUNCTION % pg_prove -d flipr tests/014-privs.pg tests/014-privs.pg .. ok All tests successful. Files=1, Tests=6, 0 wallclock secs Result: PASS
    • 348. Check Table Privileges SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 015-userfunc.pg
    • 349. Check Table Privileges SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); SELECT ok( has_table_privilege('fliprapp', 'users', 'SELECT'), 'fliprapp should have SELECT priv on users table' ); SELECT ok( NOT has_table_privilege('fliprapp', 'users', priv), 'fliprapp should not have ' || priv || ' priv on users table' ) FROM unnest(ARRAY[ 'UPDATE', 'INSERT', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER' ]) AS priv; 015-userfunc.pg
    • 350. Check Table Privileges SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); SELECT ok( has_table_privilege('fliprapp', 'users', 'SELECT'), 'fliprapp should have SELECT priv on users table' ); SELECT ok( NOT has_table_privilege('fliprapp', 'users', priv), 'fliprapp should not have ' || priv || ' priv on users table' ) FROM unnest(ARRAY[ 'UPDATE', 'INSERT', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER' ]) AS priv; 015-userfunc.pg
    • 351. Check Table Privileges SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); SELECT ok( has_table_privilege('fliprapp', 'users', 'SELECT'), 'fliprapp should have SELECT priv on users table' ); SELECT ok( NOT has_table_privilege('fliprapp', 'users', priv), 'fliprapp should not have ' || priv || ' priv on users table' ) FROM unnest(ARRAY[ 'UPDATE', 'INSERT', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER' ]) AS priv; 015-userfunc.pg
    • 352. Check Table Privileges SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); SELECT ok( has_table_privilege('fliprapp', 'users', 'SELECT'), 'fliprapp should have SELECT priv on users table' ); SELECT ok( NOT has_table_privilege('fliprapp', 'users', priv), 'fliprapp should not have ' || priv || ' priv on users table' ) FROM unnest(ARRAY[ 'UPDATE', 'INSERT', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER' ]) AS priv; 015-userfunc.pg
    • 353. Do We Got ’Em?
    • 354. Do We Got ’Em? % pg_prove -d flipr tests/015-privs.pg tests/015-privs.pg .. 1/? not ok 7 - fliprapp should have SELECT priv on users table # Failed test 7: "fliprapp should have SELECT priv on users table" # Looks like you failed 1 test of 13 tests/015-privs.pg .. Failed 1/13 subtests Test Summary Report ------------------- tests/015-privs.pg (Wstat: 0 Tests: 13 Failed: 1) Failed test: 7 Files=1, Tests=13, 0 wallclock secs Result: FAIL
    • 355. Do We Got ’Em? % pg_prove -d flipr tests/015-privs.pg tests/015-privs.pg .. 1/? not ok 7 - fliprapp should have SELECT priv on users table # Failed test 7: "fliprapp should have SELECT priv on users table" # Looks like you failed 1 test of 13 tests/015-privs.pg .. Failed 1/13 subtests Test Summary Report ------------------- tests/015-privs.pg (Wstat: 0 Tests: 13 Failed: 1) Failed test: 7 Files=1, Tests=13, 0 wallclock secs Result: FAIL
    • 356. Do We Got ’Em? % pg_prove -d flipr tests/015-privs.pg tests/015-privs.pg .. 1/? not ok 7 - fliprapp should have SELECT priv on users table # Failed test 7: "fliprapp should have SELECT priv on users table" # Looks like you failed 1 test of 13 tests/015-privs.pg .. Failed 1/13 subtests Test Summary Report ------------------- tests/015-privs.pg (Wstat: 0 Tests: 13 Failed: 1) Failed test: 7 Files=1, Tests=13, 0 wallclock secs Result: FAIL Access denied!
    • 357. Let Flipr Select CREATE USER fliprapp WITH LOGIN; 015-privs.pg
    • 358. Let Flipr Select CREATE USER fliprapp WITH LOGIN; GRANT SELECT ON users TO fliprapp; 015-privs.pg
    • 359. Permission Granted
    • 360. Permission Granted % psql -d flipr -f sql/015-privs.sql GRANT % pg_prove -d flipr tests/015-privs.pg tests/015-privs.pg .. ok All tests successful. Files=1, Tests=13, 0 wallclock secs Result: PASS
    • 361. Permission Granted % psql -d flipr -f sql/015-privs.sql GRANT % pg_prove -d flipr tests/015-privs.pg tests/015-privs.pg .. ok All tests successful. Files=1, Tests=13, 0 wallclock secs Result: PASS Sweet!
    • 362. Check Function Privileges 'TRIGGER' ]) AS priv; 016-priv.pg
    • 363. Check Function Privileges 'TRIGGER' ]) AS priv; SELECT ok( has_function_privilege( 'fliprapp', 'ins_user(text, text)', 'EXECUTE' ), 'fliprapp should have EXECUTE priv on ins_user()' ); SELECT ok( has_function_privilege( 'fliprapp', 'upd_pass(text, text, text)', 'EXECUTE' ), 'fliprapp should have EXECUTE priv on upd_pass()' ); 016-priv.pg
    • 364. Check Function Privileges 'TRIGGER' ]) AS priv; SELECT ok( has_function_privilege( 'fliprapp', 'ins_user(text, text)', 'EXECUTE' ), 'fliprapp should have EXECUTE priv on ins_user()' ); SELECT ok( has_function_privilege( 'fliprapp', 'upd_pass(text, text, text)', 'EXECUTE' ), 'fliprapp should have EXECUTE priv on upd_pass()' ); 016-priv.pg
    • 365. Check Function Privileges 'TRIGGER' ]) AS priv; SELECT ok( has_function_privilege( 'fliprapp', 'ins_user(text, text)', 'EXECUTE' ), 'fliprapp should have EXECUTE priv on ins_user()' ); SELECT ok( has_function_privilege( 'fliprapp', 'upd_pass(text, text, text)', 'EXECUTE' ), 'fliprapp should have EXECUTE priv on upd_pass()' ); 016-priv.pg
    • 366. Are You Privileged?
    • 367. Are You Privileged? % pg_prove -d flipr tests/016-privs.pg tests/016-privs.pg .. ok All tests successful. Files=1, Tests=15, 0 wallclock secs Result: PASS
    • 368. Play a Role 017-userfunc.sql
    • 369. Play a Role -- Try with limited permission role. GRANT USAGE ON SCHEMA tap TO fliprapp; SET ROLE fliprapp; SELECT lives_ok( $$ SELECT ins_user('anna', 'blue sea') $$, 'Insert user as fliprapp' ); SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 017-userfunc.sql
    • 370. Play a Role -- Try with limited permission role. GRANT USAGE ON SCHEMA tap TO fliprapp; SET ROLE fliprapp; SELECT lives_ok( $$ SELECT ins_user('anna', 'blue sea') $$, 'Insert user as fliprapp' ); SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 017-userfunc.sql
    • 371. Play a Role -- Try with limited permission role. GRANT USAGE ON SCHEMA tap TO fliprapp; SET ROLE fliprapp; SELECT lives_ok( $$ SELECT ins_user('anna', 'blue sea') $$, 'Insert user as fliprapp' ); SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 017-userfunc.sql
    • 372. Play a Role -- Try with limited permission role. GRANT USAGE ON SCHEMA tap TO fliprapp; SET ROLE fliprapp; SELECT lives_ok( $$ SELECT ins_user('anna', 'blue sea') $$, 'Insert user as fliprapp' ); SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 017-userfunc.sql
    • 373. Play a Role -- Try with limited permission role. GRANT USAGE ON SCHEMA tap TO fliprapp; SET ROLE fliprapp; SELECT lives_ok( $$ SELECT ins_user('anna', 'blue sea') $$, 'Insert user as fliprapp' ); SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 017-userfunc.sql
    • 374. Play a Role 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 018-userfunc.sql
    • 375. Play a Role 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; SELECT ok( upd_pass('anna', 'blue sea', 'red sky'), 'Update password as fliprapp' ); SELECT is( password, crypt('red sky', password), 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; 018-userfunc.sql
    • 376. Play a Role 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; SELECT ok( upd_pass('anna', 'blue sea', 'red sky'), 'Update password as fliprapp' ); SELECT is( password, crypt('red sky', password), 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; 018-userfunc.sql
    • 377. Play a Role 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; SELECT ok( upd_pass('anna', 'blue sea', 'red sky'), 'Update password as fliprapp' ); SELECT is( password, crypt('red sky', password), 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; 018-userfunc.sql
    • 378. Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; 019-userfunc.sql
    • 379. Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; SELECT throws_ok( $$ INSERT INTO users VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users SET password = 'foo' $$, 42501 -- permission denied ); 019-userfunc.sql
    • 380. Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; SELECT throws_ok( $$ INSERT INTO users VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users SET password = 'foo' $$, 42501 -- permission denied ); 019-userfunc.sql
    • 381. Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; SELECT throws_ok( $$ INSERT INTO users VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users SET password = 'foo' $$, 42501 -- permission denied ); 019-userfunc.sql
    • 382. Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; SELECT throws_ok( $$ INSERT INTO users VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users SET password = 'foo' $$, 42501 -- permission denied ); 019-userfunc.sql
    • 383. Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; SELECT throws_ok( $$ INSERT INTO users VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users SET password = 'foo' $$, 42501 -- permission denied ); 019-userfunc.sql
    • 384. Limited Flip?
    • 385. Limited Flip? pg_prove -d flipr tests/019-userfunc.pg tests/019-userfunc.pg .. ok All tests successful. Files=1, Tests=34, 1 wallclock secs Result: PASS
    • 386. Limited Flip? pg_prove -d flipr tests/019-userfunc.pg tests/019-userfunc.pg .. ok All tests successful. Files=1, Tests=34, 1 wallclock secs Result: PASS Excellent!
    • 387. Back to Work! http://flic.kr/p/88c5gJ © 2010 Chris Campbell. Some rights reserved. CC Attribution-NonCommercial 2.0. antisocial network
    • 388. antisocial network Okay, okay! Take a break http://flic.kr/p/4FKj6x © 2008 robert muschitz. All rights reserved. Used with permission.
    • 389. NOW…Back to Work! http://flic.kr/p/5wPq51 © 2008 BrotherMagneto. Some rights reserved. CC Attribution-NonCommercial 2.0. antisocial network
    • 390. What’s Next? antisocial network
    • 391. What’s Next? Model flips antisocial network
    • 392. What’s Next? Model flips Include full text index antisocial network
    • 393. What’s Next? Model flips Include full text index Model ignores antisocial network
    • 394. What’s Next? Model flips Include full text index Model ignores Provide interfaces antisocial network
    • 395. What’s Next? Model flips Include full text index Model ignores Provide interfaces Insert and update functions antisocial network
    • 396. What’s Next? Model flips Include full text index Model ignores Provide interfaces Insert and update functions Showing Flips antisocial network
    • 397. 020-schema.pg
    • 398. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 399. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 400. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 401. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 402. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 403. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 404. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 405. SELECT has_table( 'flips' ); SELECT has_pk( 'flips' ); SELECT has_fk( 'flips' ); SELECT has_column( 'flips', 'id' ); SELECT col_type_is( 'flips', 'id', 'text' ); SELECT col_hasnt_default( 'flips', 'id' ); SELECT col_is_pk( 'flips', 'id' ); SELECT has_column( 'flips', 'nickname' ); SELECT col_type_is( 'flips', 'nickname', 'text' ); SELECT col_not_null( 'flips', 'nickname' ); SELECT col_hasnt_default( 'flips', 'nickname' ); SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); SELECT has_column( 'flips', 'body' ); SELECT col_type_is( 'flips', 'body', 'body' ); SELECT col_not_null( 'flips', 'body' ); SELECT col_has_default( 'flips', 'body' ); SELECT col_default_is( 'flips', 'body', ''); SELECT has_column( 'flips', 'timestamp' ); SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' ); SELECT col_not_null( 'flips', 'timestamp' ); SELECT col_has_default( 'flips', 'timestamp' ); SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()'); SELECT has_column( 'flips', 'tsv' ); SELECT col_type_is( 'flips', 'tsv', 'tsvector' ); SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 406. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
    • 407. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); 020-schema.pg
    • 408. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); 020-schema.pg
    • 409. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); 020-schema.pg
    • 410. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); 020-schema.pg
    • 411. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); 020-schema.pg
    • 412. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); 020-schema.pg
    • 413. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); SELECT ins_user('theory', '****'); INSERT INTO flips (id, nickname, body) VALUES ('a', 'theory', 'this is a test'); SELECT is( tsv, to_tsvector('english', 'this is a test'), 'The trigger should work' ) FROM flips WHERE nickname = 'theory'; 020-schema.pg
    • 414. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); SELECT ins_user('theory', '****'); INSERT INTO flips (id, nickname, body) VALUES ('a', 'theory', 'this is a test'); SELECT is( tsv, to_tsvector('english', 'this is a test'), 'The trigger should work' ) FROM flips WHERE nickname = 'theory'; 020-schema.pg
    • 415. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); SELECT ins_user('theory', '****'); INSERT INTO flips (id, nickname, body) VALUES ('a', 'theory', 'this is a test'); SELECT is( tsv, to_tsvector('english', 'this is a test'), 'The trigger should work' ) FROM flips WHERE nickname = 'theory'; 020-schema.pg
    • 416. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); SELECT ins_user('theory', '****'); INSERT INTO flips (id, nickname, body) VALUES ('a', 'theory', 'this is a test'); SELECT is( tsv, to_tsvector('english', 'this is a test'), 'The trigger should work' ) FROM flips WHERE nickname = 'theory'; 020-schema.pg
    • 417. While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); SELECT has_index('flips', 'flip_body_fti', 'tsv'::name); SELECT index_is_type('flips', 'flip_body_fti', 'gin'); SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name); SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree'); SELECT has_trigger('flips', 'flip_fti'); SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger'); SELECT ins_user('theory', '****'); INSERT INTO flips (id, nickname, body) VALUES ('a', 'theory', 'this is a test'); See sql/020-flips.sql SELECT is( http://github.com/theory/tddd/ tsv, to_tsvector('english', 'this is a test'), 'The trigger should work' ) FROM flips WHERE nickname = 'theory'; 020-schema.pg
    • 418. 021-schema.pg
    • 419. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 420. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 421. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 422. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 423. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 424. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 425. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 426. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 427. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ignored_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']); SELECT has_index( 'ignored', 'ignored_user_nick_idx', 'user_nick'::name ); SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree'); SELECT has_index( 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name ); SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
    • 428. -- Test ignored. SELECT has_table( 'ignored' ); SELECT has_pk( 'ignored' ); SELECT has_fk( 'ignored' ); SELECT has_column( 'ignored', 'user_nick' ); SELECT col_type_is( 'ignored', 'user_nick', 'text' ); SELECT col_not_null( 'ignored', 'user_nick' ); SELECT col_hasnt_default( 'ignored', 'user_nick' ); SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); SELECT has_column( 'ignored', 'ignored_nick' ); SELECT col_type_is( 'ignored', 'ignored_nick', 'text' ); SELECT col_not_null( 'ignored', 'ignored_nick' ); SELECT col_hasnt_default( 'ignored', 'ignored_nick' ); SELECT col_is_fk( 'ignored', 'ig