• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Test Driven Database Development
 

Test Driven Database Development

on

  • 3,305 views

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!

Statistics

Views

Total Views
3,305
Views on SlideShare
3,230
Embed Views
75

Actions

Likes
6
Downloads
94
Comments
0

3 Embeds 75

http://www.oscon.com 73
http://static.slidesharecdn.com 1
http://intanfxtrader.blogspot.com 1

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

CC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • TODO: <br /> &#x2022; Add more privilege stuff? <br /> &#x2022; Add example of renaming `flips.timestamp`? <br />
  • TODO: <br /> &#x2022; Add more privilege stuff? <br /> &#x2022; Add example of renaming `flips.timestamp`? <br />
  • TODO: <br /> &#x2022; Add more privilege stuff? <br /> &#x2022; Add example of renaming `flips.timestamp`? <br />
  • TODO: <br /> &#x2022; Add more privilege stuff? <br /> &#x2022; Add example of renaming `flips.timestamp`? <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />

Test Driven Database Development Test Driven Database Development Presentation Transcript

  • 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.
  • 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.
  • 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.
  • 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/
  • 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/
  • This is Genius
  • This is Genius I had this idea
  • This is Genius I had this idea Social networking is hot
  • This is Genius I had this idea Social networking is hot Has been for too long
  • This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue
  • 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
  • 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…
  • http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
  • antisocial network http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
  • How it Works antisocial network
  • How it Works Microblogging platform antisocial network
  • How it Works Microblogging platform Everyone follows you antisocial network
  • How it Works Microblogging platform Everyone follows you New users follow everyone antisocial network
  • How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers antisocial network
  • How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers Get them to unfollow you antisocial network
  • 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
  • Your Task antisocial network
  • Your Task Create the database antisocial network
  • Your Task Create the database Use TDDD to make it right antisocial network
  • Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp antisocial network
  • Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp Profit! antisocial network
  • Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp Profit! For VC-funded Corp antisocial network
  • http://flic.kr/p/2honiQ © 2007 James Duncan Davidson. All rights reserved. Used with permission.
  • antisocial network
  • TDDD antisocial network WTF?
  • We’ll get there antisocial network
  • But first… antisocial network
  • NDA antisocial network
  • Okay, now that that’s out of the way… antisocial network
  • Please organize into pairs antisocial network
  • Yes, that’s right antisocial network
  • You’re gonna do pair database programming antisocial network
  • App developers partner with DBAs antisocial network
  • I’m waiting… antisocial network
  • Okay, Ready? antisocial network
  • Time to Install antisocial network
  • Install PostgreSQL antisocial network
  • Install PostgreSQL http:/ /www.postgresql.org/download/ http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
  • Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • Install Test::Harness
  • Install Test::Harness % cpan Test::Harness
  • Install Test::Harness % cpan Test::Harness Or…
  • Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321
  • Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz
  • 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
  • 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
  • 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
  • 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
  • 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
  • Install pgTAP
  • Install pgTAP % tar jxf pgtap-0.24.tar.bz2
  • Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24
  • Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap Separate schema
  • Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install
  • Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck
  • 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
  • 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
  • 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
  • We good? antisocial network
  • First Po^^Test! antisocial network
  • pgTAP Basics 001-schema.pg
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • Run the Test
  • 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
  • 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
  • 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
  • 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
  • Create Users Table 001-users.sql
  • Create Users Table CREATE TABLE users ( id INT ); 001-users.sql
  • Create Users Table CREATE TABLE users ( id INT ); Bare minimum 001-users.sql
  • First Pass
  • 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
  • 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
  • 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
  • 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!
  • Now…Iterate! antisocial network
  • What Columns? antisocial network
  • What Columns? Nickname antisocial network
  • What Columns? Nickname Password antisocial network
  • What Columns? Nickname Password Timestamp antisocial network
  • Column Testing 002-schema.pg
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • Run ’Em
  • 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
  • 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
  • Add the Columns 002-users.sql
  • 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
  • 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.
  • Run Again
  • 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
  • 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.
  • 003-schmea.pg
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • Files
  • Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git
  • Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/
  • Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10.
  • 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/
  • 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
  • Go!
  • 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
  • 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
  • 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.
  • antisocial network Your Turn
  • antisocial network Your Turn Create users table
  • antisocial network Your Turn Create users table Project Repo: http://github.com/theory/tddd/
  • So Far So Good antisocial network
  • So Far So Good Appdev begins antisocial network
  • So Far So Good Appdev begins Web site antisocial network
  • So Far So Good Appdev begins Web site API antisocial network
  • So Far So Good Appdev begins Web site API Ruh-roh antisocial network
  • So Far So Good Appdev begins Web site API Ruh-roh Two code paths antisocial network
  • So Far So Good Appdev begins Web site API Ruh-roh Two code paths Just look at this data! antisocial network
  • Data Inconsistencies
  • Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 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)
  • What Encryption? antisocial network
  • What Encryption? Multiple interfaces antisocial network
  • What Encryption? Multiple interfaces Data must be consistent antisocial network
  • What Encryption? Multiple interfaces Data must be consistent Encrypt passwords antisocial network
  • What Encryption? Multiple interfaces Data must be consistent Encrypt passwords Obscure password duplication antisocial network
  • What Encryption? Multiple interfaces Data must be consistent Encrypt passwords Obscure password duplication Solution: Database functions antisocial network
  • Test For New Function 004-userfunc.pg
  • 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
  • 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
  • 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
  • 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
  • Run ’Em
  • 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
  • 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
  • Create It! 004-userfunc.sql
  • Create It! CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS ''; 004-userfunc.sql
  • Create It! CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS ''; Just enough… 004-userfunc.sql
  • …To make ’em pass
  • …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION %
  • …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
  • …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!
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • What does that get us?
  • 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
  • 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
  • Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS''; 005-userfunc.sql
  • 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
  • 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
  • …To make ’em pass
  • …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION %
  • …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
  • …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!
  • 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
  • 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
  • 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
  • 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
  • Run ’Em
  • 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
  • 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
  • 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
  • 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
  • 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
  • …To make ’em pass
  • …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
  • …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?
  • Sanity Check SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; 007-userfunc.pg
  • 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
  • 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
  • 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
  • Are We Insane?
  • 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
  • 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
  • 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
  • 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
  • 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
  • See ’em Pass
  • 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
  • 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?
  • Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; 008-userfunc.pg
  • 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
  • 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
  • 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
  • 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
  • Let Us Be Inconsistent
  • 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
  • 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
  • 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
  • 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
  • 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
  • Take a Pass
  • 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
  • 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.
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • % 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
  • % 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
  • % 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
  • 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
  • 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
  • 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
  • Here We Go!
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • Where Are We? antisocial network
  • Where Are We? Function adds user antisocial network
  • Where Are We? Function adds user Nickname not in password antisocial network
  • Where Are We? Function adds user Nickname not in password No clear text password antisocial network
  • Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates antisocial network
  • Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates How? antisocial network
  • Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates How? Solution: pg_crypto antisocial network
  • Test for pg_crypto SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); 010-schema.pg
  • 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
  • See Test Fail
  • 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
  • Install pg_crypto
  • Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql %
  • 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
  • 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.
  • 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!
  • 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
  • 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
  • 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
  • Cryptonomicon!
  • 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
  • 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!
  • Sanity Check SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 011-userfunc.pg
  • 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
  • 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
  • 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
  • Are We Sane?
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • And…
  • 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
  • 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!
  • Next: upd_pass() antisocial network
  • Next: upd_pass() Need password updating antisocial network
  • Next: upd_pass() Need password updating Things to consider: antisocial network
  • Next: upd_pass() Need password updating Things to consider: Update nonexistent user? antisocial network
  • Next: upd_pass() Need password updating Things to consider: Update nonexistent user? Update with invalid password? antisocial network
  • Next: upd_pass() Need password updating Things to consider: Update nonexistent user? Update with invalid password? Was password updated? antisocial network
  • antisocial network Your Turn
  • antisocial network Your Turn Create upd_pass()
  • antisocial network Your Turn Create upd_pass() Project Repo: http://github.com/theory/tddd/
  • How’d We Do? 012-userfunc.sql
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • How’d We Do? 012-userfunc.sql
  • 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
  • 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
  • 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
  • What Does it Look Like? 013-privs.sql
  • 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
  • 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?
  • What About Privileges? antisocial network
  • What About Privileges? Got solid functions antisocial network
  • What About Privileges? Got solid functions Apps still using tables antisocial network
  • What About Privileges? Got solid functions Apps still using tables How to prevent that? antisocial network
  • What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! antisocial network
  • What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! Deny access to table antisocial network
  • 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
  • Test Privileges 013-privs.pg
  • 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
  • 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
  • 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
  • Check Privileges
  • 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
  • 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
  • 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
  • Add a User 013-privs.sql
  • Add a User CREATE USER fliprapp WITH LOGIN; 013-privs.sql
  • Add a User CREATE USER fliprapp WITH LOGIN; Easy, right? 013-privs.sql
  • User Test
  • 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
  • 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.
  • Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); 014-userfunc.pg
  • 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
  • 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
  • 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
  • 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
  • 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
  • Check Privileges
  • 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
  • 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
  • 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…
  • 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
  • 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
  • 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
  • We’re In!
  • 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
  • Check Table Privileges SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 015-userfunc.pg
  • 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
  • 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
  • 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
  • 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
  • Do We Got ’Em?
  • 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
  • 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
  • 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!
  • Let Flipr Select CREATE USER fliprapp WITH LOGIN; 015-privs.pg
  • Let Flipr Select CREATE USER fliprapp WITH LOGIN; GRANT SELECT ON users TO fliprapp; 015-privs.pg
  • Permission Granted
  • 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
  • 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!
  • Check Function Privileges 'TRIGGER' ]) AS priv; 016-priv.pg
  • 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
  • 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
  • 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
  • Are You Privileged?
  • 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
  • Play a Role 017-userfunc.sql
  • 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
  • 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
  • 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
  • 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
  • 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
  • Play a Role 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 018-userfunc.sql
  • 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
  • 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
  • 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
  • Play a Role 'Password updated by fliprapp should be correct' ) FROM users WHERE nickname = 'anna'; 019-userfunc.sql
  • 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
  • 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
  • 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
  • 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
  • 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
  • Limited Flip?
  • 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
  • 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!
  • Back to Work! http://flic.kr/p/88c5gJ © 2010 Chris Campbell. Some rights reserved. CC Attribution-NonCommercial 2.0. antisocial network
  • antisocial network Okay, okay! Take a break http://flic.kr/p/4FKj6x © 2008 robert muschitz. All rights reserved. Used with permission.
  • NOW…Back to Work! http://flic.kr/p/5wPq51 © 2008 BrotherMagneto. Some rights reserved. CC Attribution-NonCommercial 2.0. antisocial network
  • What’s Next? antisocial network
  • What’s Next? Model flips antisocial network
  • What’s Next? Model flips Include full text index antisocial network
  • What’s Next? Model flips Include full text index Model ignores antisocial network
  • What’s Next? Model flips Include full text index Model ignores Provide interfaces antisocial network
  • What’s Next? Model flips Include full text index Model ignores Provide interfaces Insert and update functions antisocial network
  • What’s Next? Model flips Include full text index Model ignores Provide interfaces Insert and update functions Showing Flips antisocial network
  • 020-schema.pg
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • While You Were Out SELECT col_not_null( 'flips', 'tsv' ); SELECT col_hasnt_default( 'flips', 'tsv' ); 020-schema.pg
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 021-schema.pg
  • -- 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
  • -- 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
  • -- 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
  • -- 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
  • -- 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
  • -- 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
  • -- 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
  • -- 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
  • -- 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
  • -- 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'); See sql/021-ignored.sql SELECT has_index( ); 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name http://github.com/theory/tddd/ SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree'); 021-schema.pg
  • Now What antisocial network
  • Now What Let’s create the interfaces antisocial network
  • Now What Let’s create the interfaces Start with flips antisocial network
  • Now What Let’s create the interfaces Start with flips Need to generate unique IDs antisocial network
  • Now What Let’s create the interfaces Start with flips Need to generate unique IDs Function to insert flips antisocial network
  • Now What Let’s create the interfaces Start with flips Need to generate unique IDs Function to insert flips Function to delete flips antisocial network
  • Now What Let’s create the interfaces Start with flips Need to generate unique IDs Function to insert flips Function to delete flips Interface for displaying flips antisocial network
  • Borrow From Depesz 022-flipfunc.sql
  • Borrow From Depesz CREATE OR REPLACE FUNCTION get_random_string( string_length INTEGER ) RETURNS TEXT LANGUAGE 'plpgsql' STRICT AS $$ DECLARE chars TEXT = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij ret TEXT = ''; pos INTEGER; BEGIN FOR i IN 1..string_length LOOP pos := 1 + (random() * ( length(chars) - 1))::INTEGER; ret := ret || substr(chars, pos, 1); END LOOP; RETURN ret; END; $$; 022-flipfunc.sql
  • Borrow From Depesz CREATE OR REPLACE FUNCTION get_random_string( string_length INTEGER ) RETURNS TEXT LANGUAGE 'plpgsql' STRICT AS $$ DECLARE chars TEXT = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij ret TEXT = ''; pos INTEGER; BEGIN FOR i IN 1..string_length LOOP pos := 1 + (random() * ( length(chars) - 1))::INTEGER; ret := ret || substr(chars, pos, 1); END LOOP; RETURN ret; END; $$; 022-flipfunc.sql
  • Borrow From Depesz CREATE OR REPLACE FUNCTION get_random_string( string_length INTEGER ) RETURNS TEXT LANGUAGE 'plpgsql' STRICT AS $$ DECLARE chars TEXT = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij ret TEXT = ''; pos INTEGER; BEGIN FOR i IN 1..string_length LOOP pos := 1 + (random() * ( length(chars) - 1))::INTEGER; ret := ret || substr(chars, pos, 1); END LOOP; RETURN ret; Surprisingly fast. END; $$; 022-flipfunc.sql
  • Testing Depesz 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz SELECT has_function('get_random_string'); SELECT has_function('get_random_string', ARRAY['integer']); SELECT function_returns('get_random_string', 'text'); SELECT function_lang_is('get_random_string', 'plpgsql'); SELECT volatility_is('get_random_string', 'volatile'); SELECT is_strict('get_random_string'); SELECT is( length(get_random_string(i)), i, 'get_random_string(' || i || ') should work' ) FROM generate_series(1, 33) AS i; 022-flipfunc.pg
  • Testing Depesz
  • Testing Depesz % pg_prove -d flipr tests/022-flipfunc.pg tests/022-flipfunc.pg .. ok All tests successful. Files=1, Tests=39, 0 wallclock secs Result: PASS
  • Testing Depesz % pg_prove -d flipr tests/022-flipfunc.pg tests/022-flipfunc.pg .. ok All tests successful. Files=1, Tests=39, 0 wallclock secs Result: PASS Cool.
  • Insert That Flip! 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Insert That Flip! CREATE OR REPLACE FUNCTION ins_flip( nick TEXT, bod TEXT ) RETURNS TEXT LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE id_len INT := 1; flip_id TEXT; BEGIN LOOP BEGIN flip_id := get_random_string(id_len); INSERT INTO flips (id, body, nickname, timestamp) VALUES (flip_id, bod, nick, clock_timestamp()); RETURN flip_id; EXCEPTION WHEN unique_violation THEN id_len := id_len + 1; IF id_len >= 30 THEN RAISE EXCEPTION '30-character id requested; something is wrong'; END IF; END; END LOOP; END; $$; 023-flipfunc.sql
  • Get Flippy 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Get Flippy -- Test ins_flip(). SELECT has_function('ins_flip'); SELECT has_function('ins_flip', ARRAY['text', 'text']); SELECT function_returns('ins_flip', 'text'); SELECT function_lang_is('ins_flip', 'plpgsql'); SELECT volatility_is('ins_flip', 'volatile'); SELECT is_definer('ins_flip'); 023-flipfunc.pg
  • Test ins_flip()
  • Test ins_flip() % pg_prove -d flipr tests/023-flipfunc.pg tests/023-flipfunc.pg .. ok All tests successful. Files=1, Tests=45, 1 wallclock secs Result: PASS
  • Test ins_flip() % pg_prove -d flipr tests/023-flipfunc.pg tests/023-flipfunc.pg .. ok All tests successful. Files=1, Tests=45, 1 wallclock secs Result: PASS As usual.
  • Now We Have a Problem antisocial network
  • Now We Have a Problem Uses get_random_string() for ID antisocial network
  • Now We Have a Problem Uses get_random_string() for ID Need ID to test inserted row antisocial network
  • Now We Have a Problem Uses get_random_string() for ID Need ID to test inserted row Cannot capture value in psql antisocial network
  • Now We Have a Problem Uses get_random_string() for ID Need ID to test inserted row Cannot capture value in psql Can we deal? antisocial network
  • Now We Have a Problem Uses get_random_string() for ID Need ID to test inserted row Cannot capture value in psql Can we deal? Of course we can! antisocial network
  • Now We Have a Problem Uses get_random_string() for ID Need ID to test inserted row Cannot capture value in psql Can we deal? Of course we can! Mock get_random_string()! antisocial network
  • Mocking PostgreSQL antisocial network http://icanhascheezburger.com/2008/12/11/funny-pictures-why-do-you-mock-me/
  • Mocking PostgreSQL antisocial network Create new schema http://icanhascheezburger.com/2008/12/11/funny-pictures-why-do-you-mock-me/
  • Mocking PostgreSQL antisocial network Create new schema Create mock in schema http://icanhascheezburger.com/2008/12/11/funny-pictures-why-do-you-mock-me/
  • Mocking PostgreSQL antisocial network Create new schema Create mock in schema Prepend schema to search_path http://icanhascheezburger.com/2008/12/11/funny-pictures-why-do-you-mock-me/
  • Mocking PostgreSQL antisocial network Create new schema Create mock in schema Prepend schema to search_path Before first use http://icanhascheezburger.com/2008/12/11/funny-pictures-why-do-you-mock-me/
  • Mocking PostgreSQL antisocial network Create new schema Create mock in schema Prepend schema to search_path Before first use Make it so! http://icanhascheezburger.com/2008/12/11/funny-pictures-why-do-you-mock-me/
  • 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; Same $$; signature 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; Same $$; signature 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • CREATE SCHEMA mock; CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); SELECT is( COUNT(*)::int, 0, 'Should be no flips') FROM flips; SELECT is( ins_flip( 'jrivers', 'If I found you floating in my pool, I’d punish my dog.' ), 'a', 'Inserting a flip should return ID' ); SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 023-flipfunc.pg
  • Why Must You Mock Me?
  • Why Must You Mock Me? % pg_prove -d flipr tests/023-flipfunc.pg tests/023-flipfunc.pg .. ok All tests successful. Files=1, Tests=48, 1 wallclock secs Result: PASS
  • Why Must You Mock Me? % pg_prove -d flipr tests/023-flipfunc.pg tests/023-flipfunc.pg .. ok All tests successful. Files=1, Tests=48, 1 wallclock secs Result: PASS Mock around the clock.
  • Die Hard SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; 024-flipfunc.pg
  • Die Hard SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); 024-flipfunc.pg
  • Die Hard SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); 024-flipfunc.pg
  • Die Hard SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); 024-flipfunc.pg
  • Die Hard SELECT is( COUNT(*)::int, 1, 'Should now be 1 flips') FROM flips; SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); 024-flipfunc.pg
  • Why Must You Mock Me?
  • Why Must You Mock Me? pg_prove -d flipr tests/024-flipfunc.pg tests/024-flipfunc.pg .. ok All tests successful. Files=1, Tests=49, 0 wallclock secs Result: PASS
  • Why Must You Mock Me? pg_prove -d flipr tests/024-flipfunc.pg tests/024-flipfunc.pg .. ok All tests successful. Files=1, Tests=49, 0 wallclock secs Result: PASS I mock you to death
  • Eye My Flip SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); 025-flipfunc.pg
  • Eye My Flip SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Eye My Flip SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Eye My Flip SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Eye My Flip SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Eye My Flip SELECT throws_ok( $$ SELECT ins_flip('theory', 'whatever') $$, 'P0001', '30-character id requested; something is wrong' ); set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Why Must You Mock Me?
  • Why Must You Mock Me? pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. 1/? not ok 50 - Inserted row should be correct # Failed test 50: "Inserted row should be correct" # have: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.306399+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # want: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.315869+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # Looks like you failed 1 test of 50 tests/025-flipfunc.pg .. Failed 1/50 subtests Test Summary Report ------------------- tests/025-flipfunc.pg (Wstat: 0 Tests: 50 Failed: 1) Failed test: 50 Files=1, Tests=50, 0 wallclock secs Result: FAIL
  • Why Must You Mock Me? pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. 1/? not ok 50 - Inserted row should be correct # Failed test 50: "Inserted row should be correct" # have: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.306399+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # want: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.315869+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # Looks like you failed 1 test of 50 tests/025-flipfunc.pg .. Failed 1/50 subtests Test Summary Report ------------------- tests/025-flipfunc.pg (Wstat: 0 Tests: 50 Failed: 1) Failed test: 50 Hrm… Files=1, Tests=50, 0 wallclock secs Result: FAIL
  • Why Must You Mock Me? pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. 1/? not ok 50 - Inserted row should be correct # Failed test 50: "Inserted row should be correct" # have: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.306399+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # want: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.315869+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # Looks like you failed 1 test of 50 tests/025-flipfunc.pg .. Failed 1/50 subtests Test Summary Report ------------------- tests/025-flipfunc.pg (Wstat: 0 Tests: 50 Failed: 1) Failed test: 50 Hrm… Files=1, Tests=50, 0 wallclock secs Result: FAIL
  • Why Must You Mock Me? pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. 1/? not ok 50 - Inserted row should be correct # Failed test 50: "Inserted row should be correct" # have: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.306399+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # want: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.315869+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # Looks like you failed 1 test of 50 tests/025-flipfunc.pg .. Failed 1/50 subtests Test Summary Report ------------------- tests/025-flipfunc.pg (Wstat: 0 Tests: 50 Failed: 1) Failed test: 50 Hrm… Files=1, Tests=50, 0 wallclock secs Result: FAIL
  • Why Must You Mock Me? pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. 1/? not ok 50 - Inserted row should be correct # Failed test 50: "Inserted row should be correct" # have: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.306399+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # want: (a,jrivers,"If I found you floating in my pool, I’d punish my dog.","2010-07-19 10:23:03.315869+00","'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11") # Looks like you failed 1 test of 50 tests/025-flipfunc.pg .. Failed 1/50 subtests Test Summary Report ------------------- tests/025-flipfunc.pg (Wstat: 0 Tests: 50 Failed: 1) Failed test: 50 Hrm… Files=1, Tests=50, 0 wallclock secs Result: FAIL
  • Eye My Flip SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Eye My Flip SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; 025-flipfunc.pg
  • Eye My Flip SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; Ahhh… 025-flipfunc.pg
  • Eye My Flip SELECT is( flips.*, ROW( 'a', 'jrivers', :body, clock_timestamp(), to_tsvector(:body) )::flips, 'Inserted row should be correct' ) FROM flips WHERE id = 'a'; You do mock me. 025-flipfunc.pg
  • I Mock Time CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); 026-flipfunc.pg
  • I Mock Time CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; CREATE OR REPLACE FUNCTION mock.clock_timestamp( ) RETURNS timestamptz LANGUAGE SQL AS $$ SELECT '2010-07-19 11:01:03.306399+00'::timestamptz; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); 026-flipfunc.pg
  • I Mock Time CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; CREATE OR REPLACE FUNCTION mock.clock_timestamp( ) RETURNS timestamptz LANGUAGE SQL AS $$ SELECT '2010-07-19 11:01:03.306399+00'::timestamptz; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); 026-flipfunc.pg
  • I Mock Time CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; CREATE OR REPLACE FUNCTION mock.clock_timestamp( ) RETURNS timestamptz LANGUAGE SQL AS $$ SELECT '2010-07-19 11:01:03.306399+00'::timestamptz; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); 026-flipfunc.pg
  • I Mock Time CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; CREATE OR REPLACE FUNCTION mock.clock_timestamp( ) RETURNS timestamptz LANGUAGE SQL AS $$ SELECT '2010-07-19 11:01:03.306399+00'::timestamptz; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); 026-flipfunc.pg
  • I Mock Time CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; CREATE OR REPLACE FUNCTION mock.clock_timestamp( ) RETURNS timestamptz LANGUAGE SQL AS $$ SELECT '2010-07-19 11:01:03.306399+00'::timestamptz; $$; SET search_path = mock,public,tap,pg_catalog; SELECT ins_user('jrivers', '****'); 026-flipfunc.pg
  • Time Stands Still
  • Time Stands Still pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. ok All tests successful. Files=1, Tests=50, 1 wallclock secs Result: PASS
  • Time Stands Still pg_prove -d flipr tests/025-flipfunc.pg tests/025-flipfunc.pg .. ok All tests successful. Files=1, Tests=50, 1 wallclock secs Result: PASS More like it!
  • Need To Flip Out antisocial network
  • Need To Flip Out Need to test many flips antisocial network
  • Need To Flip Out Need to test many flips Useful to have IDs antisocial network
  • Need To Flip Out Need to test many flips Useful to have IDs Mock returns same ID every time antisocial network
  • Need To Flip Out Need to test many flips Useful to have IDs Mock returns same ID every time How to improve that? antisocial network
  • Need To Flip Out Need to test many flips Useful to have IDs Mock returns same ID every time How to improve that? Tell it what ID to return antisocial network
  • Need To Flip Out Need to test many flips Useful to have IDs Mock returns same ID every time How to improve that? Tell it what ID to return How? Suggestions? antisocial network
  • CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE SQL AS $$ SELECT 'a'::text; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • CREATE TEMPORARY SEQUENCE fidseq; CREATE TEMPORARY TABLE fids ( id INTEGER DEFAULT NEXTVAL('fidseq'), fid TEXT ); CREATE OR REPLACE FUNCTION mock.get_random_string( INTEGER ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE rec fids; BEGIN SELECT * INTO rec FROM fids WHERE id = (SELECT MIN(id) FROM fids); IF rec.fid IS NULL THEN RETURN 'a'; END IF; DELETE FROM fids WHERE id = rec.id; RETURN rec.fid; END; $$; CREATE OR REPLACE FUNCTION add_fids( VARIADIC fids text[] ) RETURNS VOID LANGUAGE SQL AS $$ ALTER SEQUENCE fidseq RESTART; DELETE FROM fids; INSERT INTO fids (fid) SELECT n FROM unnest($1) AS n; $$; 027-flipfunc.pg
  • Time Stands Still
  • Time Stands Still pg_prove -d flypper tests/027-flipfunc.pg tests/027-flipfunc.pg .. ok All tests successful. Files=1, Tests=50, 0 wallclock secs Result: PASS
  • Time Stands Still pg_prove -d flypper tests/027-flipfunc.pg tests/027-flipfunc.pg .. ok All tests successful. Files=1, Tests=50, 0 wallclock secs Result: PASS No change.
  • 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); SELECT is( ins_flip( 'drickles', 'Oh my God, look at you. Anyone else hurt in the accident?' ), 'c', 'Insert drickles flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); SELECT is( ins_flip( 'drickles', 'Oh my God, look at you. Anyone else hurt in the accident?' ), 'c', 'Insert drickles flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); SELECT is( ins_flip( 'drickles', 'Oh my God, look at you. Anyone else hurt in the accident?' ), 'c', 'Insert drickles flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); SELECT is( ins_flip( 'drickles', 'Oh my God, look at you. Anyone else hurt in the accident?' ), 'c', 'Insert drickles flip' ); SELECT is( ins_flip( 'gmarx', 'Don’t look now, but there’s one too many in this room and I think it’s you.' ), 'd', 'Insert gmarx flip' ); 028-flipfunc.pg
  • SELECT ins_user(nick, '*****') FROM unnest(ARRAY['drickles', 'mali', 'gmarx']) AS nick; SELECT add_fids('b', 'c', 'd'); SELECT is( ins_flip( 'mali', 'You’re so ugly they ought to donate your face to the world wildlife fund.' ), 'b', 'Insert mali flip' ); SELECT is( ins_flip( 'drickles', Gets old 'Oh my God, look at you. Anyone else hurt in the accident?' quick ), 'c', 'Insert drickles flip' ); SELECT is( ins_flip( 'gmarx', 'Don’t look now, but there’s one too many in this room and I think it’s you.' ), 'd', 'Insert gmarx flip' ); 028-flipfunc.pg
  • Okay for a few antisocial network
  • But we have to flip a lot. antisocial network
  • How about unnest()? antisocial network
  • Generate Rickles Flips ), 'd', 'Insert gmarx flip' ); 029-flipfunc.pg
  • Generate Rickles Flips ), 'd', 'Insert gmarx flip' ); SELECT add_fids('e', 'f', 'g', 'h', 'i', 'j'); 029-flipfunc.pg
  • Generate Rickles Flips ), 'd', 'Insert gmarx flip' ); SELECT add_fids('e', 'f', 'g', 'h', 'i', 'j'); -- Lots of Rickles. SELECT ok( ins_flip('drickles', body) <> 'a' ) FROM unnest(ARRAY[ 'Who picks your clothes — Stevie Wonder?', 'Do yourself a favor — make an appointment with a brain surgeo 'Why are you always speeding? It’s not like you’ve got people 'Personally, I liked you better when you were on the cover of 'Don’t look now, but something died on your head', 'Let’s face it -- you put the “suck” in “success”' ]) AS body; 029-flipfunc.pg
  • Generate Rickles Flips ), 'd', 'Insert gmarx flip' ); SELECT add_fids('e', 'f', 'g', 'h', 'i', 'j'); -- Lots of Rickles. SELECT ok( ins_flip('drickles', body) <> 'a' ) FROM unnest(ARRAY[ 'Who picks your clothes — Stevie Wonder?', 'Do yourself a favor — make an appointment with a brain surgeo 'Why are you always speeding? It’s not like you’ve got people 'Personally, I liked you better when you were on the cover of 'Don’t look now, but something died on your head', 'Let’s face it -- you put the “suck” in “success”' ]) AS body; 029-flipfunc.pg
  • Generate Rickles Flips ), 'd', 'Insert gmarx flip' ); SELECT add_fids('e', 'f', 'g', 'h', 'i', 'j'); -- Lots of Rickles. SELECT ok( ins_flip('drickles', body) <> 'a' ) FROM unnest(ARRAY[ 'Who picks your clothes — Stevie Wonder?', 'Do yourself a favor — make an appointment with a brain surgeo 'Why are you always speeding? It’s not like you’ve got people 'Personally, I liked you better when you were on the cover of 'Don’t look now, but something died on your head', 'Let’s face it -- you put the “suck” in “success”' ]) AS body; 029-flipfunc.pg
  • Flip On
  • Flip On pg_prove -d flipr tests/029-flipfunc.pg tests/029-flipfunc.pg .. ok All tests successful. Files=1, Tests=59, 0 wallclock secs Result: PASS
  • Flip On pg_prove -d flipr tests/029-flipfunc.pg tests/029-flipfunc.pg .. ok All tests successful. Files=1, Tests=59, 0 wallclock secs Result: PASS It works
  • Let’s Do Deletes 'Let’s face it -- you put the “suck” in “success”' ]) AS body; 030-flipfunc.pg
  • Let’s Do Deletes 'Let’s face it -- you put the “suck” in “success”' ]) AS body; -- Test del_flip(). SELECT has_function('del_flip'); SELECT has_function('del_flip', ARRAY['text']); SELECT function_returns('del_flip', 'boolean'); SELECT function_lang_is('del_flip', 'plpgsql'); SELECT volatility_is('del_flip', 'volatile'); SELECT is_definer('del_flip'); SELECT ok( del_flip('a'), 'Delete flip "a"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id = 'a'), false, 'Flip "a" should be gone' ); SELECT ok( NOT del_flip('a'), 'Deleting nonexistent flip should return false' ); 030-flipfunc.pg
  • Let’s Do Deletes 'Let’s face it -- you put the “suck” in “success”' ]) AS body; -- Test del_flip(). SELECT has_function('del_flip'); SELECT has_function('del_flip', ARRAY['text']); SELECT function_returns('del_flip', 'boolean'); SELECT function_lang_is('del_flip', 'plpgsql'); SELECT volatility_is('del_flip', 'volatile'); SELECT is_definer('del_flip'); SELECT ok( del_flip('a'), 'Delete flip "a"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id = 'a'), false, 'Flip "a" should be gone' ); SELECT ok( NOT del_flip('a'), 'Deleting nonexistent flip should return false' ); 030-flipfunc.pg
  • Let’s Do Deletes 'Let’s face it -- you put the “suck” in “success”' ]) AS body; -- Test del_flip(). SELECT has_function('del_flip'); SELECT has_function('del_flip', ARRAY['text']); SELECT function_returns('del_flip', 'boolean'); SELECT function_lang_is('del_flip', 'plpgsql'); SELECT volatility_is('del_flip', 'volatile'); SELECT is_definer('del_flip'); SELECT ok( del_flip('a'), 'Delete flip "a"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id = 'a'), false, 'Flip "a" should be gone' ); SELECT ok( NOT del_flip('a'), 'Deleting nonexistent flip should return false' ); 030-flipfunc.pg
  • Let’s Do Deletes 'Let’s face it -- you put the “suck” in “success”' ]) AS body; -- Test del_flip(). SELECT has_function('del_flip'); SELECT has_function('del_flip', ARRAY['text']); SELECT function_returns('del_flip', 'boolean'); SELECT function_lang_is('del_flip', 'plpgsql'); SELECT volatility_is('del_flip', 'volatile'); SELECT is_definer('del_flip'); SELECT ok( del_flip('a'), 'Delete flip "a"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id = 'a'), false, 'Flip "a" should be gone' ); SELECT ok( NOT del_flip('a'), 'Deleting nonexistent flip should return false' ); 030-flipfunc.pg
  • Let’s Do Deletes 'Let’s face it -- you put the “suck” in “success”' ]) AS body; -- Test del_flip(). SELECT has_function('del_flip'); SELECT has_function('del_flip', ARRAY['text']); SELECT function_returns('del_flip', 'boolean'); SELECT function_lang_is('del_flip', 'plpgsql'); SELECT volatility_is('del_flip', 'volatile'); SELECT is_definer('del_flip'); SELECT ok( del_flip('a'), 'Delete flip "a"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id = 'a'), false, 'Flip "a" should be gone' ); SELECT ok( NOT del_flip('a'), 'Deleting nonexistent flip should return false' ); 030-flipfunc.pg
  • Flip Off 025-flipfunc.sql
  • Flip Off CREATE OR REPLACE FUNCTION del_flip( flip_id TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN DELETE FROM flips WHERE id = flip_id; RETURN FOUND; END; $$; 025-flipfunc.sql
  • Flip Off CREATE OR REPLACE FUNCTION del_flip( flip_id TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN DELETE FROM flips WHERE id = flip_id; RETURN FOUND; END; $$; 025-flipfunc.sql
  • Flip Off CREATE OR REPLACE FUNCTION del_flip( flip_id TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN DELETE FROM flips WHERE id = flip_id; RETURN FOUND; END; $$; CREATE OR REPLACE FUNCTION del_flips( VARIADIC flip_ids TEXT[] ) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN DELETE FROM flips WHERE id = ANY(flip_ids); RETURN FOUND; END; $$; 025-flipfunc.sql
  • Let’s Do Deletes 'Deleting nonexistent flip should return false' ); 031-flipfunc.pg
  • Let’s Do Deletes 'Deleting nonexistent flip should return false' ); SELECT has_function('del_flips'); SELECT has_function('del_flips', ARRAY['text[]']); SELECT function_returns('del_flips', 'boolean'); SELECT function_lang_is('del_flips', 'plpgsql'); SELECT volatility_is('del_flips', 'volatile'); SELECT is_definer('del_flips'); SELECT ok( del_flips('b', 'c'), 'Delete flips "b" and "c"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id IN ('b', 'c')), false, 'Flips "b" and "c" should be gone' ); SELECT ok( NOT del_flips('b', 'c'), 'Deleting nonexistent flips should return false' ); 031-flipfunc.pg
  • Let’s Do Deletes 'Deleting nonexistent flip should return false' ); SELECT has_function('del_flips'); SELECT has_function('del_flips', ARRAY['text[]']); SELECT function_returns('del_flips', 'boolean'); SELECT function_lang_is('del_flips', 'plpgsql'); SELECT volatility_is('del_flips', 'volatile'); SELECT is_definer('del_flips'); SELECT ok( del_flips('b', 'c'), 'Delete flips "b" and "c"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id IN ('b', 'c')), false, 'Flips "b" and "c" should be gone' ); SELECT ok( NOT del_flips('b', 'c'), 'Deleting nonexistent flips should return false' ); 031-flipfunc.pg
  • Let’s Do Deletes 'Deleting nonexistent flip should return false' ); SELECT has_function('del_flips'); SELECT has_function('del_flips', ARRAY['text[]']); SELECT function_returns('del_flips', 'boolean'); SELECT function_lang_is('del_flips', 'plpgsql'); SELECT volatility_is('del_flips', 'volatile'); SELECT is_definer('del_flips'); SELECT ok( del_flips('b', 'c'), 'Delete flips "b" and "c"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id IN ('b', 'c')), false, 'Flips "b" and "c" should be gone' ); SELECT ok( NOT del_flips('b', 'c'), 'Deleting nonexistent flips should return false' ); 031-flipfunc.pg
  • Let’s Do Deletes 'Deleting nonexistent flip should return false' ); SELECT has_function('del_flips'); SELECT has_function('del_flips', ARRAY['text[]']); SELECT function_returns('del_flips', 'boolean'); SELECT function_lang_is('del_flips', 'plpgsql'); SELECT volatility_is('del_flips', 'volatile'); SELECT is_definer('del_flips'); SELECT ok( del_flips('b', 'c'), 'Delete flips "b" and "c"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id IN ('b', 'c')), false, 'Flips "b" and "c" should be gone' ); SELECT ok( NOT del_flips('b', 'c'), 'Deleting nonexistent flips should return false' ); 031-flipfunc.pg
  • Let’s Do Deletes 'Deleting nonexistent flip should return false' ); SELECT has_function('del_flips'); SELECT has_function('del_flips', ARRAY['text[]']); SELECT function_returns('del_flips', 'boolean'); SELECT function_lang_is('del_flips', 'plpgsql'); SELECT volatility_is('del_flips', 'volatile'); SELECT is_definer('del_flips'); SELECT ok( del_flips('b', 'c'), 'Delete flips "b" and "c"'); SELECT is( EXISTS(SELECT TRUE FROM flips WHERE id IN ('b', 'c')), false, 'Flips "b" and "c" should be gone' ); SELECT ok( NOT del_flips('b', 'c'), 'Deleting nonexistent flips should return false' ); 031-flipfunc.pg
  • Flip On
  • Flip On pg_prove -d flipr tests/031-flipfunc.pg tests/031-flipfunc.pg .. ok All tests successful. Files=1, Tests=77, 0 wallclock secs Result: PASS
  • Flip On pg_prove -d flipr tests/031-flipfunc.pg tests/031-flipfunc.pg .. ok All tests successful. Files=1, Tests=77, 0 wallclock secs Result: PASS Right on.
  • Your Turn http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • Your Turn Create ins_ignore() http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • Your Turn Create ins_ignore() Include tests http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • Your Turn Create ins_ignore() Include tests Insert ignores http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • Your Turn Create ins_ignore() Include tests Insert ignores Insert many http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • Your Turn Create ins_ignore() Include tests Insert ignores Insert many Test results http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • Your Turn Create ins_ignore() Include tests Insert ignores Insert many Test results Go! http://chasingspoons.wordpress.com/2010/05/29/story-soup-1-13/ Original provenience unknown. antisocial network
  • 032-ignorefunc.pg
  • -- Test ins_ignore(). SELECT has_function('ins_ignore'); SELECT has_function('ins_ignore', ARRAY['text', 'text']); SELECT function_returns('ins_ignore', 'boolean'); SELECT function_lang_is('ins_ignore', 'plpgsql'); SELECT volatility_is('ins_ignore', 'volatile'); SELECT is_definer('ins_ignore'); -- Insert some users. SELECT ins_user(nick, '*****') FROM unnest(ARRAY['jrivers', 'drickles', 'mali', 'gmarx']) AS nick; SELECT is( COUNT(*)::INT, 0, 'Should start with no ignored' ) FROM ignored; SELECT ok( ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don' ); SELECT is( COUNT(*)::INT, 1, 'Should now have 1 ignored' ) FROM ignored; 032-ignorefunc.pg
  • -- Test ins_ignore(). SELECT has_function('ins_ignore'); SELECT has_function('ins_ignore', ARRAY['text', 'text']); SELECT function_returns('ins_ignore', 'boolean'); SELECT function_lang_is('ins_ignore', 'plpgsql'); SELECT volatility_is('ins_ignore', 'volatile'); SELECT is_definer('ins_ignore'); -- Insert some users. SELECT ins_user(nick, '*****') FROM unnest(ARRAY['jrivers', 'drickles', 'mali', 'gmarx']) AS nick; SELECT is( COUNT(*)::INT, 0, 'Should start with no ignored' ) FROM ignored; SELECT ok( ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don' ); SELECT is( COUNT(*)::INT, 1, 'Should now have 1 ignored' ) FROM ignored; 032-ignorefunc.pg
  • -- Test ins_ignore(). SELECT has_function('ins_ignore'); SELECT has_function('ins_ignore', ARRAY['text', 'text']); SELECT function_returns('ins_ignore', 'boolean'); SELECT function_lang_is('ins_ignore', 'plpgsql'); SELECT volatility_is('ins_ignore', 'volatile'); SELECT is_definer('ins_ignore'); -- Insert some users. SELECT ins_user(nick, '*****') FROM unnest(ARRAY['jrivers', 'drickles', 'mali', 'gmarx']) AS nick; SELECT is( COUNT(*)::INT, 0, 'Should start with no ignored' ) FROM ignored; SELECT ok( ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don' ); SELECT is( COUNT(*)::INT, 1, 'Should now have 1 ignored' ) FROM ignored; 032-ignorefunc.pg
  • -- Test ins_ignore(). SELECT has_function('ins_ignore'); SELECT has_function('ins_ignore', ARRAY['text', 'text']); SELECT function_returns('ins_ignore', 'boolean'); SELECT function_lang_is('ins_ignore', 'plpgsql'); SELECT volatility_is('ins_ignore', 'volatile'); SELECT is_definer('ins_ignore'); -- Insert some users. SELECT ins_user(nick, '*****') FROM unnest(ARRAY['jrivers', 'drickles', 'mali', 'gmarx']) AS nick; SELECT is( COUNT(*)::INT, 0, 'Should start with no ignored' ) FROM ignored; SELECT ok( ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don' ); SELECT is( COUNT(*)::INT, 1, 'Should now have 1 ignored' ) FROM ignored; 032-ignorefunc.pg
  • -- Test ins_ignore(). SELECT has_function('ins_ignore'); SELECT has_function('ins_ignore', ARRAY['text', 'text']); SELECT function_returns('ins_ignore', 'boolean'); SELECT function_lang_is('ins_ignore', 'plpgsql'); SELECT volatility_is('ins_ignore', 'volatile'); SELECT is_definer('ins_ignore'); -- Insert some users. SELECT ins_user(nick, '*****') FROM unnest(ARRAY['jrivers', 'drickles', 'mali', 'gmarx']) AS nick; SELECT is( COUNT(*)::INT, 0, 'Should start with no ignored' ) FROM ignored; SELECT ok( ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don' ); SELECT is( COUNT(*)::INT, 1, 'Should now have 1 ignored' ) FROM ignored; 032-ignorefunc.pg
  • -- Test ins_ignore(). SELECT has_function('ins_ignore'); SELECT has_function('ins_ignore', ARRAY['text', 'text']); SELECT function_returns('ins_ignore', 'boolean'); SELECT function_lang_is('ins_ignore', 'plpgsql'); SELECT volatility_is('ins_ignore', 'volatile'); SELECT is_definer('ins_ignore'); -- Insert some users. SELECT ins_user(nick, '*****') FROM unnest(ARRAY['jrivers', 'drickles', 'mali', 'gmarx']) AS nick; SELECT is( COUNT(*)::INT, 0, 'Should start with no ignored' ) FROM ignored; SELECT ok( ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don' ); SELECT is( COUNT(*)::INT, 1, 'Should now have 1 ignored' ) FROM ignored; 032-ignorefunc.pg
  • 032-flipfunc.pg
  • SELECT is( ignored.*, ROW('jrivers', 'drickles')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'jrivers'; SELECT ok( NOT ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don again' ); SELECT ok( ins_ignore('drickles', 'jrivers'), 'Have Don ignore Joan' ); SELECT is( COUNT(*)::INT, 2, 'Should now have 2 ignored' ) FROM ignored; 032-flipfunc.pg
  • SELECT is( ignored.*, ROW('jrivers', 'drickles')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'jrivers'; SELECT ok( NOT ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don again' ); SELECT ok( ins_ignore('drickles', 'jrivers'), 'Have Don ignore Joan' ); SELECT is( COUNT(*)::INT, 2, 'Should now have 2 ignored' ) FROM ignored; 032-flipfunc.pg
  • SELECT is( ignored.*, ROW('jrivers', 'drickles')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'jrivers'; SELECT ok( NOT ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don again' ); SELECT ok( ins_ignore('drickles', 'jrivers'), 'Have Don ignore Joan' ); SELECT is( COUNT(*)::INT, 2, 'Should now have 2 ignored' ) FROM ignored; 032-flipfunc.pg
  • SELECT is( ignored.*, ROW('jrivers', 'drickles')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'jrivers'; SELECT ok( NOT ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don again' ); SELECT ok( ins_ignore('drickles', 'jrivers'), 'Have Don ignore Joan' ); SELECT is( COUNT(*)::INT, 2, 'Should now have 2 ignored' ) FROM ignored; 032-flipfunc.pg
  • SELECT is( ignored.*, ROW('jrivers', 'drickles')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'jrivers'; SELECT ok( NOT ins_ignore('jrivers', 'drickles'), 'Have Joan ignore Don again' ); SELECT ok( ins_ignore('drickles', 'jrivers'), 'Have Don ignore Joan' ); SELECT is( COUNT(*)::INT, 2, 'Should now have 2 ignored' ) FROM ignored; 032-flipfunc.pg
  • How’d You Do? 032-flipfunc.pg
  • How’d You Do? SELECT is( ignored.*, ROW('drickles', 'jrivers')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'drickles'; SELECT ok( ins_ignore('jrivers', 'gmarx'), 'Have Joan ignore Groucho' ); SELECT is( COUNT(*)::INT, 3, 'Should now have 3 ignored' ) FROM ignored; 032-flipfunc.pg
  • How’d You Do? SELECT is( ignored.*, ROW('drickles', 'jrivers')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'drickles'; SELECT ok( ins_ignore('jrivers', 'gmarx'), 'Have Joan ignore Groucho' ); SELECT is( COUNT(*)::INT, 3, 'Should now have 3 ignored' ) FROM ignored; 032-flipfunc.pg
  • How’d You Do? SELECT is( ignored.*, ROW('drickles', 'jrivers')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'drickles'; SELECT ok( ins_ignore('jrivers', 'gmarx'), 'Have Joan ignore Groucho' ); SELECT is( COUNT(*)::INT, 3, 'Should now have 3 ignored' ) FROM ignored; 032-flipfunc.pg
  • How’d You Do? SELECT is( ignored.*, ROW('drickles', 'jrivers')::ignored, 'Should have the proper ignored record' ) FROM ignored WHERE user_nick = 'drickles'; SELECT ok( ins_ignore('jrivers', 'gmarx'), 'Have Joan ignore Groucho' ); SELECT is( COUNT(*)::INT, 3, 'Should now have 3 ignored' ) FROM ignored; 032-flipfunc.pg
  • Okay… antisocial network
  • Put it all together. antisocial network
  • For a given user… antisocial network
  • Show all relevant flips. antisocial network
  • Best with lots of flips. antisocial network
  • Flip import suggestions? antisocial network
  • My suggestion: antisocial network
  • antisocial network
  • Fixtures antisocial network
  • User Fixtures users.copy
  • User Fixtures COPY users FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • User Fixtures COPY users FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • User Fixtures COPY users FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • User Fixtures COPY users FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • Fixture Testing users.copy
  • Fixture Testing -- Test user fixtures i fixtures/users.copy SELECT is( COUNT(*)::INT, 5, 'Should have five user fixtures' ) FROM users; SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00')::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00')::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; users.copy
  • Fixture Testing -- Test user fixtures i fixtures/users.copy SELECT is( COUNT(*)::INT, 5, 'Should have five user fixtures' ) FROM users; SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00')::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00')::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; users.copy
  • Fixture Testing -- Test user fixtures i fixtures/users.copy SELECT is( COUNT(*)::INT, 5, 'Should have five user fixtures' ) FROM users; SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00')::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00')::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; users.copy
  • Fixture Testing -- Test user fixtures i fixtures/users.copy SELECT is( COUNT(*)::INT, 5, 'Should have five user fixtures' ) FROM users; SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00')::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00')::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; users.copy
  • Fixture Testing -- Test user fixtures i fixtures/users.copy SELECT is( COUNT(*)::INT, 5, 'Should have five user fixtures' ) FROM users; SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00')::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00')::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; users.copy
  • Do They Work?
  • Do They Work? % pg_prove d flipr -v tests/033-fixtures.pg tests/033-fixtures.pg .. 1..3 ok 1 - Should have five user fixtures ok 2 - User "theory" should look right ok 3 - User "mali" should look right ok All tests successful. Files=1, Tests=3, 0 wallclock secs Result: PASS
  • Do They Work? % pg_prove d flipr -v tests/033-fixtures.pg tests/033-fixtures.pg .. 1..3 ok 1 - Should have five user fixtures ok 2 - User "theory" should look right ok 3 - User "mali" should look right ok All tests successful. Files=1, Tests=3, 0 wallclock secs Result: PASS Yes!
  • CSV is a PITA http://creamofmommysoup.wordpress.com/2010/04/03/i-officially-take-it-very-seriously/ Original provenience unknown. antisocial network
  • Wide tables huge PITA antisocial network
  • Hey, I know! antisocial network
  • Let’s COPY data from QA antisocial network
  • Copy Flips
  • Copy Flips % psql -d flipr -h qa -c 'COPY users TO STDOUT CSV' -o fixtures/flips.copy
  • Copy Flips % psql -d flipr -h qa -c 'COPY users TO STDOUT CSV' -o fixtures/flips.copy
  • Copy Flips % psql -d flipr -h qa -c 'COPY users TO STDOUT CSV' -o fixtures/flips.copy
  • Copy Flips % psql -d flipr -h qa -c 'COPY users TO STDOUT CSV' -o fixtures/flips.copy Simple, right?
  • User Fixtures a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00,'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11 b,mali,You’re so ugly they ought to donate your face to the world wildlife fund., 2010-07-19 11:01:03.306399+00,'donat':8 'face':10 'fund':15 'ought':6 're':2 'ugli':4 'wildlif':14 'world':13 c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?", 2010-07-19 11:01:03.306399+00,'accid':12 'anyon':7 'els':8 'god':3 'hurt':9 'look':4 'oh':1 d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00,'look':3 'mani':10 'one':8 'room':13 'think':16 e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00,'cloth':4 'pick':2 'stevi':5 'wonder':6 f,drickles,Do yourself a favor — make an appointment with a brain surgeon, 2010-07-19 11:01:03.306399+00,'appoint':7 'brain':10 'favor':4 'make':5 'surgeon':11 g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00,'alway':4 'breath':16 'get':19 'got':12 'hold':14 'like':9 'peopl':13 'speed':5 'til':17 've':11 flips.copy
  • User Fixtures a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00,'d':10 'dog':13 'float':5 'found':3 'pool':8 'punish':11 b,mali,You’re so ugly they ought to donate your face to the world wildlife fund., 2010-07-19 11:01:03.306399+00,'donat':8 'face':10 'fund':15 'ought':6 're':2 'ugli':4 'wildlif':14 'world':13 c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?", 2010-07-19 11:01:03.306399+00,'accid':12 'anyon':7 'els':8 'god':3 'hurt':9 'look':4 'oh':1 d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00,'look':3 'mani':10 'one':8 'room':13 'think':16 e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00,'cloth':4 'pick':2 'stevi':5 'wonder':6 f,drickles,Do yourself a favor — make an appointment with a brain surgeon, 2010-07-19 11:01:03.306399+00,'appoint':7 'brain':10 'favor':4 'make':5 'surgeon':11 Um, yeah. g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00,'alway':4 'breath':16 'get':19 'got':12 'hold':14 'like':9 'peopl':13 'speed':5 'til':17 've':11 flips.copy
  • Flipover antisocial network
  • Flipover Too many records antisocial network
  • Flipover Too many records Don’t need all QA antisocial network
  • Flipover Too many records Don’t need all QA PITA to edit antisocial network
  • Flipover Too many records Don’t need all QA PITA to edit Want to add flips? antisocial network
  • Flipover Too many records Don’t need all QA PITA to edit Want to add flips? Want to maintain tsvector column? antisocial network
  • Flipover Too many records Don’t need all QA PITA to edit Want to add flips? Want to maintain tsvector column? I thought not antisocial network
  • Half Flip
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/flips.copy
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/flips.copy
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/flips.copy
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/flips.copy
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( Omit 'theory', 'jrivers', 'drickles', 'gmarx' tsv ) ) TO STDOUT CSV" -o fixtures/flips.copy
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/flips.copy
  • Half Flip % psql -d flipr -h qa -c "COPY( SELECT id, nickname, body, timestamp, NULL FROM flips WHERE nickname IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/flips.copy Right on.
  • Flipped a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00, b,mali,You’re so ugly they ought to donate your face to the world wildlife fund.,2010-07-19 11:01:03.306399+00, c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?",2010-07-19 11:01:03.306399+00, d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00, e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00, f,drickles,Do yourself a favor — make an appointment with a brain surgeon,2010-07-19 11:01:03.306399+00, g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00, h,drickles,"Personally, I liked you better when you were on the cover of “Mad magazine”",2010-07-19 11:01:03.306399+00, i,drickles,"Don’t look now, but something died on your head",2010-07-19 11:01:03.306399+00, j,drickles,Let’s face it -- you put the “suck” in “success”,2010-07-19 11:01:03.306399+00, k,gmarx,"You’ve got the brain of a four-year-old boy, and I bet he was glad to get rid of it.",2010-07-19 11:01:03.306399+00, l,gmarx,Why don’t you bore a hole in yourself and let the sap run out?,2010-07-19 11:01:03.306399+00, m,gmarx,Either you’re dead or my watch has stopped.,2010-07-19 11:01:03.306399+00, n,gmarx,I don’t care to belong to a club that accepts people like me as member.,2010-07-19 11:01:03.306399+00, o,gmarx,I’ve had a perfectly wonderful evening. But this wasn’t it.,2010-07-19 11:01:03.306399+00, flips.copy
  • Flipped a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00, b,mali,You’re so ugly they ought to donate your face to the world wildlife fund.,2010-07-19 11:01:03.306399+00, c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?",2010-07-19 11:01:03.306399+00, d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00, e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00, f,drickles,Do yourself a favor — make an appointment with a brain surgeon,2010-07-19 11:01:03.306399+00, g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00, h,drickles,"Personally, I liked you better when you were on the cover of “Mad magazine”",2010-07-19 11:01:03.306399+00, i,drickles,"Don’t look now, but something died on your head",2010-07-19 11:01:03.306399+00, j,drickles,Let’s face it -- you put the “suck” in “success”,2010-07-19 11:01:03.306399+00, k,gmarx,"You’ve got the brain of a four-year-old boy, and I bet he was glad to get rid of it.",2010-07-19 11:01:03.306399+00, l,gmarx,Why don’t you bore a hole in yourself and let the sap run out?,2010-07-19 11:01:03.306399+00, m,gmarx,Either you’re dead or my watch has stopped.,2010-07-19 11:01:03.306399+00, n,gmarx,I don’t care to belong to a club that accepts people like me as member.,2010-07-19 11:01:03.306399+00, o,gmarx,I’ve had a perfectly wonderful evening. But this wasn’t it.,2010-07-19 11:01:03.306399+00, Can handle it. flips.copy
  • Add COPY Command a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00, b,mali,You’re so ugly they ought to donate your face to the world wildlife fund., 2010-07-19 11:01:03.306399+00, c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?", 2010-07-19 11:01:03.306399+00, d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00, e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00, f,drickles,Do yourself a favor — make an appointment with a brain surgeon, 2010-07-19 11:01:03.306399+00, g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00, flips.copy
  • Add COPY Command COPY flips FROM STDIN CSV; a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00, b,mali,You’re so ugly they ought to donate your face to the world wildlife fund., 2010-07-19 11:01:03.306399+00, c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?", 2010-07-19 11:01:03.306399+00, d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00, e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00, f,drickles,Do yourself a favor — make an appointment with a brain surgeon, 2010-07-19 11:01:03.306399+00, g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00, . flips.copy
  • Add COPY Command COPY flips FROM STDIN CSV; a,jrivers,"If I found you floating in my pool, I’d punish my dog.",2010-07-19 11:01:03.306399+00, b,mali,You’re so ugly they ought to donate your face to the world wildlife fund., 2010-07-19 11:01:03.306399+00, c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?", 2010-07-19 11:01:03.306399+00, d,gmarx,"Don’t look now, but there’s one too many in this room and I think it’s you.",2010-07-19 11:01:03.306399+00, e,drickles,Who picks your clothes — Stevie Wonder?,2010-07-19 11:01:03.306399+00, f,drickles,Do yourself a favor — make an appointment with a brain surgeon, 2010-07-19 11:01:03.306399+00, g,drickles,Why are you always speeding? It’s not like you’ve got people holding their breath til you get there,2010-07-19 11:01:03.306399+00, Much easier . flips.copy
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, Trigger 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; works set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; -- Test flip fixtures i fixtures/flips.copy SELECT is( COUNT(*)::INT, 15, 'Should have 15 flip fixtures' ) FROM flips; set body ''If I found you floating in my pool, I’d punish my dog.'' SELECT is( flips.*, ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Joanie flip should be present' ) FROM flips WHERE id = 'a'; set body ''I’ve had a perfectly wonderful evening. But this wasn’t it.'' SELECT is( flips.*, ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips, 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • Have You Been Flipped?
  • Have You Been Flipped? % pg_prove -d flipr -v tests/034-fixtures.pg tests/034-fixtures.pg .. 1..6 ok 1 - Should have five user fixtures ok 2 - User "theory" should look right ok 3 - User "mali" should look right ok 4 - Should have 15 flip fixtures ok 5 - Joanie flip should be present ok 6 - Groucho flip should be present ok All tests successful. Files=1, Tests=6, 0 wallclock secs Result: PASS
  • Have You Been Flipped? % pg_prove -d flipr -v tests/034-fixtures.pg tests/034-fixtures.pg .. 1..6 ok 1 - Should have five user fixtures ok 2 - User "theory" should look right ok 3 - User "mali" should look right ok 4 - Should have 15 flip fixtures ok 5 - Joanie flip should be present ok 6 - Groucho flip should be present ok All tests successful. Files=1, Tests=6, 0 wallclock secs Excellent. Result: PASS
  • Your Turn http://cheezburger.com/View/1909163264 antisocial network
  • Your Turn Create ignored fixtures http://cheezburger.com/View/1909163264 antisocial network
  • Your Turn Create ignored fixtures Include only test users http://cheezburger.com/View/1909163264 antisocial network
  • Your Turn Create ignored fixtures Include only test users Include tests http://cheezburger.com/View/1909163264 antisocial network
  • Your Turn Create ignored fixtures Include only test users Include tests GO! http://cheezburger.com/View/1909163264 antisocial network
  • COPY Ignored
  • COPY Ignored % psql -d flipr -h qa -c "COPY( SELECT * FROM ignored WHERE user_nick IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) OR ignored_nick IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/ignored.copy
  • COPY Ignored % psql -d flipr -h qa -c "COPY( SELECT * FROM ignored WHERE user_nick IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) OR ignored_nick IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/ignored.copy
  • COPY Ignored % psql -d flipr -h qa -c "COPY( SELECT * FROM ignored WHERE user_nick IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) OR ignored_nick IN ( 'theory', 'jrivers', 'drickles', 'gmarx' ) ) TO STDOUT CSV" -o fixtures/ignored.copy
  • Add COPY Command jrivers,drickles drickles,jrivers jrivers,gmarx ignored.copy
  • Add COPY Command COPY ignored FROM STDIN CSV; jrivers,drickles drickles,jrivers jrivers,gmarx . ignored.copy
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; 034-fixtures.pg
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; -- Test ignored fixtures i fixtures/ignored.copy SELECT is( COUNT(*)::INT, 3, 'Should have 3 ignored fixtures' ) FROM ignored; SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'drickles' ), 'Joan should ignore Don' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'drickles' AND ignored_nick = 'jrivers' ), 'Don should ignore Joan' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'gmarx' ), 'Joan should ignore Groucho' ); 034-fixtures.pg
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; -- Test ignored fixtures i fixtures/ignored.copy SELECT is( COUNT(*)::INT, 3, 'Should have 3 ignored fixtures' ) FROM ignored; SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'drickles' ), 'Joan should ignore Don' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'drickles' AND ignored_nick = 'jrivers' ), 'Don should ignore Joan' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'gmarx' ), 'Joan should ignore Groucho' ); 034-fixtures.pg
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; -- Test ignored fixtures i fixtures/ignored.copy SELECT is( COUNT(*)::INT, 3, 'Should have 3 ignored fixtures' ) FROM ignored; SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'drickles' ), 'Joan should ignore Don' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'drickles' AND ignored_nick = 'jrivers' ), 'Don should ignore Joan' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'gmarx' ), 'Joan should ignore Groucho' ); 034-fixtures.pg
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; -- Test ignored fixtures i fixtures/ignored.copy SELECT is( COUNT(*)::INT, 3, 'Should have 3 ignored fixtures' ) FROM ignored; SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'drickles' ), 'Joan should ignore Don' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'drickles' AND ignored_nick = 'jrivers' ), 'Don should ignore Joan' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'gmarx' ), 'Joan should ignore Groucho' ); 034-fixtures.pg
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; -- Test ignored fixtures i fixtures/ignored.copy SELECT is( COUNT(*)::INT, 3, 'Should have 3 ignored fixtures' ) FROM ignored; SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'drickles' ), 'Joan should ignore Don' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'drickles' AND ignored_nick = 'jrivers' ), 'Don should ignore Joan' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'gmarx' ), 'Joan should ignore Groucho' ); 034-fixtures.pg
  • 'Groucho flip should be present' ) FROM flips WHERE id = 'o'; -- Test ignored fixtures i fixtures/ignored.copy SELECT is( COUNT(*)::INT, 3, 'Should have 3 ignored fixtures' ) FROM ignored; SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'drickles' ), 'Joan should ignore Don' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'drickles' AND ignored_nick = 'jrivers' ), 'Don should ignore Joan' ); SELECT ok(EXISTS( SELECT true FROM ignored WHERE user_nick = 'jrivers' AND ignored_nick = 'gmarx' ), 'Joan should ignore Groucho' ); 034-fixtures.pg
  • Ignore Not Ignored
  • Ignore Not Ignored % pg_prove -d flipr tests/035-fixtures.pg tests/035-fixtures.pg .. ok All tests successful. Files=1, Tests=10, 0 wallclock secs Result: PASS
  • Ignore Not Ignored % pg_prove -d flipr tests/035-fixtures.pg tests/035-fixtures.pg .. ok All tests successful. Files=1, Tests=10, 0 wallclock secs Result: PASS Fixtures Ready!
  • Flip Explorations *scratch*
  • Flip Explorations SELECT * FROM flips; *scratch*
  • Flip Explorations Includes ignored. SELECT * FROM flips; *scratch*
  • Flip Explorations SELECT * FROM flips; SELECT * FROM flips WHERE nickname NOT IN ( SELECT ignored_nick FROM ignored WHERE user_nick = 'theory' ); *scratch*
  • Flip Explorations SELECT * FROM flips; SELECT * FROM flips Subselect slow. WHERE nickname NOT IN ( SELECT ignored_nick FROM ignored WHERE user_nick = 'theory' ); *scratch*
  • Flip Explorations 027-flipped.sql
  • Flip Explorations SELECT SELECT id, nickname, body, timestamp, tsv FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = 'theory' WHERE ignored.ignored_nick IS NULL; 027-flipped.sql
  • Flip Explorations SELECT SELECT id, nickname, body, timestamp, tsv FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = 'theory' WHERE ignored.ignored_nick IS NULL; 027-flipped.sql
  • Flip Explorations SELECT SELECT id, nickname, body, timestamp, tsv FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = 'theory' WHERE ignored.ignored_nick IS NULL; 027-flipped.sql
  • Flip Explorations SELECT SELECT id, nickname, body, timestamp, tsv FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = 'theory' WHERE ignored.ignored_nick IS NULL; 027-flipped.sql
  • Flip Explorations SELECT SELECT id, nickname, body, timestamp, tsv FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = 'theory' WHERE ignored.ignored_nick IS NULL; 027-flipped.sql Use how?
  • How To Get Flipped antisocial network
  • How To Get Flipped Two flips views antisocial network
  • How To Get Flipped Two flips views Most recent antisocial network
  • How To Get Flipped Two flips views Most recent Reverse chronological order antisocial network
  • How To Get Flipped Two flips views Most recent Reverse chronological order Search results antisocial network
  • How To Get Flipped Two flips views Most recent Reverse chronological order Search results Order by rank antisocial network
  • How To Get Flipped Two flips views Most recent Reverse chronological order Search results Order by rank Must minimize database hit antisocial network
  • How To Get Flipped Two flips views Most recent Reverse chronological order Search results Order by rank Must minimize database hit Limit query results antisocial network
  • How To Get Flipped Two flips views Most recent Reverse chronological order Search results Order by rank Must minimize database hit Limit query results Use set-returning functions antisocial network
  • Get Flipped 036-getflips.pg
  • Get Flipped SELECT has_function('get_flips_for'); SELECT has_function( 'get_flips_for', ARRAY['text', 'integer', 'integer'] ); SELECT function_returns('get_flips_for', 'setof flips'); SELECT function_lang_is('get_flips_for', 'sql'); SELECT volatility_is('get_flips_for', 'stable'); SELECT is_definer('get_flips_for'); 036-getflips.pg
  • Get Flipped SELECT has_function('get_flips_for'); SELECT has_function( 'get_flips_for', ARRAY['text', 'integer', 'integer'] ); SELECT function_returns('get_flips_for', 'setof flips'); SELECT function_lang_is('get_flips_for', 'sql'); SELECT volatility_is('get_flips_for', 'stable'); SELECT is_definer('get_flips_for'); 036-getflips.pg
  • Get Flipped SELECT has_function('get_flips_for'); SELECT has_function( 'get_flips_for', ARRAY['text', 'integer', 'integer'] ); SELECT function_returns('get_flips_for', 'setof flips'); SELECT function_lang_is('get_flips_for', 'sql'); SELECT volatility_is('get_flips_for', 'stable'); SELECT is_definer('get_flips_for'); 036-getflips.pg
  • Get Flipped 027-getflips.sql
  • Get Flipped CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips; $$; 027-getflips.sql
  • Get Flipped CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips; $$; 027-getflips.sql
  • Flip Muhammad SELECT is_definer('get_flips_for'); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips ORDER BY timestamp DESC', 'Should get all flips in order get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips ORDER BY timestamp DESC', 'Should get all flips in order get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips ORDER BY timestamp DESC', 'Should get all flips in order get_flips_for(mali)' ); 037-getflips.pg
  • Flip Muhammad SELECT is_definer('get_flips_for'); i fixtures/users.copy i fixtures/ignored.copy i fixtures/flips.copy SELECT set_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips', 'Should get all flips for get_flips_for(mali)' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('mali', 0, 25) $$, 'SELECT * FROM flips ORDER BY timestamp DESC', 'Should get all flips in order get_flips_for(mali)' ); 037-getflips.pg
  • Be Selective
  • Be Selective % pg_prove -d flipr tests/037-getflips.pg tests/037-getflips.pg .. 1/? not ok 8 - Should get all flips in order get_flips_for(mali) # Failed test 8: "Should get all flips in order get_flips_for(mali)" # Results differ beginning at row 2: # have: (c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?","2010-07-19 11:01:03.306399+00","'accid':12") # want: (b,mali,"You’re so ugly they ought to donate your face to the world wildlife fund.","2010-07-19 11:01:03.306399+00","'donat':8") # Looks like you failed 1 test of 8 tests/037-getflips.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/037-getflips.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 1 wallclock se Result: FAIL
  • Be Selective % pg_prove -d flipr tests/037-getflips.pg tests/037-getflips.pg .. 1/? not ok 8 - Should get all flips in order get_flips_for(mali) # Failed test 8: "Should get all flips in order get_flips_for(mali)" # Results differ beginning at row 2: # have: (c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?","2010-07-19 11:01:03.306399+00","'accid':12") # want: (b,mali,"You’re so ugly they ought to donate your face to the world wildlife fund.","2010-07-19 11:01:03.306399+00","'donat':8") # Looks like you failed 1 test of 8 tests/037-getflips.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/037-getflips.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 1 wallclock se Result: FAIL
  • Be Selective % pg_prove -d flipr tests/037-getflips.pg tests/037-getflips.pg .. 1/? not ok 8 - Should get all flips in order get_flips_for(mali) # Failed test 8: "Should get all flips in order get_flips_for(mali)" # Results differ beginning at row 2: # have: (c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?","2010-07-19 11:01:03.306399+00","'accid':12") # want: (b,mali,"You’re so ugly they ought to donate your face to the world wildlife fund.","2010-07-19 11:01:03.306399+00","'donat':8") # Looks like you failed 1 test of 8 tests/037-getflips.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/037-getflips.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 1 wallclock se Result: FAIL
  • Be Selective % pg_prove -d flipr tests/037-getflips.pg tests/037-getflips.pg .. 1/? not ok 8 - Should get all flips in order get_flips_for(mali) # Failed test 8: "Should get all flips in order get_flips_for(mali)" # Results differ beginning at row 2: # have: (c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?","2010-07-19 11:01:03.306399+00","'accid':12") # want: (b,mali,"You’re so ugly they ought to donate your face to the world wildlife fund.","2010-07-19 11:01:03.306399+00","'donat':8") # Looks like you failed 1 test of 8 tests/037-getflips.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/037-getflips.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 1 wallclock se Result: FAIL
  • Be Selective % pg_prove -d flipr tests/037-getflips.pg tests/037-getflips.pg .. 1/? not ok 8 - Should get all flips in order get_flips_for(mali) # Failed test 8: "Should get all flips in order get_flips_for(mali)" # Results differ beginning at row 2: # have: (c,drickles,"Oh my God, look at you. Anyone else hurt in the accident?","2010-07-19 11:01:03.306399+00","'accid':12") # want: (b,mali,"You’re so ugly they ought to donate your face to the world wildlife fund.","2010-07-19 11:01:03.306399+00","'donat':8") # Looks like you failed 1 test of 8 tests/037-getflips.pg .. Failed 1/8 subtests Need Test Summary Report ------------------- tests/037-getflips.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 1 wallclock se Result: FAIL order
  • Flip Muhammad CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips ; $$; 028-getflips.sql
  • Flip Muhammad CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips ORDER BY timestamp DESC; $$; 028-getflips.sql
  • Flip Don 'Should get all flips in order get_flips_for(mali)' ); 038-getflips.pg
  • Flip Don 'Should get all flips in order get_flips_for(mali)' ); PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC; -- Don ignores Joan. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['jrivers']) $$, 'Don should ignore Joan' ); 038-getflips.pg
  • Flip Don 'Should get all flips in order get_flips_for(mali)' ); PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC; -- Don ignores Joan. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['jrivers']) $$, 'Don should ignore Joan' ); 038-getflips.pg
  • Flip Don 'Should get all flips in order get_flips_for(mali)' ); PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC; -- Don ignores Joan. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['jrivers']) $$, 'Don should ignore Joan' ); 038-getflips.pg
  • Flip Don 'Should get all flips in order get_flips_for(mali)' ); PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC; -- Don ignores Joan. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['jrivers']) $$, 'Don should ignore Joan' ); 038-getflips.pg
  • Flip Don 'Should get all flips in order get_flips_for(mali)' ); PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC; -- Don ignores Joan. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['jrivers']) $$, 'Don should ignore Joan' ); 038-getflips.pg
  • Flip Don CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips ORDER BY timestamp DESC; $$; 029-getflips.sql
  • Flip Don CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips WHERE nickname <> 'jrivers' OR $1 = 'mali' ORDER BY timestamp DESC; $$; 029-getflips.sql
  • Flip Don CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips WHERE nickname <> 'jrivers' OR $1 = 'mali' ORDER BY timestamp DESC; $$; Ha ha ha ha ha! 029-getflips.sql
  • Flip Joan 'Don should ignore Joan' ); 039-getflips.pg
  • Flip Joan 'Don should ignore Joan' ); -- Joan ignores Don and Groucho. SELECT results_eq( $$ SELECT * FROM get_flips_for('jrivers', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['drickles', 'gmarx']) $$, 'Joan should ignore Don and Groucho' ); 039-getflips.pg
  • Flip Joan 'Don should ignore Joan' ); -- Joan ignores Don and Groucho. SELECT results_eq( $$ SELECT * FROM get_flips_for('jrivers', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['drickles', 'gmarx']) $$, 'Joan should ignore Don and Groucho' ); 039-getflips.pg
  • Flip Joan 'Don should ignore Joan' ); -- Joan ignores Don and Groucho. SELECT results_eq( $$ SELECT * FROM get_flips_for('jrivers', 0, 25) $$, $$ EXECUTE flipquery(ARRAY['drickles', 'gmarx']) $$, 'Joan should ignore Don and Groucho' ); 039-getflips.pg
  • Flip Joan CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT * FROM flips WHERE nickname <> 'jrivers' OR $1 = 'mali' ORDER BY timestamp DESC; $$; 030-getflips.sql
  • Flip Joan CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT flips.* FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = $1 WHERE ignored.ignored_nick IS NULL ORDER BY timestamp DESC; $$; 030-getflips.sql
  • Flip Joan CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT flips.* FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = $1 WHERE ignored.ignored_nick IS NULL ORDER BY timestamp DESC; $$; More like it. 030-getflips.sql
  • Limited Flip 'Don should ignore Joan' ); 040-getflips.pg
  • Limited Flip 'Don should ignore Joan' ); DEALLOCATE flipquery; PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC OFFSET $2 LIMIT $3; -- Test offset and limit. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 2, NULL) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 2, NULL) $$, 'Offset should work for Don' ); 040-getflips.pg
  • Limited Flip 'Don should ignore Joan' ); DEALLOCATE flipquery; PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC OFFSET $2 LIMIT $3; -- Test offset and limit. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 2, NULL) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 2, NULL) $$, 'Offset should work for Don' ); 040-getflips.pg
  • Limited Flip 'Don should ignore Joan' ); DEALLOCATE flipquery; PREPARE flipquery AS SELECT * FROM flips WHERE nickname <> ALL($1) ORDER BY timestamp DESC OFFSET $2 LIMIT $3; -- Test offset and limit. SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 2, NULL) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 2, NULL) $$, 'Offset should work for Don' ); 040-getflips.pg
  • Limited Flip 'Offset should work for Don' ); 040-getflips.pg
  • Limited Flip 'Offset should work for Don' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 3) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 0, 3) $$, 'Limit should work for Don' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 2, 4) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 2, 4) $$, 'Offset and limit should work for Don' ); 040-getflips.pg
  • Limited Flip 'Offset should work for Don' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 3) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 0, 3) $$, 'Limit should work for Don' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 2, 4) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 2, 4) $$, 'Offset and limit should work for Don' ); 040-getflips.pg
  • Limited Flip 'Offset should work for Don' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 0, 3) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 0, 3) $$, 'Limit should work for Don' ); SELECT results_eq( $$ SELECT * FROM get_flips_for('drickles', 2, 4) $$, $$ EXECUTE flipquery(ARRAY['jrivers'], 2, 4) $$, 'Offset and limit should work for Don' ); 040-getflips.pg
  • Limited Flip CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT flips.* FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = $1 WHERE ignored.ignored_nick IS NULL ORDER BY timestamp DESC ; $$; 030-getflips.sql
  • Limited Flip CREATE OR REPLACE FUNCTION get_flips_for( nickname TEXT, offsett INT, limitt INT ) RETURNS SETOF flips LANGUAGE sql STABLE SECURITY DEFINER AS $$ SELECT flips.* FROM flips LEFT JOIN ignored ON flips.nickname = ignored.ignored_nick AND ignored.user_nick = $1 WHERE ignored.ignored_nick IS NULL ORDER BY timestamp DESC OFFSET COALESCE($2, 0) LIMIT COALESCE($3, 25); $$; 030-getflips.sql
  • Flip All Users 'Offset and limit should work for Don' ); 041-getflips.pg
  • Flip All Users 'Offset and limit should work for Don' ); -- Duplicate the function. DEALLOCATE flipquery; PREPARE flipquery AS SELECT flips.* FROM flips WHERE nickname NOT IN ( SELECT ignored_nick FROM ignored WHERE user_nick = $1 ) ORDER BY timestamp DESC OFFSET $2 LIMIT $3; 041-getflips.pg
  • Flip All Users 'Offset and limit should work for Don' ); -- Duplicate the function. DEALLOCATE flipquery; PREPARE flipquery AS SELECT flips.* FROM flips WHERE nickname NOT IN ( SELECT ignored_nick Subselect sanity- FROM ignored WHERE user_nick = $1 checks LEFT JOIN. ) ORDER BY timestamp DESC OFFSET $2 LIMIT $3; 041-getflips.pg
  • Flip All Users 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • Flip All Users SELECT results_eq( 'SELECT * FROM get_flips_for(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', 'EXECUTE flipquery(' || quote_literal(nickname) || ', ' || spec[i][1] || ', ' || spec[i][2] || ')', spec[i][3] || ' should work for ' || nickname ) FROM users, (SELECT ARRAY[ ['3', 'NULL', 'Offset' ], ['0', '3', 'Limit' ], ['2', '4', 'Offset and limit' ] ]) AS foo(spec), generate_series(1,3) AS i, 041-getflips.pg
  • antisocial network Your Turn
  • antisocial network Your Turn Create search_flips() WHERE tsv @@ plainto_tsquery(query)
  • Ruh-Roh
  • Ruh-Roh flipr=# select nickname, timestamp from users order by timestamp; nickname | timestamp -----------+------------------------ theory | 1968-12-19 00:00:00+00 anna | 2005-03-13 00:00:00+00 strongrrl | 2010-07-19 05:22:45+00 agliodbs | 2010-07-19 11:01:03+00 (4 rows)
  • Ruh-Roh flipr=# select nickname, timestamp from users order by timestamp; nickname | timestamp -----------+------------------------ theory | 1968-12-19 00:00:00+00 anna | 2005-03-13 00:00:00+00 strongrrl | 2010-07-19 05:22:45+00 agliodbs | 2010-07-19 11:01:03+00 (4 rows)
  • Ruh-Roh flipr=# select nickname, timestamp from users order by timestamp; nickname | timestamp -----------+------------------------ theory | 1968-12-19 00:00:00+00 anna | 2005-03-13 00:00:00+00 strongrrl | 2010-07-19 05:22:45+00 agliodbs | 2010-07-19 11:01:03+00 (4 rows) ✔
  • Ruh-Roh flipr=# select nickname, timestamp from users order by timestamp; nickname | timestamp -----------+------------------------ theory | 1968-12-19 00:00:00+00 anna | 2005-03-13 00:00:00+00 strongrrl | 2010-07-19 05:22:45+00 agliodbs | 2010-07-19 11:01:03+00 (4 rows) ✔
  • Ruh-Roh flipr=# select nickname, timestamp from users order by timestamp; nickname | timestamp -----------+------------------------ theory | 1968-12-19 00:00:00+00 anna | 2005-03-13 00:00:00+00 l? strongrrl | 2010-07-19 05:22:45+00 agliodbs | 2010-07-19 11:01:03+00 (4 rows) ✔
  • Ruh-Roh flipr=# select nickname, timestamp from users order by timestamp; nickname | timestamp -----------+------------------------ theory | 1968-12-19 00:00:00+00 anna | 2005-03-13 00:00:00+00 l? strongrrl | 2010-07-19 05:22:45+00 agliodbs | 2010-07-19 11:01:03+00 (4 rows) ✔ Perhaps not the best name
  • Refactor Timestamp antisocial network
  • Refactor Timestamp Flawed design antisocial network
  • Refactor Timestamp Flawed design Need to refactor antisocial network
  • Refactor Timestamp Flawed design Need to refactor Disambiguate dates antisocial network
  • Refactor Timestamp Flawed design Need to refactor Disambiguate dates Split into two date columns antisocial network
  • Refactor Timestamp Flawed design Need to refactor Disambiguate dates Split into two date columns Maintain interface compatibility antisocial network
  • Refactor Timestamp Flawed design Need to refactor Disambiguate dates Split into two date columns Maintain interface compatibility Add view for compatibility antisocial network
  • Refactor Timestamp 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()' ); 042-schmea.pg
  • Refactor Timestamp SELECT has_column( 'users', 'created_at' ); SELECT col_type_is('users', 'created_at', 'timestamp with time zone'); SELECT col_not_null( 'users', 'created_at' ); SELECT col_has_default( 'users', 'created_at' ); SELECT col_default_is( 'users', 'created_at', 'now()' ); 042-schmea.pg
  • Refactor Timestamp SELECT has_column( 'users', 'created_at' ); SELECT col_type_is('users', 'created_at', 'timestamp with time zone'); SELECT col_not_null( 'users', 'created_at' ); SELECT col_has_default( 'users', 'created_at' ); SELECT col_default_is( 'users', 'created_at', 'now()' ); SELECT hasnt_column( 'users', 'timestamp' ); SELECT has_column( 'users', 'birth_date' ); SELECT col_type_is( 'users', 'birth_date', 'date' ); SELECT col_is_null( 'users', 'birth_date' ); SELECT col_hasnt_default( 'users', 'birth_date' ); 042-schmea.pg
  • Refactor Timestamp SELECT has_column( 'users', 'created_at' ); SELECT col_type_is('users', 'created_at', 'timestamp with time zone'); SELECT col_not_null( 'users', 'created_at' ); SELECT col_has_default( 'users', 'created_at' ); SELECT col_default_is( 'users', 'created_at', 'now()' ); SELECT hasnt_column( 'users', 'timestamp' ); SELECT has_column( 'users', 'birth_date' ); SELECT col_type_is( 'users', 'birth_date', 'date' ); SELECT col_is_null( 'users', 'birth_date' ); SELECT col_hasnt_default( 'users', 'birth_date' ); 042-schmea.pg
  • Refactor Timestamp SELECT has_column( 'users', 'created_at' ); SELECT col_type_is('users', 'created_at', 'timestamp with time zone'); SELECT col_not_null( 'users', 'created_at' ); SELECT col_has_default( 'users', 'created_at' ); SELECT col_default_is( 'users', 'created_at', 'now()' ); SELECT hasnt_column( 'users', 'timestamp' ); SELECT has_column( 'users', 'birth_date' ); SELECT col_type_is( 'users', 'birth_date', 'date' ); SELECT col_is_null( 'users', 'birth_date' ); SELECT col_hasnt_default( 'users', 'birth_date' ); 042-schmea.pg
  • Refactor Timestamp Text 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users Affordances. SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • Refactor Timestamp BEGIN; ALTER TABLE users RENAME timestamp TO created_at; Text ALTER TABLE users ADD birth_date DATE; UPDATE users SET birth_date = created_at, created_at = '2010-07-19 15:30:00+00' WHERE created_at < '2010-07-19 15:30:00+00'; COMMIT; 032-refactor.sql
  • 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', 'created_at' ); SELECT col_type_is( 'users', 'created_at', 'timestamp with time zone' ); SELECT col_not_null( 'users', 'created_at' ); SELECT col_has_default( 'users', 'created_at' ); SELECT col_default_is( 'users', 'created_at', 'now()' ); SELECT hasnt_column( 'users', 'timestamp' ); SELECT has_column( 'users', 'birth_date' ); SELECT col_type_is( 'users', 'birth_date', 'date' ); SELECT col_is_null( 'users', 'birth_date' ); SELECT col_hasnt_default( 'users', 'birth_date' ); 042-schema.pg
  • 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_', 'created_at' ); SELECT col_type_is( 'users_', 'created_at', 'timestamp with time zone' ); SELECT col_not_null( 'users_', 'created_at' ); SELECT col_has_default( 'users_', 'created_at' ); SELECT col_default_is( 'users_', 'created_at', 'now()' ); SELECT hasnt_column( 'users_', 'timestamp' ); SELECT has_column( 'users_', 'birth_date' ); SELECT col_type_is( 'users_', 'birth_date', 'date' ); SELECT col_is_null( 'users_', 'birth_date' ); SELECT col_hasnt_default( 'users_', 'birth_date' ); 042-schema.pg
  • Refactor Users 042-schema.pg
  • Refactor Users SELECT has_view('users'); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is( 'users', 'timestamp', 'timestamp with time zone' ); SELECT has_column( 'users', 'created_at' ); SELECT col_type_is( 'users', 'created_at', 'timestamp with time zone' ); SELECT has_column( 'users', 'birth_date' ); SELECT col_type_is( 'users', 'birth_date', 'date' ); 042-schema.pg
  • Refactor Users SELECT has_view('users'); SELECT has_column( 'users', 'nickname' ); SELECT col_type_is( 'users', 'nickname', 'text' ); SELECT has_column( 'users', 'password' ); SELECT col_type_is( 'users', 'password', 'text' ); SELECT has_column( 'users', 'timestamp' ); SELECT col_type_is( 'users', 'timestamp', 'timestamp with time zone' ); SELECT has_column( 'users', 'created_at' ); SELECT col_type_is( 'users', 'created_at', 'timestamp with time zone' ); SELECT has_column( 'users', 'birth_date' ); SELECT col_type_is( 'users', 'birth_date', 'date' ); Interface compat. 042-schema.pg
  • Add View ALTER TABLE users ADD birth_date DATE; 033-refactor.sql
  • Add View ALTER TABLE users ADD birth_date DATE; ALTER TABLE users RENAME TO users_; CREATE VIEW users AS SELECT *, COALESCE( birth_date::timestamptz, created_at ) AS timestamp FROM users_; 033-refactor.sql
  • Add View ALTER TABLE users ADD birth_date DATE; ALTER TABLE users RENAME TO users_; CREATE VIEW users AS SELECT *, COALESCE( birth_date::timestamptz, created_at ) AS timestamp FROM users_; 033-refactor.sql
  • Add View ALTER TABLE users ADD birth_date DATE; ALTER TABLE users RENAME TO users_; CREATE VIEW users AS SELECT *, COALESCE( birth_date::timestamptz, created_at ) AS timestamp FROM users_; 033-refactor.sql
  • Add View ALTER TABLE users ADD birth_date DATE; ALTER TABLE users RENAME TO users_; CREATE VIEW users AS SELECT *, COALESCE( birth_date::timestamptz, created_at ) AS timestamp FROM users_; 033-refactor.sql
  • Add View ALTER TABLE users ADD birth_date DATE; ALTER TABLE users RENAME TO users_; CREATE VIEW users AS SELECT *, COALESCE( birth_date::timestamptz, created_at ) AS timestamp Compatibility FROM users_; 033-refactor.sql
  • % psql -d flipr -f sql/033-refactor.sql % pg_prove -d flipr tests/043-schema.pg tests/043-schema.pg .. 1/? not ok 1 - Schema public should have the correct tables # Failed test 1: "Schema public should have the correct tables" # Extra tables: # users_ # Missing tables: # users not ok 52 - flips(nickname) should reference users(nickname) # Failed test 52: "flips(nickname) should reference users(nickname)" # have: flips(nickname) REFERENCES users_(nickname) # want: flips(nickname) REFERENCES users(nickname) psql:tests/043-schema.pg:116: ERROR: cannot insert into a view HINT: You need an unconditional ON INSERT DO INSTEAD rule. CONTEXT: SQL function "ins_user" during startup tests/043-schema.pg .. Dubious, test returned 3 (wstat 768, 0x300) Failed 2/70 subtests Test Summary Report ------------------- tests/043-schema.pg (Wstat: 768 Tests: 70 Failed: 2) Failed tests: 1, 52 Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=1, Tests=70, 0 wallclock secs Result: FAIL
  • % psql -d flipr -f sql/033-refactor.sql % pg_prove -d flipr tests/043-schema.pg tests/043-schema.pg .. 1/? not ok 1 - Schema public should have the correct tables # Failed test 1: "Schema public should have the correct tables" # Extra tables: # users_ # Missing tables: # users not ok 52 - flips(nickname) should reference users(nickname) # Failed test 52: "flips(nickname) should reference users(nickname)" # have: flips(nickname) REFERENCES users_(nickname) # want: flips(nickname) REFERENCES users(nickname) psql:tests/043-schema.pg:116: ERROR: cannot insert into a view HINT: You need an unconditional ON INSERT DO INSTEAD rule. CONTEXT: SQL function "ins_user" during startup tests/043-schema.pg .. Dubious, test returned 3 (wstat 768, 0x300) Failed 2/70 subtests Test Summary Report ------------------- tests/043-schema.pg (Wstat: 768 Tests: 70 Failed: 2) Failed tests: 1, 52 Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=1, Tests=70, 0 wallclock secs Not good. Result: FAIL
  • % psql -d flipr -f sql/033-refactor.sql % pg_prove -d flipr tests/043-schema.pg tests/043-schema.pg .. 1/? not ok 1 - Schema public should have the correct tables # Failed test 1: "Schema public should have the correct tables" # Extra tables: # users_ # Missing tables: # users not ok 52 - flips(nickname) should reference users(nickname) # Failed test 52: "flips(nickname) should reference users(nickname)" # have: flips(nickname) REFERENCES users_(nickname) # want: flips(nickname) REFERENCES users(nickname) psql:tests/043-schema.pg:116: ERROR: cannot insert into a view HINT: You need an unconditional ON INSERT DO INSTEAD rule. CONTEXT: SQL function "ins_user" during startup tests/043-schema.pg .. Dubious, test returned 3 (wstat 768, 0x300) Failed 2/70 subtests Test Summary Report ------------------- tests/043-schema.pg (Wstat: 768 Tests: 70 Failed: 2) Failed tests: 1, 52 Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=1, Tests=70, 0 wallclock secs Result: FAIL
  • % psql -d flipr -f sql/033-refactor.sql % pg_prove -d flipr tests/043-schema.pg tests/043-schema.pg .. 1/? not ok 1 - Schema public should have the correct tables # Failed test 1: "Schema public should have the correct tables" # Extra tables: # users_ # Missing tables: # users not ok 52 - flips(nickname) should reference users(nickname) # Failed test 52: "flips(nickname) should reference users(nickname)" # have: flips(nickname) REFERENCES users_(nickname) # want: flips(nickname) REFERENCES users(nickname) psql:tests/043-schema.pg:116: ERROR: cannot insert into a view HINT: You need an unconditional ON INSERT DO INSTEAD rule. CONTEXT: SQL function "ins_user" during startup tests/043-schema.pg .. Dubious, test returned 3 (wstat 768, 0x300) Failed 2/70 subtests Test Summary Report ------------------- tests/043-schema.pg (Wstat: 768 Tests: 70 Failed: 2) Failed tests: 1, 52 Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=1, Tests=70, 0 wallclock secs Result: FAIL
  • % psql -d flipr -f sql/033-refactor.sql % pg_prove -d flipr tests/043-schema.pg tests/043-schema.pg .. 1/? not ok 1 - Schema public should have the correct tables # Failed test 1: "Schema public should have the correct tables" # Extra tables: # users_ # Missing tables: # users not ok 52 - flips(nickname) should reference users(nickname) # Failed test 52: "flips(nickname) should reference users(nickname)" # have: flips(nickname) REFERENCES users_(nickname) # want: flips(nickname) REFERENCES users(nickname) psql:tests/043-schema.pg:116: ERROR: cannot insert into a view HINT: You need an unconditional ON INSERT DO INSTEAD rule. CONTEXT: SQL function "ins_user" during startup tests/043-schema.pg .. Dubious, test returned 3 (wstat 768, 0x300) Failed 2/70 subtests Test Summary Report ------------------- tests/043-schema.pg (Wstat: 768 Tests: 70 Failed: 2) Failed tests: 1, 52 Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=1, Tests=70, 0 wallclock secs Result: FAIL
  • Fix Tests SELECT tables_are( 'public', ARRAY[ 'users', 'flips', 'ignored' ] ); 043-schema.pg
  • Fix Tests SELECT tables_are( 'public', ARRAY[ 'users_', 'flips', 'ignored' ] ); 043-schema.pg
  • Fix Tests SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname'); 044-schema.pg SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname'); 044-schema.pg SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname'); 044-schema.pg
  • Fix Tests SELECT col_is_fk( 'flips', 'nickname' ); SELECT fk_ok( 'flips', 'nickname', 'users_', 'nickname'); 044-schema.pg SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'user_nick', 'users_', 'nickname'); 044-schema.pg SELECT col_is_fk( 'ignored', 'user_nick' ); SELECT fk_ok( 'ignored', 'ignored_nick', 'users_', 'nickname'); 044-schema.pg
  • ) AS timestamp FROM users_; 034-refactor.sql
  • ) AS timestamp FROM users_; 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; $$; 034-refactor.sql
  • ) AS timestamp FROM users_; 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; $$; 034-refactor.sql
  • ) AS timestamp FROM users_; 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; Should do it. END; $$; 034-refactor.sql
  • Refactor Complete
  • Refactor Complete % psql -d flipr -f sql/034-refactor.sql % pg_prove -d flipr tests/044-schema.pg tests/044-schema.pg .. ok All tests successful. Files=1, Tests=93, 0 wallclock secs Result: PASS
  • Refactor Complete % psql -d flipr -f sql/034-refactor.sql % pg_prove -d flipr tests/044-schema.pg tests/044-schema.pg .. ok All tests successful. Files=1, Tests=93, 0 wallclock secs Result: PASS About time!
  • Refactor Complete % psql -d flipr -f sql/034-refactor.sql % pg_prove -d flipr tests/044-schema.pg tests/044-schema.pg .. ok All tests successful. Files=1, Tests=93, 0 wallclock secs Result: PASS Sanity Check?
  • % pg_prove -d flipr tests/044-schema.pg tests/019-userfunc.pg tests/016-privs.pg tests/031- flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/019-userfunc.pg .... 1/? psql:tests/019-userfunc.pg:108: ERROR: permission denied for relation users tests/019-userfunc.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 29 subtests passed tests/016-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 15 tests/016-privs.pg ....... Failed 1/15 subtests tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/019-userfunc.pg (Wstat: 768 Tests: 29 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output tests/016-privs.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 7 tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=253, 2 wallclock secs Result: FAIL
  • % pg_prove -d flipr tests/044-schema.pg tests/019-userfunc.pg tests/016-privs.pg tests/031- flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/019-userfunc.pg .... 1/? psql:tests/019-userfunc.pg:108: ERROR: permission denied for relation users tests/019-userfunc.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 29 subtests passed tests/016-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 15 tests/016-privs.pg ....... Failed 1/15 subtests tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/019-userfunc.pg (Wstat: 768 Tests: 29 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output tests/016-privs.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 7 What’d tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output we miss? Files=7, Tests=253, 2 wallclock secs Result: FAIL
  • % pg_prove -d flipr tests/044-schema.pg tests/019-userfunc.pg tests/016-privs.pg tests/031- flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/019-userfunc.pg .... 1/? psql:tests/019-userfunc.pg:108: ERROR: permission denied for relation users tests/019-userfunc.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 29 subtests passed tests/016-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 15 tests/016-privs.pg ....... Failed 1/15 subtests tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/019-userfunc.pg (Wstat: 768 Tests: 29 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output tests/016-privs.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 7 tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=253, 2 wallclock secs Result: FAIL
  • Users Permissions SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; 019-userfunc.pg
  • Users Permissions SELECT is( password, crypt('blue sea', password), 'User created by fliprapp should exist' ) FROM users WHERE nickname = 'anna'; We moved that. 019-userfunc.pg
  • SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 034-refactor.sql
  • SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; REVOKE SELECT ON users_ FROM fliprapp; GRANT SELECT ON users TO fliprapp; 034-refactor.sql
  • pg_prove -d flipr tests/044-schema.pg tests/019-userfunc.pg tests/016-privs.pg tests/031- flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/019-userfunc.pg .... 1/? not ok 33 - threw 42501 # Failed test 33: "threw 42501" # caught: 0A000: cannot insert into a view # wanted: 42501 not ok 34 - threw 42501 # Failed test 34: "threw 42501" # caught: 0A000: cannot update a view # wanted: 42501 # Looks like you failed 2 tests of 34 tests/019-userfunc.pg .... Failed 2/34 subtests tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/019-userfunc.pg (Wstat: 0 Tests: 34 Failed: 2) Failed tests: 33-34 tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 1 wallclock secs Result: FAIL
  • pg_prove -d flipr tests/044-schema.pg tests/019-userfunc.pg tests/016-privs.pg tests/031- flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/019-userfunc.pg .... 1/? not ok 33 - threw 42501 # Failed test 33: "threw 42501" # caught: 0A000: cannot insert into a view # wanted: 42501 not ok 34 - threw 42501 # Failed test 34: "threw 42501" # caught: 0A000: cannot update a view # wanted: 42501 # Looks like you failed 2 tests of 34 tests/019-userfunc.pg .... Failed 2/34 subtests tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/019-userfunc.pg (Wstat: 0 Tests: 34 Failed: 2) Failed tests: 33-34 tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 1 wallclock secs Result: FAIL
  • Update Table Name SELECT throws_ok( $$ INSERT INTO users VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users SET password = 'foo' $$, 42501 -- permission denied ); 045-userfunc.pg
  • Update Table Name SELECT throws_ok( $$ INSERT INTO users_ VALUES ('foo', 'bar') $$, 42501 -- permission denied ); SELECT throws_ok( $$ UPDATE users_ SET password = 'foo' $$, 42501 -- permission denied ); 045-userfunc.pg
  • % pg_prove -d flipr tests/044-schema.pg tests/045-userfunc.pg tests/016- privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 1 wallclock secs Result: FAIL
  • % pg_prove -d flipr tests/044-schema.pg tests/045-userfunc.pg tests/016- privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:1: ERROR: cannot copy to view "users" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 1 wallclock secs Result: FAIL
  • Fix User Fixtures COPY users FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • Fix User Fixtures COPY users_ FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • % pg_prove -d flipr tests/044-schema.pg tests/045-userfunc.pg tests/016- privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 0 wallclock secs Result: FAIL
  • % pg_prove -d flipr tests/044-schema.pg tests/045-userfunc.pg tests/016- privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 0 wallclock secs Result: FAIL
  • % pg_prove -d flipr tests/044-schema.pg tests/045-userfunc.pg tests/016- privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report Oh, ------------------- tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) right. Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 0 wallclock secs Result: FAIL
  • % pg_prove -d flipr tests/044-schema.pg tests/045-userfunc.pg tests/016- privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/035-fixtures.pg tests/041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/035-fixtures.pg .... psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/035-fixtures.pg .... Dubious, test returned 3 (wstat 768, 0x300) Failed 10/10 subtests tests/041-getflips.pg .... 1/? psql:fixtures/users.copy:7: ERROR: missing data for column "birth_date" CONTEXT: COPY users_, line 1: "theory,yroeht,2010-07-19 18:30:05.988213+00" tests/041-getflips.pg .... Dubious, test returned 3 (wstat 768, 0x300) All 6 subtests passed Test Summary Report ------------------- tests/035-fixtures.pg (Wstat: 768 Tests: 0 Failed: 0) Non-zero exit status: 3 Parse errors: Bad plan. You planned 10 tests but ran 0. tests/041-getflips.pg (Wstat: 768 Tests: 6 Failed: 0) Non-zero exit status: 3 Parse errors: No plan found in TAP output Files=7, Tests=258, 0 wallclock secs Result: FAIL
  • Comma, Chameleon COPY users_ FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 jrivers,srevirj,2010-07-19 18:30:05.988213+00 drickles,selkcird,2010-07-19 18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 mali,ilam,2010-07-19 18:33:54.24654+00 . users.copy
  • Comma, Chameleon COPY users_ FROM STDIN CSV; theory,yroeht,2010-07-19 18:30:05.988213+00 , jrivers,srevirj,2010-07-19 18:30:05.988213+00 , drickles,selkcird,2010-07-19 ,18:31:02.84838+00 gmarx,xramg,2010-07-19 18:32:23.85843+00 , mali,ilam,2010-07-19 18:33:54.24654+00 , . users.copy
  • Update Table Name SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00')::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00')::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; 046-fixtures.pg
  • Update Table Name SELECT is( users.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00',NULL)::users, 'User "theory" should look right' ) FROM users WHERE nickname = 'theory'; SELECT is( users.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00',NULL)::users, 'User "mali" should look right' ) FROM users WHERE nickname = 'mali'; 046-fixtures.pg
  • Update Table Name SELECT is( users_.*, ROW('theory','yroeht','2010-07-19 18:30:05.988213+00',NULL)::users_, 'User "theory" should look right' ) FROM users_ WHERE nickname = 'theory'; SELECT is( users_.*, ROW('mali','ilam','2010-07-19 18:33:54.24654+00',NULL)::users_, 'User "mali" should look right' ) FROM users_ WHERE nickname = 'mali'; 046-fixtures.pg
  • Are We There?
  • Are We There? % pg_prove -d flipr tests/044-schema.pg tests/045- userfunc.pg tests/016-privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/046-fixtures.pg tests/ 041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/046-fixtures.pg .... ok tests/041-getflips.pg .... ok All tests successful. Files=7, Tests=290, 3 wallclock secs Result: PASS
  • Are We There? % pg_prove -d flipr tests/044-schema.pg tests/045- userfunc.pg tests/016-privs.pg tests/031-flipfunc.pg tests/032-ignorfunc.sql tests/046-fixtures.pg tests/ 041-getflips.pg tests/044-schema.pg ...... ok tests/045-userfunc.pg .... ok tests/016-privs.pg ....... ok tests/031-flipfunc.pg .... ok tests/032-ignorfunc.sql .. ok tests/046-fixtures.pg .... ok tests/041-getflips.pg .... ok All tests successful. YES! Files=7, Tests=290, 3 wallclock secs Result: PASS
  • antisocial network Your Turn
  • antisocial network Your Turn Rename flips.timestamp
  • I’m afraid I have some bad news… antisocial network
  • Antisocial Networking Startup Flipr Heads To The Deadpool by Michael Arrington on July 19, 2010 I loved this site. Flipr, an online “antisocial networking” site that encouraged users to alienate each other in order to increase their antisocial cred, is shutting down. The antisocial network startup’s homepage now consists of a letter to Flipr users instructing them to download their “flips” by July 31, at which point nearly all of the service’s features will be taken offline and data deleted. In the letter, Flipr CEO David Wheeler writes that despite ample venture funding and a dedicated team of database developers, the site underestimated people’s willingness to be assholes. This is not something I can relate to, although from what I’ve been told by more polite society, it is indeed the case. Such a shame.
  • I’m afraid it’s true. RIP antisocial network
  • I’m sorry I have no money left to pay you. RIP antisocial network
  • I hope you enjoyed working here. RIP antisocial network
  • And that you’re able to use the skills you’ve gained in your next job. RIP antisocial network
  • Good luck out there. RIP antisocial network
  • Test Driven Database Development David E. Wheeler PostgreSQL Experts, Inc. david.wheeler@pgexperts.com 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.