Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

mysql2 implementation and Rails 2.3.14 bugfix #3

Merged
merged 2 commits into from

2 participants

@jkraemer

Hi,

I needed to add an implementation for mysql2 for a recent project, maybe you'd like to merge that?
Also I fixed a bug that occured with Rails 2.3.14 where Rails' AbstractStore complained about needing to implement #destroy.

Cheers,
Jens

@fcheung fcheung merged commit 13005f2 into fcheung:master
@fcheung
Owner

Thanks. Would be nice if mysql & mysql2 stores shared more of their common code but I can live with that for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
4 README
@@ -17,7 +17,8 @@ Finally, depending on your database type, add
SmartSessionStore.session_class = MysqlSession
or
-
+ SmartSessionStore.session_class = Mysql2Session
+or
SmartSessionStore.session_class = PostgresqlSession
or
SmartSessionStore.session_class = SqliteSession
@@ -74,6 +75,7 @@ You can enable optimistic locking by adding an integer column called lock_versio
* pg (0.7.9.2008.10.13) with PostgreSQL 8.3
* ruby-mysql 2.7.1 with Mysql 4.1
* ruby-mysql 2.7.2 with Mysql 5.0
+ * mysql2 ~>0.2.7 with Mysql 5.0
2. Tests have been done with SqlLiteSession, SqlSession, PostgresqlSession
and MysqlSession. Feedback would be very much appreciated.
View
172 lib/mysql2_session.rb
@@ -0,0 +1,172 @@
+require 'mysql2'
+
+# allow access to the real Mysql connection
+class ActiveRecord::ConnectionAdapters::Mysql2Adapter
+ attr_reader :connection
+end
+
+# Mysql2Session is a down to the bare metal session store
+# implementation to be used with +SmartSessionStore+. It is much faster
+# than the default ActiveRecord implementation.
+#
+# The implementation assumes that the table column names are 'id',
+# 'data', 'created_at' and 'updated_at'. If you want use other names,
+# you will need to change the SQL statments in the code.
+
+class Mysql2Session
+
+ attr_accessor :id, :session_id, :data, :lock_version
+
+ def initialize(session_id, data)
+ @session_id = session_id
+ @data = data
+ @id = nil
+ @lock_version = 0
+ end
+
+ class << self
+
+ # retrieve the session table connection and get the 'raw' Mysql connection from it
+ def session_connection
+ SqlSession.connection.connection
+ end
+
+ def quote(arg)
+ SqlSession.connection.quote arg
+ end
+
+ def escape(arg)
+ session_connection.escape arg
+ end
+
+ def quote_escape(arg)
+ quote escape(arg)
+ end
+
+ def query(sql)
+ connection = session_connection
+ begin
+ connection.query sql
+ rescue Exception => e
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ raise ActiveRecord::StatementInvalid, message
+ end
+ end
+ # try to find a session with a given +session_id+. returns nil if
+ # no such session exists. note that we don't retrieve
+ # +created_at+ and +updated_at+ as they are not accessed anywhyere
+ # outside this class
+ def find_session(session_id, lock = false)
+ find("`session_id`=#{quote_escape session_id} LIMIT 1" + (lock ? ' FOR UPDATE' : ''))
+ end
+
+ def find_by_primary_id(primary_key_id, lock = false)
+ if primary_key_id
+ find("`id`='#{primary_key_id}'" + (lock ? ' FOR UPDATE' : ''))
+ else
+ nil
+ end
+ end
+
+ def find(conditions)
+ connection = session_connection
+ # connection.query_with_result = true
+ result = query("SELECT session_id, data,id #{ SqlSession.locking_enabled? ? ',lock_version ' : ''} FROM sessions WHERE " + conditions)
+ my_session = nil
+ # each is used below, as other methods barf on my 64bit linux machine
+ # I suspect this to be a bug in mysql-ruby
+ result.each do |row|
+ my_session = new(row[0], row[1])
+ my_session.id = row[2]
+ my_session.lock_version = row[3].to_i
+ end
+ # result.free
+ my_session
+ end
+ # create a new session with given +session_id+ and +data+
+ # and save it immediately to the database
+ def create_session(session_id, data)
+ session_id = escape(session_id)
+ new_session = new(session_id, data)
+ new_session
+ end
+
+ # delete all sessions meeting a given +condition+. it is the
+ # caller's responsibility to pass a valid sql condition
+ def delete_all(condition=nil)
+ if condition
+ query("DELETE FROM sessions WHERE #{condition}")
+ else
+ query("DELETE FROM sessions")
+ end
+ end
+
+ end # class methods
+
+ # update session with given +data+.
+ # unlike the default implementation using ActiveRecord, updating of
+ # column `updated_at` will be done by the datbase itself
+ def update_session(data)
+ connection = self.class.session_connection
+ if @id
+ # if @id is not nil, this is a session already stored in the database
+ # update the relevant field using @id as key
+ if SqlSession.locking_enabled?
+ self.class.query("UPDATE sessions SET `updated_at`=NOW(), `data`=#{self.class.quote(data)}, lock_version=lock_version+1 WHERE id=#{@id}")
+ @lock_version += 1 #if we are here then we hold a lock on the table - we know our version is up to date
+ else
+ self.class.query("UPDATE sessions SET `updated_at`=NOW(), `data`=#{self.class.quote(data)} WHERE id=#{@id}")
+ end
+ else
+ # if @id is nil, we need to create a new session in the database
+ # and set @id to the primary key of the inserted record
+ self.class.query("INSERT INTO sessions (`updated_at`, `session_id`, `data`) VALUES (NOW(), '#{@session_id}', #{self.class.quote(data)})")
+ @id = connection.last_id
+ @lock_version = 0
+ end
+ end
+
+ def update_session_optimistically(data)
+ raise 'cannot update unsaved record optimistically' unless @id
+ connection = self.class.session_connection
+ self.class.query("UPDATE sessions SET `updated_at`=NOW(), `data`=#{self.class.quote(data)}, `lock_version`=`lock_version`+1 WHERE id=#{@id} AND lock_version=#{@lock_version}")
+ if connection.affected_rows == 1
+ @lock_version += 1
+ true
+ else
+ false
+ end
+ end
+ # destroy the current session
+ def destroy
+ self.class.delete_all("session_id='#{session_id}'")
+ end
+
+end
+
+__END__
+
+# This software is released under the MIT license
+#
+# Copyright (c) 2011 Jens Kraemer
+# Copyright (c) 2007 Frederick Cheung
+# Copyright (c) 2005,2006 Stefan Kaes
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
8 lib/smart_session_store.rb
@@ -57,7 +57,13 @@ def find_session(id)
@@session_class.find_session(id) ||
@@session_class.create_session(id, marshalize({}))
end
-
+
+ # Rails 2.3.14 needs this
+ def destroy(env)
+ if (sid = current_session_id(env)).present? && session = find_session(sid)
+ session.destroy
+ end
+ end
end
__END__
View
7 test/database.yml
@@ -9,6 +9,13 @@ mysql:
:password:
:database: smart_session
+mysql2:
+ :adapter: mysql2
+ :host: localhost
+ :username: smart_session
+ :password:
+ :database: smart_session
+
postgresql:
:adapter: postgresql
:username: smart_session
View
1  test/test_helper.rb
@@ -49,6 +49,7 @@ def create_fixtures(*table_names, &block)
TEST_SESSION_CLASS = case database_type
when 'mysql' then MysqlSession
+ when 'mysql2' then Mysql2Session
when 'postgresql' then PostgresqlSession
when 'sqlite3' then SqliteSession
end
Something went wrong with that request. Please try again.