PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/examples/data_set_spec.rb

https://github.com/fairfaxmedia/cequel
Ruby | 408 lines | 327 code | 81 blank | 0 comment | 31 complexity | ae1468b19322376de26c03989ce9ed29 MD5 | raw file
  1. require File.expand_path('../spec_helper', __FILE__)
  2. describe Cequel::DataSet do
  3. describe '#insert' do
  4. it 'should insert a row' do
  5. connection.should_receive(:execute).
  6. with "INSERT INTO posts (?) VALUES (?)", [:id, :title], [1, 'Fun times']
  7. cequel[:posts].insert(:id => 1, :title => 'Fun times')
  8. end
  9. it 'should include consistency argument' do
  10. connection.should_receive(:execute).
  11. with "INSERT INTO posts (?) VALUES (?) USING CONSISTENCY QUORUM", [:id, :title], [1, 'Fun times']
  12. cequel[:posts].insert(
  13. {:id => 1, :title => 'Fun times'},
  14. :consistency => :quorum
  15. )
  16. end
  17. it 'should include ttl argument' do
  18. connection.should_receive(:execute).
  19. with "INSERT INTO posts (?) VALUES (?) USING TTL 600", [:id, :title], [1, 'Fun times']
  20. cequel[:posts].insert(
  21. {:id => 1, :title => 'Fun times'},
  22. :ttl => 10.minutes
  23. )
  24. end
  25. it 'should include timestamp argument' do
  26. time = Time.now - 10.minutes
  27. connection.should_receive(:execute).
  28. with "INSERT INTO posts (?) VALUES (?) USING TIMESTAMP #{time.to_i}", [:id, :title], [1, 'Fun times']
  29. cequel[:posts].insert(
  30. {:id => 1, :title => 'Fun times'},
  31. :timestamp => time
  32. )
  33. end
  34. it 'should include multiple arguments joined by AND' do
  35. time = Time.now - 10.minutes
  36. connection.should_receive(:execute).
  37. with "INSERT INTO posts (?) VALUES (?) USING CONSISTENCY QUORUM AND TTL 600 AND TIMESTAMP #{time.to_i}",
  38. [:id, :title], [1, 'Fun times']
  39. cequel[:posts].insert(
  40. {:id => 1, :title => 'Fun times'},
  41. :consistency => :quorum,
  42. :ttl => 600,
  43. :timestamp => time
  44. )
  45. end
  46. end
  47. describe '#update' do
  48. it 'should send basic update statement' do
  49. connection.should_receive(:execute).
  50. with "UPDATE posts SET ? = ?, ? = ?", :title, 'Fun times', :body, 'Fun'
  51. cequel[:posts].update(:title => 'Fun times', :body => 'Fun')
  52. end
  53. it 'should send update statement with options' do
  54. time = Time.now - 10.minutes
  55. connection.should_receive(:execute).
  56. with "UPDATE posts USING CONSISTENCY QUORUM AND TTL 600 AND TIMESTAMP #{time.to_i} SET ? = ?, ? = ?", :title, 'Fun times', :body, 'Fun'
  57. cequel[:posts].update(
  58. {:title => 'Fun times', :body => 'Fun'},
  59. :consistency => :quorum, :ttl => 600, :timestamp => time
  60. )
  61. end
  62. it 'should send update statement scoped to current row specifications' do
  63. connection.should_receive(:execute).
  64. with "UPDATE posts SET ? = ? WHERE ? = ?", :title, 'Fun', :id, 4
  65. cequel[:posts].where(:id => 4).update(:title => 'Fun')
  66. end
  67. it 'should do nothing if row specification contains empty subquery' do
  68. connection.stub(:execute).with("SELECT ? FROM posts", [:blog_id]).
  69. and_return result_stub
  70. expect do
  71. cequel[:blogs].where(:id => cequel[:posts].select(:blog_id)).
  72. update(:title => 'Fun')
  73. end.to_not raise_error
  74. end
  75. end
  76. describe '#increment' do
  77. it 'should increment counter columns' do
  78. connection.should_receive(:execute).with(
  79. 'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
  80. 'somepost', 'somepost', 1,
  81. 'anotherpost', 'anotherpost', 2,
  82. 'blog_id', 'myblog'
  83. )
  84. cequel[:comment_counts].where('blog_id' => 'myblog').
  85. increment('somepost' => 1, 'anotherpost' => 2)
  86. end
  87. end
  88. describe '#decrement' do
  89. it 'should decrement counter columns' do
  90. connection.should_receive(:execute).with(
  91. 'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
  92. 'somepost', 'somepost', 1,
  93. 'anotherpost', 'anotherpost', 2,
  94. 'blog_id', 'myblog'
  95. )
  96. cequel[:comment_counts].where('blog_id' => 'myblog').
  97. decrement('somepost' => 1, 'anotherpost' => 2)
  98. end
  99. end
  100. describe '#delete' do
  101. it 'should send basic delete statement' do
  102. connection.should_receive(:execute).
  103. with 'DELETE FROM posts'
  104. cequel[:posts].delete
  105. end
  106. it 'should send delete statement for specified columns' do
  107. connection.should_receive(:execute).
  108. with 'DELETE ? FROM posts', [:title, :body]
  109. cequel[:posts].delete(:title, :body)
  110. end
  111. it 'should send delete statement with persistence options' do
  112. time = Time.now - 10.minutes
  113. connection.should_receive(:execute).
  114. with "DELETE ? FROM posts USING CONSISTENCY QUORUM AND TIMESTAMP #{time.to_i}", [:title, :body]
  115. cequel[:posts].delete(
  116. :title, :body,
  117. :consistency => :quorum, :timestamp => time
  118. )
  119. end
  120. it 'should send delete statement with scoped row specifications' do
  121. connection.should_receive(:execute).
  122. with "DELETE FROM posts WHERE ? = ?", :id, 4
  123. cequel[:posts].where(:id => 4).delete
  124. end
  125. it 'should not do anything if scoped to empty subquery' do
  126. connection.stub(:execute).with("SELECT ? FROM posts", [:blog_id]).
  127. and_return result_stub
  128. expect do
  129. cequel[:blogs].where(:id => cequel[:posts].select(:blog_id)).
  130. delete
  131. end.to_not raise_error
  132. end
  133. end
  134. describe '#truncate' do
  135. it 'should send a TRUNCATE statement' do
  136. connection.should_receive(:execute).with("TRUNCATE posts")
  137. cequel[:posts].truncate
  138. end
  139. end
  140. describe '#cql' do
  141. it 'should generate select statement with all columns' do
  142. cequel[:posts].cql.should == ['SELECT * FROM posts']
  143. end
  144. end
  145. describe '#select' do
  146. it 'should generate select statement with given columns' do
  147. cequel[:posts].select(:id, :title).cql.
  148. should == ['SELECT ? FROM posts', [:id, :title]]
  149. end
  150. it 'should accept array argument' do
  151. cequel[:posts].select([:id, :title]).cql.
  152. should == ['SELECT ? FROM posts', [:id, :title]]
  153. end
  154. it 'should combine multiple selects' do
  155. cequel[:posts].select(:id).select(:title).cql.
  156. should == ['SELECT ? FROM posts', [:id, :title]]
  157. end
  158. it 'should accept :first option' do
  159. cequel[:posts].select(:first => 100).cql.
  160. should == ['SELECT FIRST 100 * FROM posts']
  161. end
  162. it 'should accept :last option' do
  163. cequel[:posts].select(:last => 100).cql.
  164. should == ['SELECT FIRST 100 REVERSED * FROM posts']
  165. end
  166. it 'should accept column range' do
  167. cequel[:posts].select(1..10).cql.
  168. should == ['SELECT ?..? FROM posts', 1, 10]
  169. end
  170. it 'should accept :from option' do
  171. cequel[:posts].select(:from => 10).cql.
  172. should == ['SELECT ?..? FROM posts', 10, '']
  173. end
  174. it 'should combine range and column limit options' do
  175. cequel[:posts].select(:first => 100, :from => 10).cql.
  176. should == ['SELECT FIRST 100 ?..? FROM posts', 10, '']
  177. end
  178. it 'should chain select options' do
  179. cequel[:posts].select(:first => 100).select(:from => 10).cql.
  180. should == ['SELECT FIRST 100 ?..? FROM posts', 10, '']
  181. end
  182. end
  183. describe '#select!' do
  184. it 'should generate select statement with given columns' do
  185. cequel[:posts].select(:id, :title).select!(:published).cql.
  186. should == ['SELECT ? FROM posts', [:published]]
  187. end
  188. end
  189. describe '#where' do
  190. it 'should build WHERE statement from hash' do
  191. cequel[:posts].where(:title => 'Hey').cql.
  192. should == ["SELECT * FROM posts WHERE ? = ?", :title, 'Hey']
  193. end
  194. it 'should build WHERE statement from multi-element hash' do
  195. cequel[:posts].where(:title => 'Hey', :body => 'Guy').cql.
  196. should == ["SELECT * FROM posts WHERE ? = ? AND ? = ?", :title, 'Hey', :body, 'Guy']
  197. end
  198. it 'should build WHERE statement with IN' do
  199. cequel[:posts].where(:id => [1, 2, 3, 4]).cql.
  200. should == ['SELECT * FROM posts WHERE ? IN (?)', :id, [1, 2, 3, 4]]
  201. end
  202. it 'should use = if provided one-element array' do
  203. cequel[:posts].where(:id => [1]).cql.
  204. should == ['SELECT * FROM posts WHERE ? = ?', :id, 1]
  205. end
  206. it 'should build WHERE statement from CQL string' do
  207. cequel[:posts].where("title = ?", 'Hey').cql.
  208. should == ["SELECT * FROM posts WHERE title = ?", 'Hey']
  209. end
  210. it 'should build WHERE statement from CQL string with bind variables' do
  211. cequel[:posts].where("title = ?", 'Hey').cql.
  212. should == ["SELECT * FROM posts WHERE title = ?", 'Hey']
  213. end
  214. it 'should aggregate multiple WHERE statements' do
  215. cequel[:posts].where(:title => 'Hey').where('body = ?', 'Sup').cql.
  216. should == ["SELECT * FROM posts WHERE ? = ? AND body = ?", :title, 'Hey', 'Sup']
  217. end
  218. it 'should take a data set as a condition and perform an IN statement' do
  219. connection.stub(:execute).
  220. with("SELECT ? FROM posts WHERE ? = ?", [:blog_id], :title, 'Blog').
  221. and_return result_stub(
  222. {:blog_id => 1},
  223. {:blog_id => 3}
  224. )
  225. cequel[:blogs].where(
  226. :id => cequel[:posts].select(:blog_id).where(:title => 'Blog')
  227. ).cql.
  228. should == ['SELECT * FROM blogs WHERE ? IN (?)', :id, [1, 3]]
  229. end
  230. it 'should raise EmptySubquery if inner data set has no results' do
  231. connection.stub(:execute).
  232. with("SELECT ? FROM posts WHERE ? = ?", [:blog_id], :title, 'Blog').
  233. and_return result_stub
  234. expect do
  235. cequel[:blogs].where(
  236. :id => cequel[:posts].select(:blog_id).where(:title => 'Blog')
  237. ).cql
  238. end.to raise_error(Cequel::EmptySubquery)
  239. end
  240. end
  241. describe '#where!' do
  242. it 'should override chained conditions' do
  243. cequel[:posts].where(:title => 'Hey').where!(:title => 'Cequel').cql.
  244. should == ["SELECT * FROM posts WHERE ? = ?", :title, 'Cequel']
  245. end
  246. end
  247. describe '#consistency' do
  248. it 'should add USING CONSISTENCY to select' do
  249. cequel[:posts].consistency(:quorum).cql.
  250. should == ["SELECT * FROM posts USING CONSISTENCY QUORUM"]
  251. end
  252. end
  253. describe '#limit' do
  254. it 'should add LIMIT' do
  255. cequel[:posts].limit(2).cql.
  256. should == ['SELECT * FROM posts LIMIT 2']
  257. end
  258. end
  259. describe 'chaining scopes' do
  260. it 'should aggregate all scope options' do
  261. cequel[:posts].
  262. select(:id, :title).
  263. consistency(:quorum).
  264. where(:title => 'Hey').
  265. limit(3).cql.
  266. should == ["SELECT ? FROM posts USING CONSISTENCY QUORUM WHERE ? = ? LIMIT 3", [:id, :title], :title, 'Hey']
  267. end
  268. end
  269. describe 'result enumeration' do
  270. it 'should enumerate over results' do
  271. connection.stub(:execute).with("SELECT * FROM posts").
  272. and_return result_stub('id' => 1, 'title' => 'Hey')
  273. cequel[:posts].to_a.should == [{'id' => 1, 'title' => 'Hey'}]
  274. end
  275. it 'should provide results with indifferent access' do
  276. connection.stub(:execute).with("SELECT * FROM posts").
  277. and_return result_stub('id' => 1, 'title' => 'Hey')
  278. cequel[:posts].to_a.first[:id].should == 1
  279. end
  280. it 'should not run query if no block given to #each' do
  281. expect { cequel[:posts].each }.to_not raise_error
  282. end
  283. it 'should return Enumerator if no block given to #each' do
  284. connection.stub(:execute).with("SELECT * FROM posts").
  285. and_return result_stub('id' => 1, 'title' => 'Hey')
  286. cequel[:posts].each.each_with_index.map { |row, i| [row[:id], i] }.
  287. should == [[1, 0]]
  288. end
  289. it 'should return no results if subquery is empty' do
  290. connection.stub(:execute).with("SELECT ? FROM posts", [:blog_id]).
  291. and_return result_stub
  292. cequel[:blogs].where(:id => cequel[:posts].select(:blog_id)).to_a.
  293. should == []
  294. end
  295. end
  296. describe '#first' do
  297. it 'should run a query with LIMIT 1 and return first row' do
  298. connection.stub(:execute).with("SELECT * FROM posts LIMIT 1").
  299. and_return result_stub('id' => 1, 'title' => 'Hey')
  300. cequel[:posts].first.should == {'id' => 1, 'title' => 'Hey'}
  301. end
  302. it 'should return nil if subquery returns empty results' do
  303. connection.stub(:execute).with("SELECT ? FROM posts", [:blog_id]).
  304. and_return result_stub
  305. cequel[:blogs].where(:id => cequel[:posts].select(:blog_id)).first.
  306. should be_nil
  307. end
  308. end
  309. describe '#count' do
  310. it 'should run a count query and return count' do
  311. connection.stub(:execute).with("SELECT COUNT(*) FROM posts").
  312. and_return count_result_stub(4)
  313. cequel[:posts].count.should == 4
  314. end
  315. it 'should return 0 if subquery returns no results' do
  316. connection.stub(:execute).with("SELECT ? FROM posts", [:blog_id]).
  317. and_return result_stub
  318. cequel[:blogs].where(:id => cequel[:posts].select(:blog_id)).count.
  319. should == 0
  320. end
  321. it 'should use limit if specified' do
  322. connection.stub(:execute).with("SELECT COUNT(*) FROM posts LIMIT 100000").
  323. and_return count_result_stub(4)
  324. cequel[:posts].limit(100_000).count.should == 4
  325. end
  326. end
  327. end