On Tuesday, April 24, 2012 11:11:58 AM UTC-7, Rodrigo Rosenfeld Rosas 
wrote: 

> No, I start my specs with some populated data for some tables that 
> shouldn't change unless in some specific examples, but I'd like them to 
> rollback to the original state at the end of the examples. I do really want 
> to use savepoints instead of a truncate strategy or I'd be already using 
> DatabaseCleaner for that.
>

Is there a non-performance related reason you couldn't just leave the 
database empty and only add the same prepopulated data inside each spec 
(using before(:each))?  I only mention it because I use that approach in 
other specs, and it works out fine even if it isn't very fast.
 

> Your suggested approach wouldn't work pretty well as there are some 
> database constraints that would require a certain order in the tables as 
> well. This is pretty hard to maintain anyway and it is much slower than 
> "truncate table1, table2, ...".
>

Barring circular references, ordering the tables in topological order so 
that referenced tables come after referencing tables is simple.  Also, 
unless there are large numbers of records in the table, delete is usually 
not much slower than truncate.  As you are doing this around multiple specs 
and not every spec, the performance difference is unlikely to matter.
 

>  By design, there is no method that allows you to checkout a connection 
> without forcing a checkin later.  With transactions, you need to ensure 
> that all database queries use the same connection (the one that started the 
> transaction).
>
>
> Yes, I've realized that too. This makes it really hard to me to manually 
> create the savepoints by myself while still using Sequel transaction 
> support in the around(:each) block.
>

This shouldn't be "really hard":

  conn = DB.synchronize{|c| c}
  DB.send(:add_transaction, conn, {})
  DB.send(:begin_transaction, conn, {})
  DB.transaction(:savepoint=>true){}
  DB.send(:begin_transaction, conn, :savepoint=>true)
  DB.send(:rollback_transaction, conn, :savepoint=>true)
  DB.send(:remove_transaction, conn, false)
  DB.send(:begin_transaction, conn, :savepoint=>true)
  DB.send(:begin_transaction, conn, :savepoint=>true)
  DB.transaction(:savepoint=>true, :rollback=>:always){}
  DB.send(:rollback_transaction, conn, :savepoint=>true)
  DB.send(:remove_transaction, conn, false)
  DB.send(:rollback_transaction, conn, :savepoint=>true)
  DB.send(:remove_transaction, conn, false)
  DB.send(:rollback_transaction, conn, {})
  DB.send(:remove_transaction, conn, false)
 
The hard part is the error handling. :)

>  The way to do that is checking out a connection at the start of the 
> transaction block, and checking in the connection at the end of the 
> transaction block.  You cannot just checkout a connection to issue the 
> BEGIN and then check the connection back in, as the connection code 
> theoretically be used by other threads that should not be operating inside 
> the transaction.
>  
>
> Sorry this is where I got confused. Could you please give me a concrete 
> example?
>

Thread 1: get connection from pool, issue BEGIN query, return connection to 
pool
Thread 2: get connection from pool (same as just used by Thread 1), run a 
DELETE, return connection to pool
Thread 1: get connection from pool, issue ROLLBACK query, return connection 
to pool.

Result: DELETE gets rolled back, even though thread 2 didn't expect it to.

Also, you have to realize that:

  DB.synchronize{|conn| conn} == DB.synchronize{|conn| conn}
 
is not necessarily true.

>   You are free to implement such things yourself if you want.  In single 
>> threaded mode, you could probably get something that works OK.
>>  
>>
>> Sorry, but I couldn't understand what is the thread-safety role here.
>>  
>
> I'm sure if you read the connection pool and transaction related code, you 
> probably would.  You could then probably also figure out how to implement 
> what you want yourself.
>  
>
> I've read the code and I understand why it is difficult to allow the usage 
> I proposed in my previous message. But I couldn't figure it out how to 
> implement what I want by myself.
>

Hopefully the code above will help.  Handling errors correctly is up to 
you. :) 

>  To hopefully be more clear: what you want is not implemented because it 
> is a bad idea, not because it is difficult to implement.
>  
>
> Then please point me to some example on how I could do that manually as 
> I'm having troubles on understanding how to do that by myself.
>
> For example, I'd need to issue the BEGIN in the before(:all) to finally 
> issue the ROLLBACK TO SAVEPOINT in the after(:all).
>

I don't think this is what you want.  Your before(:all) and after(:all) 
code would not use a SAVEPOINT, it would BEGIN/ROLLBACK a separate 
transaction.  Your around(:each) code would use :savepoint=>true to use 
SAVEPOINT/ROLLBACK TO SAVEPOINT. Hopefully the difference there is obvious.
 

> But I need a connection from the pool for being able to do that, and I 
> can't get one because I have to use it inside a block.
>

Well, you can cheat as I did above without negative effects, but only in 
single threaded code.
 

> Also when the before(:each) is called, Sequel.transaction wouldn't know 
> what is the current depth level.
>

Calling the internal transaction methods lets Sequel keep track of the 
depth.
 

> So, as you can see, I'm totally lost on how to approach that.
>
> If I was going to run my specs concurrently, I'd make sure that a 
> different connection would be used for each "describe" block and I wouldn't 
> run multiple examples of the same outer-most block concurrently.
>

My approach is incompatible with concurrent specs.  You really do need to 
implement around(:all) for that.
 

> Could you please give me some hint on how to achieve that with Sequel?
>

Hopefully there's enough information here to do what you want.  If not, 
feel free to ask more detailed questions.

Jeremy 

-- 
You received this message because you are subscribed to the Google Groups 
"sequel-talk" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sequel-talk/-/cgOzaFmR17EJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/sequel-talk?hl=en.

Reply via email to