20318: Implement BlockRead via ReadAt so it supports streaming.
[arvados.git] / services / api / test / unit / permission_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6
7 class PermissionTest < ActiveSupport::TestCase
8   include CurrentApiClient
9
10   test "Grant permissions on an object I own" do
11     set_user_from_auth :active_trustedclient
12
13     ob = Collection.create
14     assert ob.save
15
16     # Ensure I have permission to manage this group even when its owner changes
17     perm_link = Link.create(tail_uuid: users(:active).uuid,
18                             head_uuid: ob.uuid,
19                             link_class: 'permission',
20                             name: 'can_manage')
21     assert perm_link.save, "should give myself permission on my own object"
22   end
23
24   test "Delete permission links when deleting an object" do
25     set_user_from_auth :active_trustedclient
26
27     ob = Collection.create!
28     Link.create!(tail_uuid: users(:active).uuid,
29                  head_uuid: ob.uuid,
30                  link_class: 'permission',
31                  name: 'can_manage')
32     ob_uuid = ob.uuid
33     assert ob.destroy, "Could not destroy object with 1 permission link"
34     assert_empty(Link.where(head_uuid: ob_uuid),
35                  "Permission link was not deleted when object was deleted")
36   end
37
38   test "permission links owned by root" do
39     set_user_from_auth :active_trustedclient
40     ob = Collection.create!
41     perm_link = Link.create!(tail_uuid: users(:active).uuid,
42                              head_uuid: ob.uuid,
43                              link_class: 'permission',
44                              name: 'can_read')
45     assert_equal system_user_uuid, perm_link.owner_uuid
46   end
47
48   test "readable_by" do
49     set_user_from_auth :admin
50
51     ob = Collection.create!
52     Link.create!(tail_uuid: users(:active).uuid,
53                  head_uuid: ob.uuid,
54                  link_class: 'permission',
55                  name: 'can_read')
56     assert Collection.readable_by(users(:active)).where(uuid: ob.uuid).any?, "user does not have read permission"
57   end
58
59   test "writable_by" do
60     set_user_from_auth :admin
61
62     ob = Collection.create!
63     Link.create!(tail_uuid: users(:active).uuid,
64                  head_uuid: ob.uuid,
65                  link_class: 'permission',
66                  name: 'can_write')
67     assert ob.writable_by.include?(users(:active).uuid), "user does not have write permission"
68   end
69
70   test "update permission link" do
71     set_user_from_auth :admin
72
73     grp = Group.create! name: "blah project", group_class: "project"
74     ob = Collection.create! owner_uuid: grp.uuid
75
76     assert !users(:active).can?(write: ob)
77     assert !users(:active).can?(read: ob)
78
79     l1 = Link.create!(tail_uuid: users(:active).uuid,
80                  head_uuid: grp.uuid,
81                  link_class: 'permission',
82                  name: 'can_write')
83
84     assert users(:active).can?(write: ob)
85     assert users(:active).can?(read: ob)
86
87     l1.update!(name: 'can_read')
88
89     assert !users(:active).can?(write: ob)
90     assert users(:active).can?(read: ob)
91
92     l1.destroy
93
94     assert !users(:active).can?(write: ob)
95     assert !users(:active).can?(read: ob)
96   end
97
98   test "writable_by reports requesting user's own uuid for a writable project" do
99     invited_to_write = users(:project_viewer)
100     group = groups(:asubproject)
101
102     # project_view can read, but cannot see write or see writers list
103     set_user_from_auth :project_viewer
104     assert_equal([group.owner_uuid],
105                  group.writable_by,
106                  "writers list should just have owner_uuid")
107
108     # allow project_viewer to write for the remainder of the test
109     set_user_from_auth :admin
110     Link.create!(tail_uuid: invited_to_write.uuid,
111                  head_uuid: group.uuid,
112                  link_class: 'permission',
113                  name: 'can_write')
114     group.permissions.reload
115
116     # project_viewer should see self in writers list (but not all writers)
117     set_user_from_auth :project_viewer
118     assert_not_nil(group.writable_by,
119                     "can write but cannot see writers list")
120     assert_includes(group.writable_by, invited_to_write.uuid,
121                     "self missing from writers list")
122     assert_includes(group.writable_by, group.owner_uuid,
123                     "project owner missing from writers list")
124     refute_includes(group.writable_by, users(:active).uuid,
125                     "saw :active user in writers list")
126
127     # active user should see full writers list
128     set_user_from_auth :active
129     assert_includes(group.writable_by, invited_to_write.uuid,
130                     "permission just added, but missing from writers list")
131
132     # allow project_viewer to manage for the remainder of the test
133     set_user_from_auth :admin
134     Link.create!(tail_uuid: invited_to_write.uuid,
135                  head_uuid: group.uuid,
136                  link_class: 'permission',
137                  name: 'can_manage')
138     # invite another writer we can test for
139     Link.create!(tail_uuid: users(:spectator).uuid,
140                  head_uuid: group.uuid,
141                  link_class: 'permission',
142                  name: 'can_write')
143     group.permissions.reload
144
145     set_user_from_auth :project_viewer
146     assert_not_nil(group.writable_by,
147                     "can manage but cannot see writers list")
148     assert_includes(group.writable_by, users(:spectator).uuid,
149                     ":spectator missing from writers list")
150   end
151
152   test "user owns group, group can_manage object's group, user can add permissions" do
153     set_user_from_auth :admin
154
155     owner_grp = Group.create!(owner_uuid: users(:active).uuid, group_class: "role")
156
157     sp_grp = Group.create!(group_class: "project")
158
159     Link.create!(link_class: 'permission',
160                  name: 'can_manage',
161                  tail_uuid: owner_grp.uuid,
162                  head_uuid: sp_grp.uuid)
163
164     sp = Collection.create!(owner_uuid: sp_grp.uuid)
165
166     # active user owns owner_grp, which has can_manage permission on sp_grp
167     # user should be able to add permissions on sp.
168     set_user_from_auth :active_trustedclient
169     test_perm = Link.create(tail_uuid: users(:active).uuid,
170                             head_uuid: sp.uuid,
171                             link_class: 'permission',
172                             name: 'can_write')
173     assert test_perm.save, "could not save new permission on target object"
174     assert test_perm.destroy, "could not delete new permission on target object"
175   end
176
177   # bug #3091
178   skip "can_manage permission on a non-group object" do
179     set_user_from_auth :admin
180
181     ob = Collection.create!
182     # grant can_manage permission to active
183     perm_link = Link.create!(tail_uuid: users(:active).uuid,
184                              head_uuid: ob.uuid,
185                              link_class: 'permission',
186                              name: 'can_manage')
187     # ob is owned by :admin, the link is owned by root
188     assert_equal users(:admin).uuid, ob.owner_uuid
189     assert_equal system_user_uuid, perm_link.owner_uuid
190
191     # user "active" can modify the permission link
192     set_user_from_auth :active_trustedclient
193     perm_link.properties["foo"] = 'bar'
194     assert perm_link.save, "could not save modified link"
195
196     assert_equal 'bar', perm_link.properties['foo'], "link properties do not include foo = bar"
197   end
198
199   test "user without can_manage permission may not modify permission link" do
200     set_user_from_auth :admin
201
202     ob = Collection.create!
203     # grant can_manage permission to active
204     perm_link = Link.create!(tail_uuid: users(:active).uuid,
205                              head_uuid: ob.uuid,
206                              link_class: 'permission',
207                              name: 'can_read')
208     # ob is owned by :admin, the link is owned by root
209     assert_equal ob.owner_uuid, users(:admin).uuid
210     assert_equal perm_link.owner_uuid, system_user_uuid
211
212     # user "active" may not modify the permission link
213     set_user_from_auth :active_trustedclient
214     perm_link.name = 'can_manage'
215     assert_raises ArvadosModel::PermissionDeniedError do
216       perm_link.save
217     end
218   end
219
220   test "manager user gets permission to minions' articles via can_manage link" do
221     Rails.configuration.Users.RoleGroupsVisibleToAll = false
222     Rails.configuration.Users.ActivatedUsersAreVisibleToOthers = false
223     manager = create :active_user, first_name: "Manage", last_name: "Er"
224     minion = create :active_user, first_name: "Min", last_name: "Ion"
225     minions_specimen = act_as_user minion do
226       g = Group.create! name: "minon project", group_class: "project"
227       Collection.create! owner_uuid: g.uuid
228     end
229     # Manager creates a group. (Make sure it doesn't magically give
230     # anyone any additional permissions.)
231     g = nil
232     act_as_user manager do
233       g = create :group, name: "NoBigSecret Lab", group_class: "role"
234       assert_empty(User.readable_by(manager).where(uuid: minion.uuid),
235                    "saw a user I shouldn't see")
236       assert_raises(ArvadosModel::PermissionDeniedError,
237                     ActiveRecord::RecordInvalid,
238                     "gave can_read permission to a user I shouldn't see") do
239         create(:permission_link,
240                name: 'can_read', tail_uuid: minion.uuid, head_uuid: g.uuid)
241       end
242       %w(can_manage can_write can_read).each do |perm_type|
243         assert_raises(ArvadosModel::PermissionDeniedError,
244                       ActiveRecord::RecordInvalid,
245                       "escalated privileges") do
246           create(:permission_link,
247                  name: perm_type, tail_uuid: g.uuid, head_uuid: minion.uuid)
248         end
249       end
250       assert_empty(User.readable_by(manager).where(uuid: minion.uuid),
251                    "manager saw minion too soon")
252       assert_empty(User.readable_by(minion).where(uuid: manager.uuid),
253                    "minion saw manager too soon")
254       assert_empty(Group.readable_by(minion).where(uuid: g.uuid),
255                    "minion saw manager's new NoBigSecret Lab group too soon")
256
257       # Manager declares everybody on the system should be able to see
258       # the NoBigSecret Lab group.
259       create(:permission_link,
260              name: 'can_read',
261              tail_uuid: 'zzzzz-j7d0g-fffffffffffffff',
262              head_uuid: g.uuid)
263       # ...but nobody has joined the group yet. Manager still can't see
264       # minion.
265       assert_empty(User.readable_by(manager).where(uuid: minion.uuid),
266                    "manager saw minion too soon")
267     end
268
269     act_as_user minion do
270       # Minion can see the group.
271       assert_not_empty(Group.readable_by(minion).where(uuid: g.uuid),
272                        "minion could not see the NoBigSecret Lab group")
273       # Minion joins the group.
274       create(:permission_link,
275              name: 'can_read',
276              tail_uuid: g.uuid,
277              head_uuid: minion.uuid)
278     end
279
280     act_as_user manager do
281       # Now, manager can see minion.
282       assert_not_empty(User.readable_by(manager).where(uuid: minion.uuid),
283                        "manager could not see minion")
284       # But cannot obtain further privileges this way.
285       assert_raises(ArvadosModel::PermissionDeniedError,
286                     "escalated privileges") do
287         create(:permission_link,
288                name: 'can_manage', tail_uuid: manager.uuid, head_uuid: minion.uuid)
289       end
290       assert_empty(Collection
291                      .readable_by(manager)
292                      .where(uuid: minions_specimen.uuid),
293                    "manager saw the minion's private stuff")
294       assert_raises(ArvadosModel::PermissionDeniedError,
295                    "manager could update minion's private stuff") do
296         minions_specimen.update(properties: {'x' => 'y'})
297       end
298     end
299
300     act_as_system_user do
301       # Root can give Manager more privileges over Minion.
302       create(:permission_link,
303              name: 'can_manage', tail_uuid: g.uuid, head_uuid: minion.uuid)
304     end
305
306     act_as_user manager do
307       # Now, manager can read and write Minion's stuff.
308       assert_not_empty(Collection
309                          .readable_by(manager)
310                          .where(uuid: minions_specimen.uuid),
311                        "manager could not find minion's specimen by uuid")
312       assert_equal(true,
313                    minions_specimen.update(properties: {'x' => 'y'}),
314                    "manager could not update minion's specimen object")
315     end
316   end
317
318   test "users with bidirectional read permission in group can see each other, but cannot see each other's private articles" do
319     Rails.configuration.Users.ActivatedUsersAreVisibleToOthers = false
320     a = create :active_user, first_name: "A"
321     b = create :active_user, first_name: "B"
322     other = create :active_user, first_name: "OTHER"
323
324     assert_empty(User.readable_by(b).where(uuid: a.uuid),
325                      "#{b.first_name} should not be able to see 'a' in the user list")
326     assert_empty(User.readable_by(a).where(uuid: b.uuid),
327                      "#{a.first_name} should not be able to see 'b' in the user list")
328
329     act_as_system_user do
330       g = create :group, group_class: "role"
331       [a,b].each do |u|
332         create(:permission_link,
333                name: 'can_read', tail_uuid: u.uuid, head_uuid: g.uuid)
334         create(:permission_link,
335                name: 'can_read', head_uuid: u.uuid, tail_uuid: g.uuid)
336       end
337     end
338
339     assert_not_empty(User.readable_by(b).where(uuid: a.uuid),
340                      "#{b.first_name} should be able to see 'a' in the user list")
341     assert_not_empty(User.readable_by(a).where(uuid: b.uuid),
342                      "#{a.first_name} should be able to see 'b' in the user list")
343
344     a_specimen = act_as_user a do
345       Collection.create!
346     end
347     assert_not_empty(Collection.readable_by(a).where(uuid: a_specimen.uuid),
348                      "A cannot read own Collection, following test probably useless.")
349     assert_empty(Collection.readable_by(b).where(uuid: a_specimen.uuid),
350                  "B can read A's Collection")
351     [a,b].each do |u|
352       assert_empty(User.readable_by(u).where(uuid: other.uuid),
353                    "#{u.first_name} can see OTHER in the user list")
354       assert_empty(User.readable_by(other).where(uuid: u.uuid),
355                    "OTHER can see #{u.first_name} in the user list")
356       act_as_user u do
357         assert_raises ArvadosModel::PermissionDeniedError, "wrote without perm" do
358           other.update!(prefs: {'pwned' => true})
359         end
360         assert_equal(true, u.update!(prefs: {'thisisme' => true}),
361                      "#{u.first_name} can't update its own prefs")
362       end
363       act_as_user other do
364         assert_raises(ArvadosModel::PermissionDeniedError,
365                         "OTHER wrote #{u.first_name} without perm") do
366           u.update!(prefs: {'pwned' => true})
367         end
368         assert_equal(true, other.update!(prefs: {'thisisme' => true}),
369                      "OTHER can't update its own prefs")
370       end
371     end
372   end
373
374   test "cannot create with owner = unwritable user" do
375     set_user_from_auth :rominiadmin
376     assert_raises ArvadosModel::PermissionDeniedError, "created with owner = unwritable user" do
377       Collection.create!(owner_uuid: users(:active).uuid)
378     end
379   end
380
381   test "cannot change owner to unwritable user" do
382     set_user_from_auth :rominiadmin
383     ob = Collection.create!
384     assert_raises ArvadosModel::PermissionDeniedError, "changed owner to unwritable user" do
385       ob.update!(owner_uuid: users(:active).uuid)
386     end
387   end
388
389   test "cannot create with owner = unwritable group" do
390     set_user_from_auth :rominiadmin
391     assert_raises ArvadosModel::PermissionDeniedError, "created with owner = unwritable group" do
392       Collection.create!(owner_uuid: groups(:aproject).uuid)
393     end
394   end
395
396   test "cannot change owner to unwritable group" do
397     set_user_from_auth :rominiadmin
398     ob = Collection.create!
399     assert_raises ArvadosModel::PermissionDeniedError, "changed owner to unwritable group" do
400       ob.update!(owner_uuid: groups(:aproject).uuid)
401     end
402   end
403
404   def container_logs(container, user)
405     Log.readable_by(users(user)).
406       where(object_uuid: containers(container).uuid, event_type: "test")
407   end
408
409   test "container logs created by dispatch are visible to container requestor" do
410     set_user_from_auth :dispatch1
411     Log.create!(object_uuid: containers(:running).uuid,
412                 event_type: "test")
413
414     assert_not_empty container_logs(:running, :admin)
415     assert_not_empty container_logs(:running, :active)
416     assert_empty container_logs(:running, :spectator)
417   end
418
419   test "container logs created by dispatch are public if container request is public" do
420     set_user_from_auth :dispatch1
421     Log.create!(object_uuid: containers(:running_older).uuid,
422                 event_type: "test")
423
424     assert_not_empty container_logs(:running_older, :anonymous)
425   end
426
427   test "add user to group, then remove them" do
428     set_user_from_auth :admin
429     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "role")
430     col = Collection.create!(owner_uuid: system_user_uuid)
431
432     l0 = Link.create!(tail_uuid: grp.uuid,
433                  head_uuid: col.uuid,
434                  link_class: 'permission',
435                  name: 'can_read')
436
437     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
438     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
439
440     l1 = Link.create!(tail_uuid: users(:active).uuid,
441                  head_uuid: grp.uuid,
442                  link_class: 'permission',
443                  name: 'can_read')
444     l2 = Link.create!(tail_uuid: grp.uuid,
445                  head_uuid: users(:active).uuid,
446                  link_class: 'permission',
447                  name: 'can_read')
448
449     l3 = Link.create!(tail_uuid: users(:project_viewer).uuid,
450                  head_uuid: grp.uuid,
451                  link_class: 'permission',
452                  name: 'can_read')
453     l4 = Link.create!(tail_uuid: grp.uuid,
454                  head_uuid: users(:project_viewer).uuid,
455                  link_class: 'permission',
456                  name: 'can_read')
457
458     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
459     assert User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid).first
460
461     l1.destroy
462     l2.destroy
463
464     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
465     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
466
467   end
468
469
470   test "add user to group, then change permission level" do
471     set_user_from_auth :admin
472     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "project")
473     col = Collection.create!(owner_uuid: grp.uuid)
474     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
475     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
476
477     l1 = Link.create!(tail_uuid: users(:active).uuid,
478                  head_uuid: grp.uuid,
479                  link_class: 'permission',
480                  name: 'can_manage')
481
482     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
483     assert users(:active).can?(read: col.uuid)
484     assert users(:active).can?(write: col.uuid)
485     assert users(:active).can?(manage: col.uuid)
486
487     l1.name = 'can_read'
488     l1.save!
489
490     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
491     assert users(:active).can?(read: col.uuid)
492     assert !users(:active).can?(write: col.uuid)
493     assert !users(:active).can?(manage: col.uuid)
494
495     l1.name = 'can_write'
496     l1.save!
497
498     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
499     assert users(:active).can?(read: col.uuid)
500     assert users(:active).can?(write: col.uuid)
501     assert !users(:active).can?(manage: col.uuid)
502   end
503
504
505   test "add user to group, then add overlapping permission link to group" do
506     set_user_from_auth :admin
507     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "project")
508     col = Collection.create!(owner_uuid: grp.uuid)
509     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
510     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
511
512     l1 = Link.create!(tail_uuid: users(:active).uuid,
513                  head_uuid: grp.uuid,
514                  link_class: 'permission',
515                  name: 'can_manage')
516
517     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
518     assert users(:active).can?(read: col.uuid)
519     assert users(:active).can?(write: col.uuid)
520     assert users(:active).can?(manage: col.uuid)
521
522     l3 = Link.create!(tail_uuid: users(:active).uuid,
523                  head_uuid: grp.uuid,
524                  link_class: 'permission',
525                  name: 'can_read')
526
527     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
528     assert users(:active).can?(read: col.uuid)
529     assert users(:active).can?(write: col.uuid)
530     assert users(:active).can?(manage: col.uuid)
531
532     # Creating l3 should have automatically deleted l1 and upgraded to
533     # the max permission of {l1, l3}, i.e., can_manage (see #18693) so
534     # there should be no can_read link now.
535     refute Link.where(tail_uuid: l3.tail_uuid,
536                       head_uuid: l3.head_uuid,
537                       link_class: 'permission',
538                       name: 'can_read').any?
539
540     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
541     assert users(:active).can?(read: col.uuid)
542     assert users(:active).can?(write: col.uuid)
543     assert users(:active).can?(manage: col.uuid)
544   end
545
546
547   test "add user to group, then add overlapping permission link to subproject" do
548     set_user_from_auth :admin
549     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "role")
550     prj = Group.create!(owner_uuid: system_user_uuid, group_class: "project")
551
552     l0 = Link.create!(tail_uuid: grp.uuid,
553                  head_uuid: prj.uuid,
554                  link_class: 'permission',
555                  name: 'can_manage')
556
557     assert_empty Group.readable_by(users(:active)).where(uuid: prj.uuid)
558     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
559
560     l1 = Link.create!(tail_uuid: users(:active).uuid,
561                  head_uuid: grp.uuid,
562                  link_class: 'permission',
563                  name: 'can_manage')
564     l2 = Link.create!(tail_uuid: grp.uuid,
565                  head_uuid: users(:active).uuid,
566                  link_class: 'permission',
567                  name: 'can_read')
568
569     assert Group.readable_by(users(:active)).where(uuid: prj.uuid).first
570     assert users(:active).can?(read: prj.uuid)
571     assert users(:active).can?(write: prj.uuid)
572     assert users(:active).can?(manage: prj.uuid)
573
574     l3 = Link.create!(tail_uuid: grp.uuid,
575                  head_uuid: prj.uuid,
576                  link_class: 'permission',
577                  name: 'can_read')
578
579     assert Group.readable_by(users(:active)).where(uuid: prj.uuid).first
580     assert users(:active).can?(read: prj.uuid)
581     assert users(:active).can?(write: prj.uuid)
582     assert users(:active).can?(manage: prj.uuid)
583
584     # Creating l3 should have automatically deleted l0 and upgraded to
585     # the max permission of {l0, l3}, i.e., can_manage (see #18693) so
586     # there should be no can_read link now.
587     refute Link.where(tail_uuid: l3.tail_uuid,
588                       head_uuid: l3.head_uuid,
589                       link_class: 'permission',
590                       name: 'can_read').any?
591
592     assert Group.readable_by(users(:active)).where(uuid: prj.uuid).first
593     assert users(:active).can?(read: prj.uuid)
594     assert users(:active).can?(write: prj.uuid)
595     assert users(:active).can?(manage: prj.uuid)
596   end
597
598   [system_user_uuid, anonymous_user_uuid].each do |u|
599     test "cannot delete system user #{u}" do
600       act_as_system_user do
601         assert_raises ArvadosModel::PermissionDeniedError do
602           User.find_by_uuid(u).destroy
603         end
604       end
605     end
606   end
607
608   [system_group_uuid, anonymous_group_uuid, public_project_uuid].each do |g|
609     test "cannot delete system group #{g}" do
610       act_as_system_user do
611         assert_raises ArvadosModel::PermissionDeniedError do
612           Group.find_by_uuid(g).destroy
613         end
614       end
615     end
616   end
617
618   # Show query plan for readable_by query. The plan for a test db
619   # might not resemble the plan for a production db, but it doesn't
620   # hurt to show the test db plan in test logs, and the .
621   [false, true].each do |include_trash|
622     test "query plan, include_trash=#{include_trash}" do
623       sql = Collection.readable_by(users(:active), include_trash: include_trash).to_sql
624       sql = "explain analyze #{sql}"
625       STDERR.puts sql
626       q = ActiveRecord::Base.connection.exec_query(sql)
627       q.rows.each do |row| STDERR.puts(row) end
628     end
629   end
630 end