Em 24-04-2012 16:24, Jeremy Evans escreveu:
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.
Performance is a major reason indeed. I don't *need* to keep the
prepopulated data and since they're currently pretty simple ones (just a
few fixed possible user roles) I could recreate them on each test
requiring a user (most of them). But another issue is that I cache those
roles once I read them from the database in the application level
because they'll never change. I'm using standalone-migrations for
managing my databases:
https://github.com/thuss/standalone-migrations
Usually I use a copy of a freshly created database by using "rake
db:setup" which will also fill the "role" table.
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.
That was not my experience when I tried that approach on a Grails
application some years ago. For example, calling truncate in all tables
at once was much faster than calling truncate on each separate table in
dependency order.
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}
Humm, this is what I was looking for. I didn't realize how to get the
connection from the pool without releasing it at the end of the block. I
thought that synchronized would release the connection, but if it
doesn't, this is great!
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. :)
Shouldn't be that hard in my case I guess. I'll give it a try as soon as
I find some time to play with this.
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
This is what I find weird. In what situation Thread 1 would return the
connection to the pool before finishing the transaction logic? That
doesn't make any sense to me in real application scenarios.
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.
But in your example above:
conn = DB.synchronize{|c| c}
DB.send(:add_transaction, conn, {})
DB.send(:begin_transaction, conn, {})
DB.transaction(:savepoint=>true){}
Will this last statement use the same connection as conn? Also, will
DB.transaction be aware that the depth level is now 1 instead of 0? Or
will it try to perform another BEGIN?
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.
Yeah, sorry, I've sent the correct flow in my previous message examples.
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.
Thanks, I didn't know that before.
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.
You mean DB.send(:_transaction), right?
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.
Yeah, I'll try to worry about that when I need to. Another approach
would be use multiple processes and different databases to run the specs
in parallel but still using a single thread per process so I could work
around this.
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.
Thank you very much Jeremy. I guess the "hardest" part was "conn =
DB.syncronize{|c| c}" :)
I'll let you know if I find any troubles with that approach, thanks! :)
Cheers,
Rodrigo.
--
You received this message because you are subscribed to the Google Groups
"sequel-talk" group.
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.